2D Vector Library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

852 lines
16 KiB

package vector
import (
"errors"
"math"
"testing"
)
func TestSubMag(t *testing.T) {
p1 := Point2d{10, 10}
p2 := Point2d{7, 6}
v := p1.Sub(p2)
if v != (Vector2d{3, 4}) {
t.Errorf("Sub Error")
}
m := v.Mag()
if m != 5 {
t.Errorf("Mag Error")
}
}
func TestScale(t *testing.T) {
v := Vector2d{3, 4}
m := v.Mag()
if m != 5 {
t.Errorf("Mag Error")
}
m2 := v.Scale(2)
if m2.Mag() != 10 {
t.Errorf("Mag Error")
}
}
func TestAdd(t *testing.T) {
p := Point2d{10, 10}
v := Vector2d{3, 4}
m := p.Add(v)
if m.X != 13 && m.Y != 14 {
t.Errorf("Add Error")
}
}
func TestNormalize(t *testing.T) {
v := Vector2d{3, 4}
vn := v.Normalize()
if vn.Mag() != 1 {
t.Errorf("Normalize Error")
}
v0 := Vector2d{0, 0}
vn0 := v0.Normalize()
if vn0.Mag() != 0 {
t.Errorf("Normalize Error")
}
}
func TestDot(t *testing.T) {
v1 := Vector2d{0, 5}
v2 := Vector2d{5, 0}
d := v1.Dot(v2)
if d != 0 {
t.Errorf("Dot Error")
}
v1 = Vector2d{1, 5}
v2 = Vector2d{5, 0}
d = v1.Dot(v2)
if d < 0 {
t.Errorf("Dot Error")
}
v1 = Vector2d{-1, 5}
v2 = Vector2d{5, 0}
d = v1.Dot(v2)
if d > 0 {
t.Errorf("Dot Error")
}
v1 = Vector2d{5, 5}
v2 = Vector2d{5, 0}
v1 = v1.Normalize()
v2 = v2.Normalize()
d = v1.Dot(v2)
if math.Abs(float64(d)-math.Acos(math.Pi/4)) < 0.0001 {
t.Errorf("Dot Error")
}
}
func TestIntersection(t *testing.T) {
p1 := Point2d{0, 5}
p2 := Point2d{5, 0}
v1 := Vector2d{10, 0}
v2 := Vector2d{0, 10}
i, _ := Intersection(p1, p2, v1, v2)
if !i {
t.Errorf("Intersection Error")
}
p1 = Point2d{20, 5}
p2 = Point2d{5, 0}
v1 = Vector2d{10, 0}
v2 = Vector2d{0, 10}
i, _ = Intersection(p1, p2, v1, v2)
if i {
t.Errorf("Intersection Error")
}
}
func TestAngle(t *testing.T) {
cases := []struct {
v1 Vector2d
v2 Vector2d
expected float64
}{
{
v1: Vector2d{10, 0},
v2: Vector2d{0, 10},
expected: -math.Pi / 2,
},
{
v1: Vector2d{5, 0},
v2: Vector2d{10, 10},
expected: -math.Pi / 4,
},
{
v1: Vector2d{0, 10},
v2: Vector2d{0, -10},
expected: math.Pi,
},
// nan angles:
{
v1: Vector2d{0, 0},
v2: Vector2d{0, 0},
expected: 0,
},
// nan angles
{
v1: Vector2d{0.0001, 0},
v2: Vector2d{0, 0},
expected: math.Pi * Rad2deg,
},
}
var a float64
for _, c := range cases {
a = Angle(c.v2, c.v1)
if math.Abs(float64(a-c.expected)) > Epsilon {
t.Errorf("Angle Error; expected: %f, actual: %f", c.expected, a)
}
}
}
func TestRotate(t *testing.T) {
v1 := Vector2d{10, 0}
v2 := v1.Rotate(math.Pi / 2)
if v2.X > Epsilon || v2.Y-10 > Epsilon {
t.Errorf("Rotate Error")
}
v1 = Vector2d{1, 0}
v2 = v1.Rotate(-30 * Deg2rad)
if v2.X-0.866025403784438 > Epsilon || v2.Y+0.5 > Epsilon {
t.Errorf("Rotate Error")
}
}
func TestPtInRect(t *testing.T) {
p1 := Point2d{10, 0}
p2 := Point2d{10, 10}
p3 := Point2d{11, 10000}
p4 := Point2d{0, 0}
pA := Point2d{5, 5}
pB := Point2d{15, 15}
r := AABB2d{
A: pA,
B: pB,
}
out := PointInRect(p1, r)
if out != false {
t.Errorf("PointInRect Error")
}
out = PointInRect(p2, r)
if out != true {
t.Errorf("PointInRect Error")
}
out = PointInRect(p3, r)
if out != false {
t.Errorf("PointInRect Error")
}
out = PointInRect(p4, r)
if out != false {
t.Errorf("PointInRect Error")
}
}
func TestRectIntersect(t *testing.T) {
type result struct {
collided bool
inside bool
pos *Point2d
}
tests := []struct {
rect AABB2d
p Point2d
v Vector2d
expected result
}{
// inside
{
rect: AABB2d{
A: Point2d{0, 0},
B: Point2d{10, 10},
},
p: Point2d{1, 1},
v: Vector2d{2, 2},
expected: result{
collided: false,
inside: true,
pos: nil,
},
},
// bottom
{
rect: AABB2d{
A: Point2d{5, 5},
B: Point2d{15, 15},
},
p: Point2d{10, 0},
v: Vector2d{0, 10},
expected: result{
collided: true,
inside: false,
pos: &Point2d{10, 5},
},
},
// wall left
{
rect: AABB2d{
A: Point2d{0, 0},
B: Point2d{10, 10},
},
p: Point2d{-1, 5},
v: Vector2d{2, 0},
expected: result{
collided: true,
inside: false,
pos: &Point2d{0, 5},
},
},
// wall right
{
rect: AABB2d{
A: Point2d{0, 0},
B: Point2d{10, 10},
},
p: Point2d{11, 5},
v: Vector2d{-2, 0},
expected: result{
collided: true,
inside: false,
pos: &Point2d{10, 5},
},
},
// wall top
{
rect: AABB2d{
A: Point2d{0, 0},
B: Point2d{10, 10},
},
p: Point2d{5, 11},
v: Vector2d{0, -2},
expected: result{
collided: true,
inside: false,
pos: &Point2d{5, 10},
},
},
// outside
{
rect: AABB2d{
A: Point2d{0, 0},
B: Point2d{10, 10},
},
p: Point2d{5, -1},
v: Vector2d{0, -1},
expected: result{
collided: false,
inside: false,
pos: nil,
},
},
}
for _, test := range tests {
coll, inside, pos := RectIntersection(test.rect, test.p, test.v)
if coll != test.expected.collided {
t.Errorf("unexpected collision: expected: %t, actual: %t", test.expected.collided, coll)
}
if inside != test.expected.inside {
t.Errorf("RectIntersection Error")
t.Errorf("unexpected inside: expected: %t, actual: %t", test.expected.inside, inside)
}
if test.expected.pos == nil && pos != nil {
t.Error("expected nil position, got non-nil")
}
if pos != nil && test.expected.pos != nil {
if *pos != *test.expected.pos {
t.Errorf("unexpected collision point: expected: %+v, actual: %+v", test.expected.pos, pos)
}
}
}
}
func TestRectRectIntersectionTrue(t *testing.T) {
r1 := AABB2d{Point2d{10, 10}, Point2d{20, 20}}
r2 := AABB2d{Point2d{15, 15}, Point2d{25, 25}}
col := RectRectIntersection(r1, r2)
if !col {
t.Errorf("RectRect Error")
}
r1 = AABB2d{Point2d{10, 10}, Point2d{20, 20}}
r2 = AABB2d{Point2d{5, 15}, Point2d{30, 25}}
col = RectRectIntersection(r1, r2)
if !col {
t.Errorf("RectRect Error")
}
}
func TestRectRectIntersectionFalse(t *testing.T) {
r1 := AABB2d{Point2d{10, 10}, Point2d{14, 14}}
r2 := AABB2d{Point2d{15, 15}, Point2d{25, 25}}
col := RectRectIntersection(r1, r2)
if col {
t.Errorf("RectRect Error")
}
}
func TestPolyIntersect(t *testing.T) {
p1 := Polygon2d{}
p1.Points = append(p1.Points, Vector2d{3, 3})
p1.Points = append(p1.Points, Vector2d{5, 5})
p1.Points = append(p1.Points, Vector2d{5, 3})
i, _ := PolygonIntersection(p1, Point2d{0, 0}, Vector2d{10, 10})
if i {
t.Errorf("PolyIntersect Error")
}
}
func TestPolyIntersectFail(t *testing.T) {
p1 := Polygon2d{}
p1.Points = append(p1.Points, Vector2d{3, 3})
p1.Points = append(p1.Points, Vector2d{5, 5})
p1.Points = append(p1.Points, Vector2d{5, 3})
i, _ := PolygonIntersection(p1, Point2d{0, 0}, Vector2d{-10, -10})
if i {
t.Errorf("PolyIntersect Error")
}
}
func TestPolyIntersectTwoHits(t *testing.T) {
p1 := Polygon2d{}
p1.Points = append(p1.Points, Vector2d{0, 0})
p1.Points = append(p1.Points, Vector2d{0, 10})
p1.Points = append(p1.Points, Vector2d{10, 10})
p1.Points = append(p1.Points, Vector2d{10, 0})
i, p := PolygonIntersection(p1, Point2d{-1, 5}, Vector2d{20, 0})
if !i || (p.X != 0 && p.Y != 5) {
t.Errorf("PolyIntersect Error")
}
}
func TestPolyPolyIntersect(t *testing.T) {
p1 := Polygon2d{}
p1.Points = append(p1.Points, Vector2d{0, 0})
p1.Points = append(p1.Points, Vector2d{0, 10})
p1.Points = append(p1.Points, Vector2d{10, 10})
p1.Points = append(p1.Points, Vector2d{10, 0})
p2 := Polygon2d{}
p2.Origin = Point2d{5, 5}
p2.Points = append(p2.Points, Vector2d{0, 0})
p2.Points = append(p2.Points, Vector2d{0, 10})
p2.Points = append(p2.Points, Vector2d{10, 10})
p2.Points = append(p2.Points, Vector2d{10, 0})
i, _, _ := PolyPolyIntersection(p1, Vector2d{0, 0}, p2)
if !i {
t.Errorf("should have intersected")
}
}
func TestPolyPolyIntersectNegativeTranslationScale(t *testing.T) {
p1 := Polygon2d{}
p1.Points = append(p1.Points, Vector2d{0, 0})
p1.Points = append(p1.Points, Vector2d{0, 10})
p1.Points = append(p1.Points, Vector2d{10, 10})
p1.Points = append(p1.Points, Vector2d{10, 0})
p2 := Polygon2d{}
p2.Origin = Point2d{-5, -5}
p2.Points = append(p2.Points, Vector2d{0, 0})
p2.Points = append(p2.Points, Vector2d{0, 10})
p2.Points = append(p2.Points, Vector2d{10, 10})
p2.Points = append(p2.Points, Vector2d{10, 0})
i, _, _ := PolyPolyIntersection(p1, Vector2d{0, 0}, p2)
if !i {
t.Errorf("should not have intersected")
}
}
func TestPolyPolyIntersectMoving(t *testing.T) {
p1 := Polygon2d{}
p1.Points = append(p1.Points, Vector2d{0, 0})
p1.Points = append(p1.Points, Vector2d{0, 10})
p1.Points = append(p1.Points, Vector2d{10, 10})
p1.Points = append(p1.Points, Vector2d{10, 0})
p2 := Polygon2d{}
p2.Origin = Point2d{15, 15}
p2.Points = append(p2.Points, Vector2d{0, 0})
p2.Points = append(p2.Points, Vector2d{0, 10})
p2.Points = append(p2.Points, Vector2d{10, 10})
p2.Points = append(p2.Points, Vector2d{10, 0})
i, m, _ := PolyPolyIntersection(p1, Vector2d{10, 10}, p2)
if i {
t.Errorf("should not have started intersected")
}
if !m {
t.Errorf("should have intersected after movement")
}
}
func TestPolyPolyIntersectFail(t *testing.T) {
p1 := Polygon2d{}
p1.Points = append(p1.Points, Vector2d{0, 0})
p1.Points = append(p1.Points, Vector2d{0, 10})
p1.Points = append(p1.Points, Vector2d{10, 10})
p1.Points = append(p1.Points, Vector2d{10, 0})
p2 := Polygon2d{}
p2.Origin = Point2d{15, 15}
p2.Points = append(p2.Points, Vector2d{0, 0})
p2.Points = append(p2.Points, Vector2d{0, 10})
p2.Points = append(p2.Points, Vector2d{10, 10})
p2.Points = append(p2.Points, Vector2d{10, 0})
i, m, _ := PolyPolyIntersection(p1, Vector2d{0, 0}, p2)
if i || m {
t.Errorf("should not have intersected")
}
}
func TestPolyPolyNoOverlapMoveOnto(t *testing.T) {
p1 := Polygon2d{}
p1.Points = append(p1.Points, Vector2d{0, 0})
p1.Points = append(p1.Points, Vector2d{0, 10})
p1.Points = append(p1.Points, Vector2d{10, 10})
p1.Points = append(p1.Points, Vector2d{10, 0})
p2 := Polygon2d{}
p2.Origin = Point2d{15, 15}
p2.Points = append(p2.Points, Vector2d{0, 0})
p2.Points = append(p2.Points, Vector2d{0, 10})
p2.Points = append(p2.Points, Vector2d{10, 10})
p2.Points = append(p2.Points, Vector2d{10, 0})
i, m, _ := PolyPolyIntersection(p1, Vector2d{-10, -10}, p2)
if i {
t.Errorf("should not have started intersecting")
}
if m {
t.Errorf("should not have intersected")
}
}
func TestOrientedSquare(t *testing.T) {
p := OrientedSquare(Point2d{10, 10}, Vector2d{0.5, 0.5}, 5)
expected := []Vector2d{
{X: 0, Y: 7.071067811865475},
{X: -7.071067811865475, Y: 0},
{X: 0, Y: -7.071067811865475},
{X: 7.071067811865475, Y: 0},
}
for i, v := range expected {
if p.Points[i] != v {
t.Errorf("unexpected points: %+v, expected: %+v", p.Points, expected)
}
}
}
func TestPopPop(t *testing.T) {
v1 := Vector2d{10, 10}
if v1.PopPop() != math.Sqrt(10*10+10*10) {
t.Errorf("Incorrect Magnitude!")
}
}
func TestAASquareAtPoint(t *testing.T) {
p1 := Point2d{10, 10}
expected := AABB2d{
A: Point2d{5, 5},
B: Point2d{15, 15},
}
aabb := AASquareAtPoint(p1, 10)
if aabb != expected {
t.Errorf("incorrect AABB2d; expected: %+v, calculated: %+v ", expected, aabb)
}
}
func TestDistance(t *testing.T) {
if Distance(Point2d{0, 0}, Point2d{0, 0}) != 0.0 {
t.Error("incorectly calculating distance between two points at origin")
}
if Distance(Point2d{0, 0}, Point2d{10, 0}) != 10.0 {
t.Error("incorectly calculating distance between points on y axis")
}
if Distance(Point2d{0, 0}, Point2d{3, 4}) != 5.0 {
t.Error("incorectly calculating distance for pythagorean triple")
}
}
func TestAABBToRect(t *testing.T) {
r := AABB2d{
A: Point2d{5, 5},
B: Point2d{15, 15},
}
p := r.ToPolygon()
expected := Polygon2d{
Points: []Vector2d{
Vector2d{X: 0, Y: 0},
Vector2d{X: 0, Y: 10},
Vector2d{X: 10, Y: 10},
Vector2d{X: 10, Y: 0},
},
Origin: Point2d{X: 5, Y: 5},
}
if len(p.Points) != 4 {
t.Error("incorrect number of points")
}
// alright, this just feels cumbersome ... unfortunate that structs with
// slices can't be compared this way ...
if p.Origin != expected.Origin {
t.Error("just wrong")
}
for i, pp := range expected.Points {
if p.Points[i] != pp {
t.Error("just wrong")
}
}
}
func TestPointInPolygon(t *testing.T) {
poly := AABB2d{
A: Point2d{5, 5},
B: Point2d{15, 15},
}.ToPolygon()
point := Point2d{10, 10}
if success := PointInPolygon(point, poly); !success {
// XXX: this needs removed ;)
t.Skip("NYI")
t.Error("point should have been in polygon")
}
}
func TestExtrapolate(t *testing.T) {
_, err := Extrapolate([]Point2d{})
if err == nil {
t.Error("should require len(P) == 3")
}
tests := []struct {
points []Point2d
expected Point2d
}{
{
points: []Point2d{
{0, 0},
{1, 1},
{2, 2},
},
expected: Point2d{3, 3},
},
{
points: []Point2d{
{0, 0},
{1, 1},
{2, 1},
},
expected: Point2d{3, 0},
},
{
points: []Point2d{
{0, 0},
{1, 0},
{2, 0},
},
expected: Point2d{3, 0},
},
{
points: []Point2d{
{0, 0},
{0, 1},
{0, 2},
},
expected: Point2d{0, 3},
},
{
points: []Point2d{
{0, 0},
{0, 0},
{0, 0},
},
expected: Point2d{0, 0},
},
{
points: []Point2d{
{42, 42},
{42, 42},
{42, 42},
},
expected: Point2d{42, 42},
},
{
points: []Point2d{
{104, 288},
{110, 270},
{120, 250},
},
expected: Point2d{134, 228},
},
{
points: []Point2d{
{0, 0},
{5, 3},
{7, 3},
},
expected: Point2d{6, 0},
},
}
for _, test := range tests {
p, err := Extrapolate(test.points)
if err != nil {
t.Errorf("yeah, this should've been nil: %s", err)
}
if *p != test.expected {
t.Errorf("wrong: expected: %+v, actual: %+v", test.expected, p)
}
}
}
func TestNeville(t *testing.T) {
knownFails := []struct {
points []Point2d
ts []float64
t float64
expected Point2d
err error
}{
{
points: []Point2d{
{0, 0},
{1, 1},
{2, 2},
},
ts: []float64{
1.0 / 3.0,
2.0 / 3.0,
},
err: errors.New("Incompatable slice lengths len(P): 3 != len(ts): 2"),
},
}
for _, test := range knownFails {
p, err := Neville(test.points, test.ts, test.t)
if p != nil {
t.Errorf("p should've been nil: %+v", p)
}
if err.Error() != test.err.Error() {
t.Errorf("expected err: '%s', unexpectd err: '%s'", test.err, err)
}
}
tests := []struct {
points []Point2d
ts []float64
t float64
expected Point2d
}{
{
points: []Point2d{
{0, 0},
{3, 3},
{5, 3},
{7, 0},
},
ts: []float64{
0,
1,
3,
4,
},
t: 2.0,
expected: Point2d{25.0 / 6.0, 4.0},
},
{
points: []Point2d{
{0, 0},
{1, 1},
{2, 2},
},
ts: []float64{
0,
1.0 / 3.0,
2.0 / 3.0,
},
t: 1.0,
expected: Point2d{3, 3},
},
{
points: []Point2d{
{0, 0},
{1, 1},
{2, 1},
},
ts: []float64{
0,
1.0 / 3.0,
2.0 / 3.0,
},
t: 1.0,
expected: Point2d{3, 0},
},
{
ts: []float64{
0,
1.0 / 3.0,
2.0 / 3.0,
},
t: 1.0,
points: []Point2d{
{0, 0},
{1, 0},
{2, 0},
},
expected: Point2d{3, 0},
},
{
points: []Point2d{
{0, 0},
{0, 1},
{0, 2},
},
ts: []float64{
0,
1.0 / 3.0,
2.0 / 3.0,
},
t: 1.0,
expected: Point2d{0, 3},
},
{
ts: []float64{
0,
1.0 / 3.0,
2.0 / 3.0,
},
t: 1.0,
points: []Point2d{
{0, 0},
{0, 0},
{0, 0},
},
expected: Point2d{0, 0},
},
{
points: []Point2d{
{42, 42},
{42, 42},
{42, 42},
},
expected: Point2d{42, 42},
ts: []float64{
0,
1.0 / 3.0,
2.0 / 3.0,
},
t: 1.0,
},
{
points: []Point2d{
{104, 288},
{110, 270},
{120, 250},
},
expected: Point2d{134, 228},
ts: []float64{
0,
1.0 / 3.0,
2.0 / 3.0,
},
t: 1.0,
},
}
for _, test := range tests {
p, err := Neville(test.points, test.ts, test.t)
if err != nil {
t.Errorf("yeah, this should've been nil: %s", err)
}
if p == nil {
t.Errorf("nil point?")
} else {
deltaX := math.Abs(float64(p.X - test.expected.X))
deltaY := math.Abs(float64(p.Y - test.expected.Y))
if deltaX > 1e-4 || deltaY > 1e-4 { // HOLY SHIT!!!
t.Errorf("wrong: expected: %+v, actual: %+v", test.expected, p)
}
}
}
}