1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package s2
16
17 import (
18 "fmt"
19 "math"
20 "testing"
21
22 "github.com/golang/geo/r1"
23 "github.com/golang/geo/r2"
24 "github.com/golang/geo/r3"
25 "github.com/golang/geo/s1"
26 )
27
28 func TestEdgeClippingIntersectsFace(t *testing.T) {
29 tests := []struct {
30 a pointUVW
31 want bool
32 }{
33 {pointUVW{r3.Vector{2.05335e-06, 3.91604e-22, 2.90553e-06}}, false},
34 {pointUVW{r3.Vector{-3.91604e-22, -2.05335e-06, -2.90553e-06}}, false},
35 {pointUVW{r3.Vector{0.169258, -0.169258, 0.664013}}, false},
36 {pointUVW{r3.Vector{0.169258, -0.169258, -0.664013}}, false},
37 {pointUVW{r3.Vector{math.Sqrt(2.0 / 3.0), -math.Sqrt(2.0 / 3.0), 3.88578e-16}}, true},
38 {pointUVW{r3.Vector{-3.88578e-16, -math.Sqrt(2.0 / 3.0), math.Sqrt(2.0 / 3.0)}}, true},
39 }
40
41 for _, test := range tests {
42 if got := test.a.intersectsFace(); got != test.want {
43 t.Errorf("%v.intersectsFace() = %v, want %v", test.a, got, test.want)
44 }
45 }
46 }
47
48 func TestEdgeClippingIntersectsOppositeEdges(t *testing.T) {
49 tests := []struct {
50 a pointUVW
51 want bool
52 }{
53 {pointUVW{r3.Vector{0.169258, -0.169258, 0.664013}}, false},
54 {pointUVW{r3.Vector{0.169258, -0.169258, -0.664013}}, false},
55
56 {pointUVW{r3.Vector{-math.Sqrt(4.0 / 3.0), 0, -math.Sqrt(4.0 / 3.0)}}, true},
57 {pointUVW{r3.Vector{math.Sqrt(4.0 / 3.0), 0, math.Sqrt(4.0 / 3.0)}}, true},
58
59 {pointUVW{r3.Vector{-math.Sqrt(2.0 / 3.0), -math.Sqrt(2.0 / 3.0), 1.66533453694e-16}}, false},
60 {pointUVW{r3.Vector{math.Sqrt(2.0 / 3.0), math.Sqrt(2.0 / 3.0), -1.66533453694e-16}}, false},
61 }
62 for _, test := range tests {
63 if got := test.a.intersectsOppositeEdges(); got != test.want {
64 t.Errorf("%v.intersectsOppositeEdges() = %v, want %v", test.a, got, test.want)
65 }
66 }
67 }
68
69 func TestEdgeClippingExitAxis(t *testing.T) {
70 tests := []struct {
71 a pointUVW
72 want axis
73 }{
74 {pointUVW{r3.Vector{0, -math.Sqrt(2.0 / 3.0), math.Sqrt(2.0 / 3.0)}}, axisU},
75 {pointUVW{r3.Vector{0, math.Sqrt(4.0 / 3.0), -math.Sqrt(4.0 / 3.0)}}, axisU},
76 {pointUVW{r3.Vector{-math.Sqrt(4.0 / 3.0), -math.Sqrt(4.0 / 3.0), 0}}, axisV},
77 {pointUVW{r3.Vector{math.Sqrt(4.0 / 3.0), math.Sqrt(4.0 / 3.0), 0}}, axisV},
78 {pointUVW{r3.Vector{math.Sqrt(2.0 / 3.0), -math.Sqrt(2.0 / 3.0), 0}}, axisV},
79 {pointUVW{r3.Vector{1.67968702783622, 0, 0.870988820096491}}, axisV},
80 {pointUVW{r3.Vector{0, math.Sqrt2, math.Sqrt2}}, axisU},
81 }
82
83 for _, test := range tests {
84 if got := test.a.exitAxis(); got != test.want {
85 t.Errorf("%v.exitAxis() = %v, want %v", test.a, got, test.want)
86 }
87 }
88 }
89
90 func TestEdgeClippingExitPoint(t *testing.T) {
91 tests := []struct {
92 a pointUVW
93 exitAxis axis
94 want r2.Point
95 }{
96 {pointUVW{r3.Vector{-3.88578058618805e-16, -math.Sqrt(2.0 / 3.0), math.Sqrt(2.0 / 3.0)}}, axisU, r2.Point{-1, 1}},
97 {pointUVW{r3.Vector{math.Sqrt(4.0 / 3.0), -math.Sqrt(4.0 / 3.0), 0}}, axisV, r2.Point{-1, -1}},
98 {pointUVW{r3.Vector{-math.Sqrt(4.0 / 3.0), -math.Sqrt(4.0 / 3.0), 0}}, axisV, r2.Point{-1, 1}},
99 {pointUVW{r3.Vector{-6.66134e-16, math.Sqrt(4.0 / 3.0), -math.Sqrt(4.0 / 3.0)}}, axisU, r2.Point{1, 1}},
100 }
101
102 for _, test := range tests {
103 if got := test.a.exitPoint(test.exitAxis); !r2PointsApproxEqual(got, test.want, epsilon) {
104 t.Errorf("%v.exitPoint() = %v, want %v", test.a, got, test.want)
105 }
106 }
107 }
108
109
110
111
112
113
114
115
116
117 func testClipToPaddedFace(t *testing.T, a, b Point) {
118 a = Point{a.Normalize()}
119 b = Point{b.Normalize()}
120 if a.Vector == b.Mul(-1) {
121 return
122 }
123
124
125 segments := FaceSegments(a, b)
126 n := len(segments)
127 if n == 0 {
128 t.Errorf("FaceSegments(%v, %v) should have generated at least one entry", a, b)
129 }
130
131 biunit := r2.Rect{r1.Interval{-1, 1}, r1.Interval{-1, 1}}
132 const errorRadians = faceClipErrorRadians
133
134
135 if aPrime := faceUVToXYZ(segments[0].face, segments[0].a.X, segments[0].a.Y); a.Angle(aPrime) > errorRadians {
136 t.Errorf("%v.Angle(%v) = %v, want < %v", a, aPrime, a.Angle(aPrime), errorRadians)
137 }
138 if bPrime := faceUVToXYZ(segments[n-1].face, segments[n-1].b.X, segments[n-1].b.Y); b.Angle(bPrime) > errorRadians {
139 t.Errorf("%v.Angle(%v) = %v, want < %v", b, bPrime, b.Angle(bPrime), errorRadians)
140 }
141
142 norm := Point{a.PointCross(b).Normalize()}
143 aTan := Point{norm.Cross(a.Vector)}
144 bTan := Point{b.Cross(norm.Vector)}
145
146 for i := 0; i < n; i++ {
147
148 if !biunit.ContainsPoint(segments[i].a) {
149 t.Errorf("biunit.ContainsPoint(%v) = false, want true", segments[i].a)
150 }
151 if !biunit.ContainsPoint(segments[i].b) {
152 t.Errorf("biunit.ContainsPoint(%v) = false, want true", segments[i].b)
153 }
154 if i == 0 {
155 continue
156 }
157
158
159
160 if segments[i-1].face == segments[i].face {
161 t.Errorf("%v.face != %v.face", segments[i-1], segments[i])
162 }
163 if got, want := faceUVToXYZ(segments[i-1].face, segments[i-1].b.X, segments[i-1].b.Y),
164 faceUVToXYZ(segments[i].face, segments[i].a.X, segments[i].a.Y); !got.ApproxEqual(want) {
165 t.Errorf("interior vertices on adjacent faces should be the same point. got %v != %v", got, want)
166 }
167
168
169
170
171 p := faceUVToXYZ(segments[i].face, segments[i].a.X, segments[i].a.Y).Normalize()
172 if got := math.Abs(p.Dot(norm.Vector)); got > errorRadians {
173 t.Errorf("%v.Dot(%v) = %v, want <= %v", p, norm, got, errorRadians)
174 }
175 if got := p.Dot(aTan.Vector); got < -errorRadians {
176 t.Errorf("%v.Dot(%v) = %v, want >= %v", p, aTan, got, -errorRadians)
177 }
178 if got := p.Dot(bTan.Vector); got < -errorRadians {
179 t.Errorf("%v.Dot(%v) = %v, want >= %v", p, bTan, got, -errorRadians)
180 }
181 }
182
183 padding := 0.0
184 if !oneIn(10) {
185 padding = 1e-10 * math.Pow(1e-5, randomFloat64())
186 }
187
188 xAxis := a
189 yAxis := aTan
190
191
192
193 expectedAngles := s1.Interval{0, float64(a.Angle(b.Vector))}
194 if expectedAngles.IsInverted() {
195 expectedAngles = s1.Interval{expectedAngles.Hi, expectedAngles.Lo}
196 }
197 maxAngles := expectedAngles.Expanded(faceClipErrorRadians)
198 var actualAngles s1.Interval
199
200 for face := 0; face < 6; face++ {
201 aUV, bUV, intersects := ClipToPaddedFace(a, b, face, padding)
202 if !intersects {
203 continue
204 }
205
206 aClip := Point{faceUVToXYZ(face, aUV.X, aUV.Y).Normalize()}
207 bClip := Point{faceUVToXYZ(face, bUV.X, bUV.Y).Normalize()}
208
209 desc := fmt.Sprintf("on face %d, a=%v, b=%v, aClip=%v, bClip=%v,", face, a, b, aClip, bClip)
210
211 if got := math.Abs(aClip.Dot(norm.Vector)); got > faceClipErrorRadians {
212 t.Errorf("%s abs(%v.Dot(%v)) = %v, want <= %v", desc, aClip, norm, got, faceClipErrorRadians)
213 }
214 if got := math.Abs(bClip.Dot(norm.Vector)); got > faceClipErrorRadians {
215 t.Errorf("%s abs(%v.Dot(%v)) = %v, want <= %v", desc, bClip, norm, got, faceClipErrorRadians)
216 }
217
218 if float64(aClip.Angle(a.Vector)) > faceClipErrorRadians {
219 if got := math.Max(math.Abs(aUV.X), math.Abs(aUV.Y)); !float64Eq(got, 1+padding) {
220 t.Errorf("%s the largest component of %v = %v, want %v", desc, aUV, got, 1+padding)
221 }
222 }
223 if float64(bClip.Angle(b.Vector)) > faceClipErrorRadians {
224 if got := math.Max(math.Abs(bUV.X), math.Abs(bUV.Y)); !float64Eq(got, 1+padding) {
225 t.Errorf("%s the largest component of %v = %v, want %v", desc, bUV, got, 1+padding)
226 }
227 }
228
229 aAngle := math.Atan2(aClip.Dot(yAxis.Vector), aClip.Dot(xAxis.Vector))
230 bAngle := math.Atan2(bClip.Dot(yAxis.Vector), bClip.Dot(xAxis.Vector))
231
232
233
234
235 faceAngles := s1.IntervalFromEndpoints(aAngle, bAngle)
236 if faceAngles.IsInverted() {
237 faceAngles = s1.Interval{faceAngles.Hi, faceAngles.Lo}
238 }
239 if !maxAngles.ContainsInterval(faceAngles) {
240 t.Errorf("%s %v.ContainsInterval(%v) = false, but should have contained this interval", desc, maxAngles, faceAngles)
241 }
242 actualAngles = actualAngles.Union(faceAngles)
243 }
244 if !actualAngles.Expanded(faceClipErrorRadians).ContainsInterval(expectedAngles) {
245 t.Errorf("the union of all angle segments should be larger than the expected angle")
246 }
247 }
248
249 func TestEdgeClippingClipToPaddedFace(t *testing.T) {
250
251
252 testClipToPaddedFace(t, Point{r3.Vector{1, -0.5, -0.5}}, Point{r3.Vector{1, 0.5, 0.5}})
253 testClipToPaddedFace(t, Point{r3.Vector{1, 0.5, 0.5}}, Point{r3.Vector{1, -0.5, -0.5}})
254
255 testClipToPaddedFace(t, Point{r3.Vector{1, 0, 0}}, Point{r3.Vector{0, 1, 0}})
256 testClipToPaddedFace(t, Point{r3.Vector{0, 1, 0}}, Point{r3.Vector{1, 0, 0}})
257
258 testClipToPaddedFace(t, Point{r3.Vector{0.75, 0, -1}}, Point{r3.Vector{0.75, 0, 1}})
259 testClipToPaddedFace(t, Point{r3.Vector{0.75, 0, 1}}, Point{r3.Vector{0.75, 0, -1}})
260
261 testClipToPaddedFace(t, Point{r3.Vector{1, 0, 0.75}}, Point{r3.Vector{0, 1, 0.75}})
262 testClipToPaddedFace(t, Point{r3.Vector{0, 1, 0.75}}, Point{r3.Vector{1, 0, 0.75}})
263
264 testClipToPaddedFace(t, Point{r3.Vector{1, 0.9, 0.95}}, Point{r3.Vector{-1, 0.95, 0.9}})
265 testClipToPaddedFace(t, Point{r3.Vector{-1, 0.95, 0.9}}, Point{r3.Vector{1, 0.9, 0.95}})
266
267
268
269 biunit := r2.Rect{r1.Interval{-1, 1}, r1.Interval{-1, 1}}
270
271 for i := 0; i < 1000; i++ {
272
273 face := randomUniformInt(6)
274 i := randomUniformInt(4)
275 j := (i + 1) & 3
276 p := Point{faceUVToXYZ(face, biunit.Vertices()[i].X, biunit.Vertices()[i].Y)}
277 q := Point{faceUVToXYZ(face, biunit.Vertices()[j].X, biunit.Vertices()[j].Y)}
278
279
280
281 a := perturbedCornerOrMidpoint(p, q)
282 b := perturbedCornerOrMidpoint(p, q)
283 testClipToPaddedFace(t, a, b)
284 }
285 }
286
287
288
289 func getFraction(t *testing.T, x, a, b r2.Point) float64 {
290
291
292 errorDist := (edgeClipErrorUVDist + intersectsRectErrorUVDist)
293 if a == b {
294 return 0.0
295 }
296 dir := b.Sub(a).Normalize()
297 if got := math.Abs(x.Sub(a).Dot(dir.Ortho())); got > errorDist {
298 t.Errorf("getFraction(%v, %v, %v) = %v, which exceeds errorDist %v", x, a, b, got, errorDist)
299 }
300 return x.Sub(a).Dot(dir)
301 }
302
303
304
305
306
307 func randomPointFromInterval(clip r1.Interval) float64 {
308 if oneIn(5) {
309 if oneIn(2) {
310 return clip.Lo
311 }
312 return clip.Hi
313 }
314
315 switch randomUniformInt(3) {
316 case 0:
317 return clip.Lo - randomFloat64()
318 case 1:
319 return clip.Hi + randomFloat64()
320 default:
321 return clip.Lo + randomFloat64()*clip.Length()
322 }
323 }
324
325
326 func chooseRectEndpoint(clip r2.Rect) r2.Point {
327 if oneIn(10) {
328
329 diag := randomUniformInt(2)
330 t := randomUniformFloat64(-1, 2)
331 return clip.Vertices()[diag].Mul(1 - t).Add(clip.Vertices()[diag+2].Mul(t))
332 }
333 return r2.Point{randomPointFromInterval(clip.X), randomPointFromInterval(clip.Y)}
334 }
335
336
337
338 func choosePointInRect(a, b r2.Point) r2.Point {
339 if oneIn(5) {
340 if oneIn(2) {
341 return a
342 }
343 return b
344 }
345
346 if oneIn(3) {
347 return a.Add(b.Sub(a).Mul(randomFloat64()))
348 }
349 return r2.Point{randomUniformFloat64(a.X, b.X), randomUniformFloat64(a.Y, b.Y)}
350 }
351
352
353
354
355 func checkPointOnBoundary(t *testing.T, p, a r2.Point, clip r2.Rect) {
356 if got := clip.ContainsPoint(p); !got {
357 t.Errorf("%v.ContainsPoint(%v) = %v, want true", clip, p, got)
358 }
359 if p != a {
360 p1 := r2.Point{math.Nextafter(p.X, a.X), math.Nextafter(p.Y, a.Y)}
361 if got := clip.ContainsPoint(p1); got {
362 t.Errorf("%v.ContainsPoint(%v) = %v, want false", clip, p1, got)
363 }
364 }
365 }
366
367 func TestEdgeClippingClipEdge(t *testing.T) {
368
369
370 errorDist := (edgeClipErrorUVDist + intersectsRectErrorUVDist)
371 testRects := []r2.Rect{
372
373 r2.RectFromPoints(
374 r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)},
375 r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)}),
376 r2.RectFromPoints(
377 r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)},
378 r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)}),
379 r2.RectFromPoints(
380 r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)},
381 r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)}),
382 r2.RectFromPoints(
383 r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)},
384 r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)}),
385 r2.RectFromPoints(
386 r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)},
387 r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)}),
388
389
390 {r1.Interval{-0.7, -0.7}, r1.Interval{0.3, 0.35}},
391 {r1.Interval{0.2, 0.5}, r1.Interval{0.3, 0.3}},
392 {r1.Interval{-0.7, 0.3}, r1.Interval{0, 0}},
393 r2.RectFromPoints(r2.Point{0.3, 0.8}),
394 r2.EmptyRect(),
395 }
396
397 for _, r := range testRects {
398 for i := 0; i < 1000; i++ {
399 a := chooseRectEndpoint(r)
400 b := chooseRectEndpoint(r)
401
402 aClip, bClip, intersects := ClipEdge(a, b, r)
403 if !intersects {
404 if edgeIntersectsRect(a, b, r.ExpandedByMargin(-errorDist)) {
405 t.Errorf("edgeIntersectsRect(%v, %v, %v.ExpandedByMargin(%v) = true, want false", a, b, r, -errorDist)
406 }
407 } else {
408 if !edgeIntersectsRect(a, b, r.ExpandedByMargin(errorDist)) {
409 t.Errorf("edgeIntersectsRect(%v, %v, %v.ExpandedByMargin(%v) = false, want true", a, b, r, errorDist)
410 }
411
412
413
414 if gotA, gotB := getFraction(t, aClip, a, b), getFraction(t, bClip, a, b); gotA > gotB {
415 t.Errorf("getFraction(%v,%v,%v) = %v, getFraction(%v, %v, %v) = %v; %v < %v = false, want true", aClip, a, b, gotA, bClip, a, b, gotB, gotA, gotB)
416 }
417
418
419 checkPointOnBoundary(t, aClip, a, r)
420 checkPointOnBoundary(t, bClip, b, r)
421 }
422
423
424 initialClip := r2.RectFromPoints(choosePointInRect(a, b), choosePointInRect(a, b))
425 bound := clippedEdgeBound(a, b, initialClip)
426 if bound.IsEmpty() {
427
428 continue
429 }
430 maxBound := bound.Intersection(r)
431 if bound, intersects := clipEdgeBound(a, b, r, bound); !intersects {
432 if edgeIntersectsRect(a, b, maxBound.ExpandedByMargin(-errorDist)) {
433 t.Errorf("edgeIntersectsRect(%v, %v, %v.ExpandedByMargin(%v) = true, want false", a, b, maxBound.ExpandedByMargin(-errorDist), -errorDist)
434 }
435 } else {
436 if !edgeIntersectsRect(a, b, maxBound.ExpandedByMargin(errorDist)) {
437 t.Errorf("edgeIntersectsRect(%v, %v, %v.ExpandedByMargin(%v) = false, want true", a, b, maxBound.ExpandedByMargin(errorDist), errorDist)
438 }
439
440 ai := 0
441 if a.X > b.X {
442 ai = 1
443 }
444 aj := 0
445 if a.Y > b.Y {
446 aj = 1
447 }
448 checkPointOnBoundary(t, bound.VertexIJ(ai, aj), a, maxBound)
449 checkPointOnBoundary(t, bound.VertexIJ(1-ai, 1-aj), b, maxBound)
450 }
451 }
452 }
453 }
454
View as plain text