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  }