mirror of
https://github.com/gonum/gonum.git
synced 2025-10-05 15:16:59 +08:00
spatial/{r2,r3}: add Cos, Unit
This commit is contained in:
@@ -54,6 +54,20 @@ func Norm2(p Vec) float64 {
|
|||||||
return p.X*p.X + p.Y*p.Y
|
return p.X*p.X + p.Y*p.Y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unit returns the unit vector colinear to p.
|
||||||
|
// Unit returns {NaN,NaN} for the zero vector.
|
||||||
|
func Unit(p Vec) Vec {
|
||||||
|
if p.X == 0 && p.Y == 0 {
|
||||||
|
return Vec{X: math.NaN(), Y: math.NaN()}
|
||||||
|
}
|
||||||
|
return p.Scale(1 / Norm(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cos returns the cosine of the opening angle between p and q.
|
||||||
|
func Cos(p, q Vec) float64 {
|
||||||
|
return p.Dot(q) / (Norm(p) * Norm(q))
|
||||||
|
}
|
||||||
|
|
||||||
// Box is a 2D bounding box.
|
// Box is a 2D bounding box.
|
||||||
type Box struct {
|
type Box struct {
|
||||||
Min, Max Vec
|
Min, Max Vec
|
||||||
|
@@ -7,6 +7,8 @@ package r2
|
|||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gonum.org/v1/gonum/floats"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAdd(t *testing.T) {
|
func TestAdd(t *testing.T) {
|
||||||
@@ -181,3 +183,77 @@ func TestNorm2(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnit(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
v, want Vec
|
||||||
|
}{
|
||||||
|
{Vec{}, Vec{math.NaN(), math.NaN()}},
|
||||||
|
{Vec{1, 0}, Vec{1, 0}},
|
||||||
|
{Vec{0, 1}, Vec{0, 1}},
|
||||||
|
{Vec{-1, 0}, Vec{-1, 0}},
|
||||||
|
{Vec{3, 4}, Vec{0.6, 0.8}},
|
||||||
|
{Vec{3, -4}, Vec{0.6, -0.8}},
|
||||||
|
{Vec{1, 1}, Vec{1. / math.Sqrt(2), 1. / math.Sqrt(2)}},
|
||||||
|
{Vec{1, 1e-16}, Vec{1, 1e-16}},
|
||||||
|
{Vec{1, 1e16}, Vec{1e-16, 1}},
|
||||||
|
{Vec{1e4, math.MaxFloat32 - 1}, Vec{0, 1}},
|
||||||
|
} {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
got := Unit(test.v)
|
||||||
|
if !vecApproxEqual(got, test.want) {
|
||||||
|
t.Fatalf(
|
||||||
|
"Unit(%v) = %v, want %v",
|
||||||
|
test.v, got, test.want,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if vecIsNaN(got) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n, want := Norm(got), 1.0; n != want {
|
||||||
|
t.Fatalf("|%v| = %v, want 1", got, n)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCos(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
v1, v2 Vec
|
||||||
|
want float64
|
||||||
|
}{
|
||||||
|
{Vec{1, 1}, Vec{1, 1}, 1},
|
||||||
|
{Vec{1, 1}, Vec{-1, -1}, -1},
|
||||||
|
{Vec{1, 0}, Vec{1, 0}, 1},
|
||||||
|
{Vec{1, 0}, Vec{0, 1}, 0},
|
||||||
|
{Vec{1, 0}, Vec{-1, 0}, -1},
|
||||||
|
} {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
tol := 1e-14
|
||||||
|
got := Cos(test.v1, test.v2)
|
||||||
|
if !floats.EqualWithinAbs(got, test.want, tol) {
|
||||||
|
t.Fatalf("cos(%v, %v)= %v, want %v",
|
||||||
|
test.v1, test.v2, got, test.want,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func vecIsNaN(v Vec) bool {
|
||||||
|
return math.IsNaN(v.X) && math.IsNaN(v.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func vecIsNaNAny(v Vec) bool {
|
||||||
|
return math.IsNaN(v.X) || math.IsNaN(v.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func vecApproxEqual(a, b Vec) bool {
|
||||||
|
const tol = 1e-14
|
||||||
|
if vecIsNaNAny(a) || vecIsNaNAny(b) {
|
||||||
|
return vecIsNaN(a) && vecIsNaN(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return floats.EqualWithinAbs(a.X, b.X, tol) &&
|
||||||
|
floats.EqualWithinAbs(a.Y, b.Y, tol)
|
||||||
|
}
|
||||||
|
@@ -61,6 +61,20 @@ func Norm2(p Vec) float64 {
|
|||||||
return p.X*p.X + p.Y*p.Y + p.Z*p.Z
|
return p.X*p.X + p.Y*p.Y + p.Z*p.Z
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unit returns the unit vector colinear to p.
|
||||||
|
// Unit returns {NaN,NaN,NaN} for the zero vector.
|
||||||
|
func Unit(p Vec) Vec {
|
||||||
|
if p.X == 0 && p.Y == 0 && p.Z == 0 {
|
||||||
|
return Vec{X: math.NaN(), Y: math.NaN(), Z: math.NaN()}
|
||||||
|
}
|
||||||
|
return p.Scale(1 / Norm(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cos returns the cosine of the opening angle between p and q.
|
||||||
|
func Cos(p, q Vec) float64 {
|
||||||
|
return p.Dot(q) / (Norm(p) * Norm(q))
|
||||||
|
}
|
||||||
|
|
||||||
// Box is a 3D bounding box.
|
// Box is a 3D bounding box.
|
||||||
type Box struct {
|
type Box struct {
|
||||||
Min, Max Vec
|
Min, Max Vec
|
||||||
|
@@ -4,7 +4,12 @@
|
|||||||
|
|
||||||
package r3
|
package r3
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gonum.org/v1/gonum/floats"
|
||||||
|
)
|
||||||
|
|
||||||
func TestAdd(t *testing.T) {
|
func TestAdd(t *testing.T) {
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
@@ -176,3 +181,72 @@ func TestNorm2(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnit(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
v, want Vec
|
||||||
|
}{
|
||||||
|
{Vec{}, Vec{math.NaN(), math.NaN(), math.NaN()}},
|
||||||
|
{Vec{1, 0, 0}, Vec{1, 0, 0}},
|
||||||
|
{Vec{0, 1, 0}, Vec{0, 1, 0}},
|
||||||
|
{Vec{0, 0, 1}, Vec{0, 0, 1}},
|
||||||
|
{Vec{1, 1, 1}, Vec{1. / math.Sqrt(3), 1. / math.Sqrt(3), 1. / math.Sqrt(3)}},
|
||||||
|
{Vec{1, 1e-16, 1e-32}, Vec{1, 1e-16, 1e-32}},
|
||||||
|
} {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
got := Unit(test.v)
|
||||||
|
if !vecEqual(got, test.want) {
|
||||||
|
t.Fatalf(
|
||||||
|
"Normalize(%v) = %v, want %v",
|
||||||
|
test.v, got, test.want,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if test.v == (Vec{}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n, want := Norm(got), 1.0; n != want {
|
||||||
|
t.Fatalf("|%v| = %v, want 1", got, n)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCos(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
v1, v2 Vec
|
||||||
|
want float64
|
||||||
|
}{
|
||||||
|
{Vec{1, 1, 1}, Vec{1, 1, 1}, 1},
|
||||||
|
{Vec{1, 1, 1}, Vec{-1, -1, -1}, -1},
|
||||||
|
{Vec{1, 1, 1}, Vec{1, -1, 1}, 1.0 / 3},
|
||||||
|
{Vec{1, 0, 0}, Vec{1, 0, 0}, 1},
|
||||||
|
{Vec{1, 0, 0}, Vec{0, 1, 0}, 0},
|
||||||
|
{Vec{1, 0, 0}, Vec{0, 1, 1}, 0},
|
||||||
|
{Vec{1, 0, 0}, Vec{-1, 0, 0}, -1},
|
||||||
|
} {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
tol := 1e-14
|
||||||
|
got := Cos(test.v1, test.v2)
|
||||||
|
if !floats.EqualWithinAbs(got, test.want, tol) {
|
||||||
|
t.Fatalf("cos(%v, %v)= %v, want %v",
|
||||||
|
test.v1, test.v2, got, test.want,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func vecIsNaN(v Vec) bool {
|
||||||
|
return math.IsNaN(v.X) && math.IsNaN(v.Y) && math.IsNaN(v.Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
func vecIsNaNAny(v Vec) bool {
|
||||||
|
return math.IsNaN(v.X) || math.IsNaN(v.Y) || math.IsNaN(v.Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
func vecEqual(a, b Vec) bool {
|
||||||
|
if vecIsNaNAny(a) || vecIsNaNAny(b) {
|
||||||
|
return vecIsNaN(a) && vecIsNaN(b)
|
||||||
|
}
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user