gonum.org/v1/gonum@v0.14.0/spatial/r2/triangle_test.go (about) 1 // Copyright ©2022 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package r2 6 7 import ( 8 "math" 9 "testing" 10 11 "golang.org/x/exp/rand" 12 ) 13 14 func TestTriangleDegenerate(t *testing.T) { 15 const ( 16 // tol is how much closer the problematic 17 // vertex is placed to avoid floating point error 18 // for degeneracy calculation. 19 tol = 1e-12 20 // This is the argument to Degenerate and represents 21 // the minimum permissible distance between the triangle 22 // longest edge and the opposite vertex. 23 spatialTol = 1e-2 24 ) 25 rnd := rand.New(rand.NewSource(1)) 26 randVec := func() Vec { 27 return Vec{X: 20 * (rnd.Float64() - 0.5), Y: 20 * (rnd.Float64() - 0.5)} 28 } 29 for i := 0; i < 200; i++ { 30 // Generate a random line for the longest triangle side. 31 ln := line{randVec(), randVec()} 32 lineDir := Sub(ln[1], ln[0]) 33 34 perpendicular := Unit(Vec{X: lineDir.X, Y: -lineDir.Y}) 35 // generate 3 permutations of needle triangles for 36 // each vertex. A needle triangle has two vertices 37 // very close to eachother an its third vertex far away. 38 var needle Triangle 39 for j := 0; j < 3; j++ { 40 needle[j] = ln[0] 41 needle[(j+1)%3] = ln[1] 42 needle[(j+2)%3] = Add(ln[1], Scale((1-tol)*spatialTol, perpendicular)) 43 if !needle.IsDegenerate(spatialTol) { 44 t.Error("needle triangle not degenerate") 45 } 46 } 47 48 midpoint := ln.vecOnLine(0.5) 49 // cap triangles are characterized by having two sides 50 // of similar lengths and whose sum is approximately equal 51 // to the remaining longest side. 52 var cap Triangle 53 for j := 0; j < 3; j++ { 54 cap[j] = ln[0] 55 cap[(j+1)%3] = ln[1] 56 cap[(j+2)%3] = Add(midpoint, Scale((1-tol)*spatialTol, perpendicular)) 57 if !cap.IsDegenerate(spatialTol) { 58 t.Error("cap triangle not degenerate") 59 } 60 } 61 62 var degenerate Triangle 63 for j := 0; j < 3; j++ { 64 degenerate[j] = ln[0] 65 degenerate[(j+1)%3] = ln[1] 66 // vertex perpendicular to some random point on longest side. 67 degenerate[(j+2)%3] = Add(ln.vecOnLine(rnd.Float64()), Scale((1-tol)*spatialTol, perpendicular)) 68 if !degenerate.IsDegenerate(spatialTol) { 69 t.Error("random degenerate triangle not degenerate") 70 } 71 // vertex about longest side 0 vertex 72 degenerate[(j+2)%3] = Add(ln[0], Scale((1-tol)*spatialTol, Unit(randVec()))) 73 if !degenerate.IsDegenerate(spatialTol) { 74 t.Error("needle-like degenerate triangle not degenerate") 75 } 76 // vertex about longest side 1 vertex 77 degenerate[(j+2)%3] = Add(ln[1], Scale((1-tol)*spatialTol, Unit(randVec()))) 78 if !degenerate.IsDegenerate(spatialTol) { 79 t.Error("needle-like degenerate triangle not degenerate") 80 } 81 } 82 } 83 } 84 85 func TestTriangleArea(t *testing.T) { 86 const tol = 1e-16 87 for _, test := range []struct { 88 T Triangle 89 Expect float64 90 }{ 91 { 92 T: Triangle{ 93 {0, 0}, 94 {1, 0}, 95 {0, 1}, 96 }, 97 Expect: 0.5, 98 }, 99 { 100 T: Triangle{ 101 {1, 0}, 102 {0, 1}, 103 {0, 0}, 104 }, 105 Expect: 0.5, 106 }, 107 { 108 T: Triangle{ 109 {0, 0}, 110 {0, 20}, 111 {20, 0}, 112 }, 113 Expect: 20 * 20 / 2, 114 }, 115 } { 116 got := test.T.Area() 117 if math.Abs(got-test.Expect) > tol { 118 t.Errorf("got area %g, expected %g", got, test.Expect) 119 } 120 const tol2 = 1e-11 121 rnd := rand.New(rand.NewSource(1)) 122 for i := 0; i < 100; i++ { 123 tri := Triangle{ 124 {rnd.Float64() * 20, rnd.Float64() * 20}, 125 {rand.Float64() * 20, rnd.Float64() * 20}, 126 {rnd.Float64() * 20, rnd.Float64() * 20}, 127 } 128 129 got := tri.Area() 130 want := math.Abs(Cross(Sub(tri[1], tri[0]), Sub(tri[1], tri[2]))) / 2 131 if math.Abs(got-want) > tol2 { 132 t.Errorf("got area %g not match half norm of cross product %g", got, want) 133 } 134 } 135 } 136 } 137 138 func TestTriangleCentroid(t *testing.T) { 139 const tol = 1e-12 140 rnd := rand.New(rand.NewSource(1)) 141 for i := 0; i < 100; i++ { 142 tri := Triangle{ 143 {rnd.Float64() * 20, rnd.Float64() * 20}, 144 {rand.Float64() * 20, rnd.Float64() * 20}, 145 {rnd.Float64() * 20, rnd.Float64() * 20}, 146 } 147 got := tri.Centroid() 148 want := Vec{ 149 X: (tri[0].X + tri[1].X + tri[2].X) / 3, 150 Y: (tri[0].Y + tri[1].Y + tri[2].Y) / 3, 151 } 152 if math.Abs(got.X-want.X) > tol || math.Abs(got.Y-want.Y) > tol { 153 t.Fatalf("got %.6g, want %.6g", got, want) 154 } 155 } 156 }