// 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) } } }