-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathzcoord_test.go
More file actions
143 lines (128 loc) · 5.04 KB
/
Copy pathzcoord_test.go
File metadata and controls
143 lines (128 loc) · 5.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package polyclip
import (
"testing"
"github.com/lestrrat-go/polyclip/geom"
"github.com/stretchr/testify/require"
)
// constZ assigns a fixed Z to every crossing vertex.
type constZ float64
func (c constZ) AssignZ(_, _, _, _, _ geom.Point) float64 { return float64(c) }
// coordZ encodes the crossing point into Z as X*100+Y, so a test can assert
// both that the callback fired and that it received the right crossing point.
type coordZ struct{}
func (coordZ) AssignZ(_, _, _, _ geom.Point, crossing geom.Point) float64 {
return crossing.X*100 + crossing.Y
}
// recordZ captures every AssignZ call for inspection.
type recordZ struct {
calls [][5]geom.Point
}
func (r *recordZ) AssignZ(e1bot, e1top, e2bot, e2top, crossing geom.Point) float64 {
r.calls = append(r.calls, [5]geom.Point{e1bot, e1top, e2bot, e2top, crossing})
return 0
}
// zAt returns the Z of the result vertex at (x,y), or NaN-substitute via found.
func zAt(m geom.MultiPolygon, x, y float64) (float64, bool) {
for _, ex := range m {
for _, p := range ex.Outer {
if p.X == x && p.Y == y {
return p.Z, true
}
}
for _, h := range ex.Holes {
for _, p := range h {
if p.X == x && p.Y == y {
return p.Z, true
}
}
}
}
return 0, false
}
func TestBuilderZCrossingAndInputPreserved(t *testing.T) {
// A = [0,0]-[10,10] with Z=1 on every vertex; B = [5,5]-[15,15] with Z=2.
// Intersect = [5,5]-[10,10]. Its corners:
// (5,5) B's vertex inside A -> input Z 2
// (10,10) A's vertex inside B -> input Z 1
// (10,5) crossing A-right×B-bottom -> assigned 10*100+5 = 1005
// (5,10) crossing A-top×B-left -> assigned 5*100+10 = 510
a := geom.New().
Point3(0, 0, 1).Point3(10, 0, 1).Point3(10, 10, 1).Point3(0, 10, 1).
MustBuild()
b := geom.New().
Point3(5, 5, 2).Point3(15, 5, 2).Point3(15, 15, 2).Point3(5, 15, 2).
MustBuild()
res, err := New().AddSubject(a).AddClip(b).SetZAssigner(coordZ{}).Execute(OpIntersect)
require.NoError(t, err)
want := map[[2]float64]float64{
{5, 5}: 2,
{10, 10}: 1,
{10, 5}: 1005,
{5, 10}: 510,
}
for xy, wz := range want {
z, ok := zAt(res.Closed, xy[0], xy[1])
require.True(t, ok, "vertex (%g,%g) missing from result", xy[0], xy[1])
require.Equal(t, wz, z, "Z at (%g,%g) = %g, want %g", xy[0], xy[1], z, wz)
}
}
func TestBuilderZDisabledIsZero(t *testing.T) {
// Without an assigner, output Z is zero even when inputs carry Z (the engine
// ignores Z; the free functions never touch it).
a := geom.New().
Point3(0, 0, 1).Point3(10, 0, 1).Point3(10, 10, 1).Point3(0, 10, 1).
MustBuild()
b := geom.New().
Point3(5, 5, 2).Point3(15, 5, 2).Point3(15, 15, 2).Point3(5, 15, 2).
MustBuild()
res, err := New().AddSubject(a).AddClip(b).Execute(OpIntersect)
require.NoError(t, err)
for _, ex := range res.Closed {
for _, p := range ex.Outer {
require.Equal(t, float64(0), p.Z, "Z = %g at (%g,%g), want 0 (tracking disabled)", p.Z, p.X, p.Y)
}
}
}
func TestBuilderZConstantOnAllCrossings(t *testing.T) {
// Every corner of the intersection rectangle that is a genuine crossing gets
// the constant; the two corners that are input vertices keep Z 0 (inputs here
// carry no Z).
a := geom.New().Point(0, 0).Point(10, 0).Point(10, 10).Point(0, 10).MustBuild()
b := geom.New().Point(5, 5).Point(15, 5).Point(15, 15).Point(5, 15).MustBuild()
res, err := New().AddSubject(a).AddClip(b).SetZAssigner(constZ(7)).Execute(OpIntersect)
require.NoError(t, err)
for _, xy := range [][2]float64{{10, 5}, {5, 10}} {
z, ok := zAt(res.Closed, xy[0], xy[1])
require.True(t, ok && z == 7, "crossing (%g,%g): Z=%g ok=%v, want 7", xy[0], xy[1], z, ok)
}
}
func TestBuilderZAssignerEndpoints(t *testing.T) {
// The assigner is called once per genuine crossing with that crossing's
// point; the two crossings of the overlapping squares are (10,5) and (5,10).
a := geom.New().Point(0, 0).Point(10, 0).Point(10, 10).Point(0, 10).MustBuild()
b := geom.New().Point(5, 5).Point(15, 5).Point(15, 15).Point(5, 15).MustBuild()
rec := &recordZ{}
_, err := New().AddSubject(a).AddClip(b).SetZAssigner(rec).Execute(OpIntersect)
require.NoError(t, err)
require.Len(t, rec.calls, 2, "AssignZ called %d times, want 2", len(rec.calls))
seen := map[[2]float64]bool{}
for _, c := range rec.calls {
cr := c[4]
seen[[2]float64{cr.X, cr.Y}] = true
}
for _, xy := range [][2]float64{{10, 5}, {5, 10}} {
require.True(t, seen[xy], "no AssignZ call for crossing (%g,%g)", xy[0], xy[1])
}
}
func TestBuilderZXorComposition(t *testing.T) {
// Xor is computed by composition (Union, Intersect, Difference). Z tracking
// must still flow through: the same crossing points appear and get assigned.
a := geom.New().Point(0, 0).Point(10, 0).Point(10, 10).Point(0, 10).MustBuild()
b := geom.New().Point(5, 5).Point(15, 5).Point(15, 15).Point(5, 15).MustBuild()
res, err := New().AddSubject(a).AddClip(b).SetZAssigner(constZ(9)).Execute(OpXor)
require.NoError(t, err)
for _, xy := range [][2]float64{{10, 5}, {5, 10}} {
z, ok := zAt(res.Closed, xy[0], xy[1])
require.True(t, ok && z == 9, "Xor crossing (%g,%g): Z=%g ok=%v, want 9", xy[0], xy[1], z, ok)
}
}