Files
aego/contracts/shapes2d/validate.go
T
sanspie a6822156cd
ci-platforms / windows amd64 smoke (push) Skipped
ci / release (push) Successful in 8m58s
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / darwin arm64 smoke (push) Skipped
ci / checks (push) Successful in 4m46s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 13m28s
Finish Stage 12 editor fixes and refreshed platformer
2026-11-05 17:36:42 +03:00

82 lines
2.6 KiB
Go

package shapes2d
import (
"aego/core/mathx"
"errors"
"math"
)
func finiteContour(v float32) bool { return !math.IsNaN(float64(v)) && !math.IsInf(float64(v), 0) }
// ValidateContour checks a simple closed polygon or open chain at a geometry revision boundary.
// It rejects holes, crossings, nonfinite points and zero-length edges, but accepts concave polygons and either winding.
func ValidateContour(points []mathx.Vec2, closed bool) error {
if len(points) > MaxContourPoints {
return ErrBudget
}
if len(points) < 2 || closed && len(points) < 3 {
return errors.New("a chain needs two points and a closed contour needs three")
}
for _, p := range points {
if !finiteContour(p.X) || !finiteContour(p.Y) {
return errors.New("shape coordinates must be finite")
}
}
edges := len(points) - 1
if closed {
edges++
}
for i := 0; i < edges; i++ {
a, b := points[i], points[(i+1)%len(points)]
if a == b {
return errors.New("shape has a zero-length edge")
}
for j := i + 2; j < edges; j++ {
if closed && i == 0 && j == edges-1 {
continue
}
if shapeSegmentsMeet(a, b, points[j], points[(j+1)%len(points)]) {
return errors.New("shape contour intersects itself")
}
}
}
for i := 0; i < len(points); i++ {
if !closed && (i == 0 || i == len(points)-1) {
continue
}
a, b, c := points[(i+len(points)-1)%len(points)], points[i], points[(i+1)%len(points)]
if shapeOrient(a, b, c) == 0 &&
(float64(a.X)-float64(b.X))*(float64(c.X)-float64(b.X))+(float64(a.Y)-float64(b.Y))*(float64(c.Y)-float64(b.Y)) > 0 {
return errors.New("adjacent shape edges overlap")
}
}
if closed {
// Relative coordinates and float64 accumulation remain reliable for
// small shapes far from the local origin.
o := points[0]
area := 0.0
for i := 1; i+1 < len(points); i++ {
area += shapeOrient(o, points[i], points[i+1])
}
if area == 0 {
return errors.New("closed shape has zero area")
}
}
return nil
}
func shapeOrient(a, b, c mathx.Vec2) float64 {
return (float64(b.X)-float64(a.X))*(float64(c.Y)-float64(a.Y)) - (float64(b.Y)-float64(a.Y))*(float64(c.X)-float64(a.X))
}
func shapeSegmentsMeet(a, b, c, d mathx.Vec2) bool {
abc, abd, cda, cdb := shapeOrient(a, b, c), shapeOrient(a, b, d), shapeOrient(c, d, a), shapeOrient(c, d, b)
between := func(a, b, p mathx.Vec2) bool {
return p.X >= min(a.X, b.X) && p.X <= max(a.X, b.X) && p.Y >= min(a.Y, b.Y) && p.Y <= max(a.Y, b.Y)
}
if abc == 0 && between(a, b, c) || abd == 0 && between(a, b, d) || cda == 0 && between(c, d, a) || cdb == 0 && between(c, d, b) {
return true
}
return (abc < 0 && abd > 0 || abc > 0 && abd < 0) && (cda < 0 && cdb > 0 || cda > 0 && cdb < 0)
}