File: triangle_test.go

package info (click to toggle)
golang-gonum-v1-gonum 0.15.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 18,792 kB
  • sloc: asm: 6,252; fortran: 5,271; sh: 377; ruby: 211; makefile: 98
file content (156 lines) | stat: -rw-r--r-- 4,219 bytes parent folder | download
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
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright ©2022 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package r2

import (
	"math"
	"testing"

	"golang.org/x/exp/rand"
)

func TestTriangleDegenerate(t *testing.T) {
	const (
		// tol is how much closer the problematic
		// vertex is placed to avoid floating point error
		// for degeneracy calculation.
		tol = 1e-12
		// This is the argument to Degenerate and represents
		// the minimum permissible distance between the triangle
		// longest edge and the opposite vertex.
		spatialTol = 1e-2
	)
	rnd := rand.New(rand.NewSource(1))
	randVec := func() Vec {
		return Vec{X: 20 * (rnd.Float64() - 0.5), Y: 20 * (rnd.Float64() - 0.5)}
	}
	for i := 0; i < 200; i++ {
		// Generate a random line for the longest triangle side.
		ln := line{randVec(), randVec()}
		lineDir := Sub(ln[1], ln[0])

		perpendicular := Unit(Vec{X: lineDir.X, Y: -lineDir.Y})
		// generate 3 permutations of needle triangles for
		// each vertex. A needle triangle has two vertices
		// very close to eachother an its third vertex far away.
		var needle Triangle
		for j := 0; j < 3; j++ {
			needle[j] = ln[0]
			needle[(j+1)%3] = ln[1]
			needle[(j+2)%3] = Add(ln[1], Scale((1-tol)*spatialTol, perpendicular))
			if !needle.IsDegenerate(spatialTol) {
				t.Error("needle triangle not degenerate")
			}
		}

		midpoint := ln.vecOnLine(0.5)
		// cap triangles are characterized by having two sides
		// of similar lengths and whose sum is approximately equal
		// to the remaining longest side.
		var cap Triangle
		for j := 0; j < 3; j++ {
			cap[j] = ln[0]
			cap[(j+1)%3] = ln[1]
			cap[(j+2)%3] = Add(midpoint, Scale((1-tol)*spatialTol, perpendicular))
			if !cap.IsDegenerate(spatialTol) {
				t.Error("cap triangle not degenerate")
			}
		}

		var degenerate Triangle
		for j := 0; j < 3; j++ {
			degenerate[j] = ln[0]
			degenerate[(j+1)%3] = ln[1]
			// vertex perpendicular to some random point on longest side.
			degenerate[(j+2)%3] = Add(ln.vecOnLine(rnd.Float64()), Scale((1-tol)*spatialTol, perpendicular))
			if !degenerate.IsDegenerate(spatialTol) {
				t.Error("random degenerate triangle not degenerate")
			}
			// vertex about longest side 0 vertex
			degenerate[(j+2)%3] = Add(ln[0], Scale((1-tol)*spatialTol, Unit(randVec())))
			if !degenerate.IsDegenerate(spatialTol) {
				t.Error("needle-like degenerate triangle not degenerate")
			}
			// vertex about longest side 1 vertex
			degenerate[(j+2)%3] = Add(ln[1], Scale((1-tol)*spatialTol, Unit(randVec())))
			if !degenerate.IsDegenerate(spatialTol) {
				t.Error("needle-like degenerate triangle not degenerate")
			}
		}
	}
}

func TestTriangleArea(t *testing.T) {
	const tol = 1e-16
	for _, test := range []struct {
		T      Triangle
		Expect float64
	}{
		{
			T: Triangle{
				{0, 0},
				{1, 0},
				{0, 1},
			},
			Expect: 0.5,
		},
		{
			T: Triangle{
				{1, 0},
				{0, 1},
				{0, 0},
			},
			Expect: 0.5,
		},
		{
			T: Triangle{
				{0, 0},
				{0, 20},
				{20, 0},
			},
			Expect: 20 * 20 / 2,
		},
	} {
		got := test.T.Area()
		if math.Abs(got-test.Expect) > tol {
			t.Errorf("got area %g, expected %g", got, test.Expect)
		}
		const tol2 = 1e-11
		rnd := rand.New(rand.NewSource(1))
		for i := 0; i < 100; i++ {
			tri := Triangle{
				{rnd.Float64() * 20, rnd.Float64() * 20},
				{rand.Float64() * 20, rnd.Float64() * 20},
				{rnd.Float64() * 20, rnd.Float64() * 20},
			}

			got := tri.Area()
			want := math.Abs(Cross(Sub(tri[1], tri[0]), Sub(tri[1], tri[2]))) / 2
			if math.Abs(got-want) > tol2 {
				t.Errorf("got area %g not match half norm of cross product %g", got, want)
			}
		}
	}
}

func TestTriangleCentroid(t *testing.T) {
	const tol = 1e-12
	rnd := rand.New(rand.NewSource(1))
	for i := 0; i < 100; i++ {
		tri := Triangle{
			{rnd.Float64() * 20, rnd.Float64() * 20},
			{rand.Float64() * 20, rnd.Float64() * 20},
			{rnd.Float64() * 20, rnd.Float64() * 20},
		}
		got := tri.Centroid()
		want := Vec{
			X: (tri[0].X + tri[1].X + tri[2].X) / 3,
			Y: (tri[0].Y + tri[1].Y + tri[2].Y) / 3,
		}
		if math.Abs(got.X-want.X) > tol || math.Abs(got.Y-want.Y) > tol {
			t.Fatalf("got %.6g, want %.6g", got, want)
		}
	}
}