gonum.org/v1/gonum@v0.14.0/spatial/r3/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 r3
     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  	for i := 0; i < 200; i++ {
    27  		// Generate a random line for the longest triangle side.
    28  		ln := line{randomVec(rnd), randomVec(rnd)}
    29  		lineDir := Sub(ln[1], ln[0])
    30  
    31  		perpendicular := Unit(Cross(lineDir, randomVec(rnd)))
    32  		// generate 3 permutations of needle triangles for
    33  		// each vertex. A needle triangle has two vertices
    34  		// very close to eachother an its third vertex far away.
    35  		var needle Triangle
    36  		for j := 0; j < 3; j++ {
    37  			needle[j] = ln[0]
    38  			needle[(j+1)%3] = ln[1]
    39  			needle[(j+2)%3] = Add(ln[1], Scale((1-tol)*spatialTol, perpendicular))
    40  			if !needle.IsDegenerate(spatialTol) {
    41  				t.Error("needle triangle not degenerate")
    42  			}
    43  		}
    44  
    45  		midpoint := ln.vecOnLine(0.5)
    46  		// cap triangles are characterized by having two sides
    47  		// of similar lengths and whose sum is approximately equal
    48  		// to the remaining longest side.
    49  		var cap Triangle
    50  		for j := 0; j < 3; j++ {
    51  			cap[j] = ln[0]
    52  			cap[(j+1)%3] = ln[1]
    53  			cap[(j+2)%3] = Add(midpoint, Scale((1-tol)*spatialTol, perpendicular))
    54  			if !cap.IsDegenerate(spatialTol) {
    55  				t.Error("cap triangle not degenerate")
    56  			}
    57  		}
    58  
    59  		var degenerate Triangle
    60  		for j := 0; j < 3; j++ {
    61  			degenerate[j] = ln[0]
    62  			degenerate[(j+1)%3] = ln[1]
    63  			// vertex perpendicular to some random point on longest side.
    64  			degenerate[(j+2)%3] = Add(ln.vecOnLine(rnd.Float64()), Scale((1-tol)*spatialTol, perpendicular))
    65  			if !degenerate.IsDegenerate(spatialTol) {
    66  				t.Error("random degenerate triangle not degenerate")
    67  			}
    68  			// vertex about longest side 0 vertex
    69  			degenerate[(j+2)%3] = Add(ln[0], Scale((1-tol)*spatialTol, Unit(randomVec(rnd))))
    70  			if !degenerate.IsDegenerate(spatialTol) {
    71  				t.Error("needle-like degenerate triangle not degenerate")
    72  			}
    73  			// vertex about longest side 1 vertex
    74  			degenerate[(j+2)%3] = Add(ln[1], Scale((1-tol)*spatialTol, Unit(randomVec(rnd))))
    75  			if !degenerate.IsDegenerate(spatialTol) {
    76  				t.Error("needle-like degenerate triangle not degenerate")
    77  			}
    78  		}
    79  	}
    80  }
    81  
    82  func TestTriangleCentroid(t *testing.T) {
    83  	const tol = 1e-12
    84  	rnd := rand.New(rand.NewSource(1))
    85  	for i := 0; i < 100; i++ {
    86  		tri := randomTriangle(rnd)
    87  		got := tri.Centroid()
    88  		want := Vec{
    89  			X: (tri[0].X + tri[1].X + tri[2].X) / 3,
    90  			Y: (tri[0].Y + tri[1].Y + tri[2].Y) / 3,
    91  			Z: (tri[0].Z + tri[1].Z + tri[2].Z) / 3,
    92  		}
    93  		if !vecApproxEqual(got, want, tol) {
    94  			t.Fatalf("got %.6g, want %.6g", got, want)
    95  		}
    96  	}
    97  }
    98  
    99  func TestTriangleNormal(t *testing.T) {
   100  	const tol = 1e-12
   101  	rnd := rand.New(rand.NewSource(1))
   102  	for i := 0; i < 100; i++ {
   103  		tri := randomTriangle(rnd)
   104  		got := tri.Normal()
   105  		expect := goldenNormal(tri)
   106  		if !vecApproxEqual(got, expect, tol) {
   107  			t.Fatalf("got %.6g, want %.6g", got, expect)
   108  		}
   109  	}
   110  }
   111  
   112  func TestTriangleArea(t *testing.T) {
   113  	const tol = 1e-16
   114  	for _, test := range []struct {
   115  		T      Triangle
   116  		Expect float64
   117  	}{
   118  		{
   119  			T: Triangle{
   120  				{0, 0, 0},
   121  				{1, 0, 0},
   122  				{0, 1, 0},
   123  			},
   124  			Expect: 0.5,
   125  		},
   126  		{
   127  			T: Triangle{
   128  				{1, 0, 0},
   129  				{0, 1, 0},
   130  				{0, 0, 0},
   131  			},
   132  			Expect: 0.5,
   133  		},
   134  		{
   135  			T: Triangle{
   136  				{20, 0, 0},
   137  				{0, 0, 20},
   138  				{0, 0, 0},
   139  			},
   140  			Expect: 20 * 20 / 2,
   141  		},
   142  	} {
   143  		got := test.T.Area()
   144  		if math.Abs(got-test.Expect) > tol {
   145  			t.Errorf("got area %g, expected %g", got, test.Expect)
   146  		}
   147  		if test.T.IsDegenerate(tol) {
   148  			t.Error("well-formed triangle is degenerate")
   149  		}
   150  	}
   151  	const tol2 = 1e-12
   152  	rnd := rand.New(rand.NewSource(1))
   153  	for i := 0; i < 100; i++ {
   154  		tri := randomTriangle(rnd)
   155  		got := tri.Area()
   156  		want := Norm(Cross(Sub(tri[1], tri[0]), Sub(tri[2], tri[0]))) / 2
   157  		if math.Abs(got-want) > tol2 {
   158  			t.Errorf("got area %g not match half norm of cross product %g", got, want)
   159  		}
   160  	}
   161  }
   162  
   163  func TestTriangleOrderedLengths(t *testing.T) {
   164  	rnd := rand.New(rand.NewSource(1))
   165  	for i := 0; i < 200; i++ {
   166  		tri := randomTriangle(rnd)
   167  		s1, s2, s3 := tri.sides()
   168  		l1 := Norm(s1)
   169  		l2 := Norm(s2)
   170  		l3 := Norm(s3)
   171  		a, b, c := tri.orderedLengths()
   172  		if a != l1 && a != l2 && a != l3 {
   173  			t.Error("shortest ordered length not a side of the triangle")
   174  		}
   175  		if b != l1 && b != l2 && b != l3 {
   176  			t.Error("middle ordered length not a side of the triangle")
   177  		}
   178  		if c != l1 && c != l2 && c != l3 {
   179  			t.Error("longest ordered length not a side of the triangle")
   180  		}
   181  		if a > b || a > c {
   182  			t.Error("ordered short side not shortest side")
   183  		}
   184  		if c < b {
   185  			t.Error("ordered long side not longest side")
   186  		}
   187  	}
   188  }
   189  
   190  // taken from soypat/sdf library where it has been thoroughly tested empirically.
   191  func goldenNormal(t Triangle) Vec {
   192  	e1 := Sub(t[1], t[0])
   193  	e2 := Sub(t[2], t[0])
   194  	return Cross(e1, e2)
   195  }
   196  
   197  func randomTriangle(rnd *rand.Rand) Triangle {
   198  	return Triangle{
   199  		randomVec(rnd),
   200  		randomVec(rnd),
   201  		randomVec(rnd),
   202  	}
   203  }