mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-01-23 01:36:31 -05:00
Vector float64 (#565)
* Fixed nil pointer in Copy() * Position added Added Floor() and String() methods to Vector. Also added Position which declares an embedded Vector2 and returns various forms of it. * d2vector.Vector2 renamed to d2vector.BigFloat * vector.go renamed to big_float.go * Float64 stub and more renaming * Vector value getters * Separate vector types with initial methods. * Divide and lint warnings. * Distance and Length. * Scale, Abs and Negate. * CompareFloat64Fuzzy delta direction reversed. * Refactor vector_test.go. * Renamed Approx methods. * Distance and Length. * Distance and Length. * Removed BigFloat and Vector, renamed Float64 to Vector, simplified tests. * Angle, SignedAngle and other small functions. * Receiver rename. * SingedAngle and test fixed * Rotate. * SetLength. * Cross. * NinetyAnti and NinetyClock. * Lerp and Clamp. * Reflect and ReflectSurface. * Cardinal convenience functions. * Comments. * Panic on NaN and Inf in Position. * Lint warnings and comments.
This commit is contained in:
parent
e2e8a303c2
commit
029cb62972
@ -1,45 +0,0 @@
|
||||
package d2interface
|
||||
|
||||
import "math/big"
|
||||
|
||||
// Vector is a 2-dimensional vector implementation using big.Float
|
||||
type Vector interface {
|
||||
X() *big.Float
|
||||
Y() *big.Float
|
||||
Marshal() ([]byte, error)
|
||||
Unmarshal(buf []byte) error
|
||||
Clone() Vector
|
||||
Copy(src Vector) Vector
|
||||
// SetFromEntity(entity WorldEntity) Vector
|
||||
Set(x, y *big.Float) Vector
|
||||
SetToPolar(azimuth, radius *big.Float) Vector
|
||||
Equals(src Vector) bool
|
||||
FuzzyEquals(src Vector) bool
|
||||
Abs() Vector
|
||||
Angle() *big.Float
|
||||
SetAngle(angle *big.Float) Vector
|
||||
Add(src Vector) Vector
|
||||
Subtract(src Vector) Vector
|
||||
Multiply(src Vector) Vector
|
||||
Scale(value *big.Float) Vector
|
||||
Divide(src Vector) Vector
|
||||
Negate() Vector
|
||||
Distance(src Vector) *big.Float
|
||||
DistanceSq(src Vector) *big.Float
|
||||
Length() *big.Float
|
||||
SetLength(length *big.Float) Vector
|
||||
LengthSq() (*big.Float, *big.Float)
|
||||
Normalize() Vector
|
||||
NormalizeRightHand() Vector
|
||||
NormalizeLeftHand() Vector
|
||||
Dot(src Vector) *big.Float
|
||||
Cross(src Vector) *big.Float
|
||||
Lerp(src Vector, t *big.Float) Vector
|
||||
Reset() Vector
|
||||
Limit(max *big.Float) Vector
|
||||
Reflect(normal Vector) Vector
|
||||
Mirror(axis Vector) Vector
|
||||
Rotate(delta *big.Float) Vector
|
||||
Floor() Vector
|
||||
String() string
|
||||
}
|
@ -1,56 +1,72 @@
|
||||
package d2vector
|
||||
|
||||
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Position is a vector in world space. The stored
|
||||
// value is the one returned by Position.World()
|
||||
const subTilesPerTile float64 = 5
|
||||
|
||||
// Position is a vector in world space. The stored value is the one returned by Position.World()
|
||||
type Position struct {
|
||||
d2interface.Vector
|
||||
Vector
|
||||
}
|
||||
|
||||
// NewPosition creates a new Position at the given
|
||||
// float64 world position.
|
||||
// NewPosition creates a new Position at the given float64 world position.
|
||||
func NewPosition(x, y float64) *Position {
|
||||
return &Position{New(x, y)}
|
||||
p := &Position{NewVector(x, y)}
|
||||
p.checkValues()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// World is the position, where 1 = one map
|
||||
// tile.
|
||||
func (p *Position) World() d2interface.Vector {
|
||||
return p.Vector
|
||||
// Set sets this position to the given x and y values.
|
||||
func (p *Position) Set(x, y float64) {
|
||||
p.x, p.y = x, y
|
||||
p.checkValues()
|
||||
}
|
||||
|
||||
// Tile is the tile position, always a whole
|
||||
// number.
|
||||
func (p *Position) Tile() d2interface.Vector {
|
||||
func (p *Position) checkValues() {
|
||||
if math.IsNaN(p.x) || math.IsNaN(p.y) {
|
||||
panic(fmt.Sprintf("float value is NaN: %s", p.Vector))
|
||||
}
|
||||
|
||||
if math.IsInf(p.x, 0) || math.IsInf(p.y, 0) {
|
||||
panic(fmt.Sprintf("float value is Inf: %s", p.Vector))
|
||||
}
|
||||
}
|
||||
|
||||
// World is the position, where 1 = one map tile.
|
||||
func (p *Position) World() *Vector {
|
||||
return &p.Vector
|
||||
}
|
||||
|
||||
// Tile is the tile position, always a whole number.
|
||||
func (p *Position) Tile() *Vector {
|
||||
c := p.World().Clone()
|
||||
return c.Floor()
|
||||
}
|
||||
|
||||
// TileOffset is the offset from the tile position,
|
||||
// always < 1.
|
||||
func (p *Position) TileOffset() d2interface.Vector {
|
||||
// TileOffset is the offset from the tile position, always < 1.
|
||||
func (p *Position) TileOffset() *Vector {
|
||||
c := p.World().Clone()
|
||||
return c.Subtract(p.Tile())
|
||||
}
|
||||
|
||||
// SubWorld is the position, where 5 = one map
|
||||
// tile.
|
||||
func (p *Position) SubWorld() d2interface.Vector {
|
||||
// SubWorld is the position, where 5 = one map tile.
|
||||
func (p *Position) SubWorld() *Vector {
|
||||
c := p.World().Clone()
|
||||
return c.Multiply(New(5, 5))
|
||||
return c.Scale(subTilesPerTile)
|
||||
}
|
||||
|
||||
// SubTile is the tile position in sub tiles,
|
||||
// always a multiple of 5.
|
||||
func (p *Position) SubTile() d2interface.Vector {
|
||||
// SubTile is the tile position in sub tiles, always a multiple of 5.
|
||||
func (p *Position) SubTile() *Vector {
|
||||
c := p.Tile().Clone()
|
||||
return c.Multiply(New(5, 5))
|
||||
return c.Scale(subTilesPerTile)
|
||||
}
|
||||
|
||||
// SubTileOffset is the offset from the sub tile
|
||||
// position in sub tiles, always < 1.
|
||||
func (p *Position) SubTileOffset() d2interface.Vector {
|
||||
// SubTileOffset is the offset from the sub tile position in sub tiles, always < 1.
|
||||
func (p *Position) SubTileOffset() *Vector {
|
||||
c := p.SubWorld().Clone()
|
||||
return c.Subtract(p.SubTile())
|
||||
}
|
||||
|
@ -2,16 +2,14 @@ package d2vector
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
)
|
||||
|
||||
func validate(t *testing.T, original, got, want, unchanged d2interface.Vector) {
|
||||
if !got.FuzzyEquals(want) {
|
||||
func validate(t *testing.T, original, got, want, unchanged Vector) {
|
||||
if !got.EqualsApprox(want) {
|
||||
t.Errorf("want %s: got %s", want, got)
|
||||
}
|
||||
|
||||
if !original.FuzzyEquals(unchanged) {
|
||||
if !original.EqualsApprox(unchanged) {
|
||||
t.Errorf("Position value %s was incorrectly changed to %s when calling this method", unchanged, original)
|
||||
}
|
||||
}
|
||||
@ -19,44 +17,44 @@ func validate(t *testing.T, original, got, want, unchanged d2interface.Vector) {
|
||||
func TestTile(t *testing.T) {
|
||||
p := NewPosition(1.6, 1.6)
|
||||
got := p.Tile()
|
||||
want := New(1, 1)
|
||||
unchanged := New(1.6, 1.6)
|
||||
want := NewVector(1, 1)
|
||||
unchanged := NewVector(1.6, 1.6)
|
||||
|
||||
validate(t, p, got, want, unchanged)
|
||||
validate(t, p.Vector, *got, want, unchanged)
|
||||
}
|
||||
|
||||
func TestTileOffset(t *testing.T) {
|
||||
p := NewPosition(1.6, 1.6)
|
||||
got := p.TileOffset()
|
||||
want := New(0.6, 0.6)
|
||||
unchanged := New(1.6, 1.6)
|
||||
want := NewVector(0.6, 0.6)
|
||||
unchanged := NewVector(1.6, 1.6)
|
||||
|
||||
validate(t, p, got, want, unchanged)
|
||||
validate(t, p.Vector, *got, want, unchanged)
|
||||
}
|
||||
|
||||
func TestSubWorld(t *testing.T) {
|
||||
p := NewPosition(1, 1)
|
||||
got := p.SubWorld()
|
||||
want := New(5, 5)
|
||||
unchanged := New(1, 1)
|
||||
want := NewVector(5, 5)
|
||||
unchanged := NewVector(1, 1)
|
||||
|
||||
validate(t, p, got, want, unchanged)
|
||||
validate(t, p.Vector, *got, want, unchanged)
|
||||
}
|
||||
|
||||
func TestSubTile(t *testing.T) {
|
||||
p := NewPosition(1, 1)
|
||||
got := p.SubTile()
|
||||
want := New(5, 5)
|
||||
unchanged := New(1, 1)
|
||||
want := NewVector(5, 5)
|
||||
unchanged := NewVector(1, 1)
|
||||
|
||||
validate(t, p, got, want, unchanged)
|
||||
validate(t, p.Vector, *got, want, unchanged)
|
||||
}
|
||||
|
||||
func TestSubTileOffset(t *testing.T) {
|
||||
p := NewPosition(1.1, 1.1)
|
||||
got := p.SubTileOffset()
|
||||
want := New(0.5, 0.5)
|
||||
unchanged := New(1.1, 1.1)
|
||||
want := NewVector(0.5, 0.5)
|
||||
unchanged := NewVector(1.1, 1.1)
|
||||
|
||||
validate(t, p, got, want, unchanged)
|
||||
validate(t, p.Vector, *got, want, unchanged)
|
||||
}
|
||||
|
@ -1,406 +1,312 @@
|
||||
// Package d2vector is an Implementation of 2-dimensional vectors with big.Float components
|
||||
// Package d2vector provides an implementation of a 2D Euclidean vector using float64 to store the two values.
|
||||
package d2vector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
)
|
||||
|
||||
const (
|
||||
// Epsilon is the threshold for what is `smol enough`
|
||||
epsilon float64 = 0.0001
|
||||
|
||||
// d2precision is how much precision we want from big.Float
|
||||
d2precision uint = 64 // was chosen arbitrarily
|
||||
|
||||
// for convenience in negating sign
|
||||
negative1 float64 = -1.0
|
||||
|
||||
// for convenience
|
||||
zero float64 = 0.0
|
||||
)
|
||||
|
||||
// New creates a new Vector2 and returns a pointer to it.
|
||||
func New(x, y float64) *Vector2 {
|
||||
xbf, ybf := big.NewFloat(x), big.NewFloat(y)
|
||||
xbf.SetPrec(d2precision)
|
||||
ybf.SetPrec(d2precision)
|
||||
result := &Vector2{xbf, ybf}
|
||||
|
||||
return result
|
||||
// Vector is an implementation of a Euclidean vector using float64 with common vector convenience methods.
|
||||
type Vector struct {
|
||||
x, y float64
|
||||
}
|
||||
|
||||
// Vector2 has two big.Floats x and y and a set of methods
|
||||
// for common vector operations.
|
||||
type Vector2 struct {
|
||||
x *big.Float
|
||||
y *big.Float
|
||||
const two float64 = 2
|
||||
|
||||
// NewVector creates a new Vector with the given x and y values.
|
||||
func NewVector(x, y float64) Vector {
|
||||
return Vector{x, y}
|
||||
}
|
||||
|
||||
// X returns the x member of the Vector
|
||||
func (v *Vector2) X() *big.Float {
|
||||
return v.x
|
||||
// Equals returns true if the float64 values of this vector are exactly equal to the given Vector.
|
||||
func (v *Vector) Equals(o Vector) bool {
|
||||
return v.x == o.x && v.y == o.y
|
||||
}
|
||||
|
||||
// Y returns the y member of the Vector
|
||||
func (v *Vector2) Y() *big.Float {
|
||||
return v.y
|
||||
// EqualsApprox returns true if the values of this Vector are approximately equal to those of the given Vector. If the
|
||||
// difference between either of the value pairs is smaller than d2math.Epsilon, they will be considered equal.
|
||||
func (v *Vector) EqualsApprox(o Vector) bool {
|
||||
x, y := v.CompareApprox(o)
|
||||
return x == 0 && y == 0
|
||||
}
|
||||
|
||||
// Marshal converts the Vector into a slice of bytes
|
||||
func (v *Vector2) Marshal() ([]byte, error) {
|
||||
// TODO not sure how to do this properly
|
||||
return nil, nil
|
||||
// CompareApprox returns 2 ints describing the difference between the vectors. If the difference between either of the
|
||||
// value pairs is smaller than d2math.Epsilon, they will be considered equal.
|
||||
func (v *Vector) CompareApprox(o Vector) (x, y int) {
|
||||
return d2math.CompareFloat64Fuzzy(v.x, o.x),
|
||||
d2math.CompareFloat64Fuzzy(v.y, o.y)
|
||||
}
|
||||
|
||||
// Unmarshal converts a slice of bytes to x/y *big.Float
|
||||
// and assigns them to itself
|
||||
func (v *Vector2) Unmarshal(buf []byte) error {
|
||||
// TODO not sure how to do this properly
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone creates a copy of this Vector
|
||||
func (v *Vector2) Clone() d2interface.Vector {
|
||||
result := New(0, 0)
|
||||
result.Copy(v)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Copy copies the src x/y members to this Vector x/y members
|
||||
func (v *Vector2) Copy(src d2interface.Vector) d2interface.Vector {
|
||||
v.x.Copy(src.X())
|
||||
v.y.Copy(src.Y())
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// SetFromEntity copies the vector of a world entity
|
||||
// func (v *Vector2) SetFromEntity(entity d2interface.WorldEntity) d2interface.Vector {
|
||||
// return v.Copy(entity.Position())
|
||||
// }
|
||||
|
||||
// Set the x,y members of the Vector
|
||||
func (v *Vector2) Set(x, y *big.Float) d2interface.Vector {
|
||||
// Set the vector values to the given float64 values.
|
||||
func (v *Vector) Set(x, y float64) *Vector {
|
||||
v.x = x
|
||||
v.y = y
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// SetToPolar sets the `x` and `y` values of this object
|
||||
// from a given polar coordinate.
|
||||
func (v *Vector2) SetToPolar(azimuth, radius *big.Float) d2interface.Vector {
|
||||
// HACK we should do this better, with the big.Float
|
||||
a, _ := azimuth.Float64()
|
||||
r, _ := radius.Float64()
|
||||
v.x.SetFloat64(math.Cos(a) * r)
|
||||
v.y.SetFloat64(math.Sin(a) * r)
|
||||
|
||||
return v
|
||||
// Clone returns a new a copy of this Vector.
|
||||
func (v *Vector) Clone() Vector {
|
||||
return NewVector(v.x, v.y)
|
||||
}
|
||||
|
||||
// Equals check whether this Vector is equal to a given Vector.
|
||||
func (v *Vector2) Equals(src d2interface.Vector) bool {
|
||||
return v.x.Cmp(src.X()) == 0 && v.y.Cmp(src.Y()) == 0
|
||||
}
|
||||
|
||||
// FuzzyEquals checks if the Vector is approximately equal
|
||||
// to the given Vector. epsilon is what we consider `smol enough`
|
||||
func (v *Vector2) FuzzyEquals(src d2interface.Vector) bool {
|
||||
smol := big.NewFloat(epsilon)
|
||||
d := v.Distance(src)
|
||||
d.Abs(d)
|
||||
|
||||
return d.Cmp(smol) < 1 || d.Cmp(smol) < 1
|
||||
}
|
||||
|
||||
// Abs returns a clone that is positive
|
||||
func (v *Vector2) Abs() d2interface.Vector {
|
||||
clone := v.Clone()
|
||||
neg1 := big.NewFloat(-1.0)
|
||||
|
||||
if clone.X().Sign() == -1 { // is negative1
|
||||
clone.X().Mul(clone.X(), neg1)
|
||||
}
|
||||
|
||||
if v.Y().Sign() == -1 { // is negative1
|
||||
clone.Y().Mul(clone.Y(), neg1)
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// Angle computes the angle in radians with respect
|
||||
// to the positive x-axis
|
||||
func (v *Vector2) Angle() *big.Float {
|
||||
// HACK we should find a way to do this purely
|
||||
// with big.Float
|
||||
floatX, _ := v.X().Float64()
|
||||
floatY, _ := v.Y().Float64()
|
||||
floatAngle := math.Atan2(floatY, floatX)
|
||||
|
||||
if floatAngle < 0 {
|
||||
floatAngle += 2.0 * math.Pi
|
||||
}
|
||||
|
||||
return big.NewFloat(floatAngle)
|
||||
}
|
||||
|
||||
// SetAngle sets the angle of this Vector
|
||||
func (v *Vector2) SetAngle(angle *big.Float) d2interface.Vector {
|
||||
return v.SetToPolar(angle, v.Length())
|
||||
}
|
||||
|
||||
// Add to this Vector the components of the given Vector
|
||||
func (v *Vector2) Add(src d2interface.Vector) d2interface.Vector {
|
||||
v.x.Add(v.x, src.X())
|
||||
v.y.Add(v.y, src.Y())
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Subtract from this Vector the components of the given Vector
|
||||
func (v *Vector2) Subtract(src d2interface.Vector) d2interface.Vector {
|
||||
v.x.Sub(v.x, src.X())
|
||||
v.y.Sub(v.y, src.Y())
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Multiply this Vector with the components of the given Vector
|
||||
func (v *Vector2) Multiply(src d2interface.Vector) d2interface.Vector {
|
||||
v.x.Mul(v.x, src.X())
|
||||
v.y.Mul(v.y, src.Y())
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Scale this Vector by the given value
|
||||
func (v *Vector2) Scale(s *big.Float) d2interface.Vector {
|
||||
v.x.Sub(v.x, s)
|
||||
v.y.Sub(v.y, s)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Divide this Vector by the given Vector
|
||||
func (v *Vector2) Divide(src d2interface.Vector) d2interface.Vector {
|
||||
v.x.Quo(v.x, src.X())
|
||||
v.y.Quo(v.y, src.Y())
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Negate thex and y components of this Vector
|
||||
func (v *Vector2) Negate() d2interface.Vector {
|
||||
return v.Scale(big.NewFloat(negative1))
|
||||
}
|
||||
|
||||
// Distance calculate the distance between this Vector and the given Vector
|
||||
func (v *Vector2) Distance(src d2interface.Vector) *big.Float {
|
||||
dist := v.DistanceSq(src)
|
||||
|
||||
return dist.Sqrt(dist)
|
||||
}
|
||||
|
||||
// DistanceSq calculate the distance suared between this Vector and the given
|
||||
// Vector
|
||||
func (v *Vector2) DistanceSq(src d2interface.Vector) *big.Float {
|
||||
delta := src.Clone().Subtract(v)
|
||||
deltaSq := delta.Multiply(delta)
|
||||
|
||||
return big.NewFloat(zero).Add(deltaSq.X(), deltaSq.Y())
|
||||
}
|
||||
|
||||
// Length returns the length of this Vector
|
||||
func (v *Vector2) Length() *big.Float {
|
||||
xsq, ysq := v.LengthSq()
|
||||
|
||||
return xsq.Add(xsq, ysq)
|
||||
}
|
||||
|
||||
// LengthSq returns the x and y values squared
|
||||
func (v *Vector2) LengthSq() (*big.Float, *big.Float) {
|
||||
clone := v.Clone()
|
||||
x, y := clone.X(), clone.Y()
|
||||
|
||||
return x.Mul(x, x), y.Mul(y, y)
|
||||
}
|
||||
|
||||
// SetLength sets the length of this Vector
|
||||
func (v *Vector2) SetLength(length *big.Float) d2interface.Vector {
|
||||
return v.Normalize().Scale(length)
|
||||
}
|
||||
|
||||
// Normalize Makes the vector a unit length vector (magnitude of 1) in the same
|
||||
// direction.
|
||||
func (v *Vector2) Normalize() d2interface.Vector {
|
||||
xsq, ysq := v.LengthSq()
|
||||
length := big.NewFloat(zero).Add(xsq, ysq)
|
||||
one := big.NewFloat(1.0)
|
||||
|
||||
if length.Cmp(one) > 0 {
|
||||
length.Quo(one, length.Sqrt(length))
|
||||
|
||||
v.x.Mul(v.x, length)
|
||||
v.y.Mul(v.y, length)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// NormalizeRightHand rotate this Vector to its perpendicular,
|
||||
// in the positive direction.
|
||||
func (v *Vector2) NormalizeRightHand() d2interface.Vector {
|
||||
x := v.x
|
||||
v.x = v.y.Mul(v.y, big.NewFloat(negative1))
|
||||
v.y = x
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// NormalizeLeftHand rotate this Vector to its perpendicular,
|
||||
// in the negative1 direction.
|
||||
func (v *Vector2) NormalizeLeftHand() d2interface.Vector {
|
||||
x := v.x
|
||||
v.x = v.y
|
||||
v.y = x.Mul(x, big.NewFloat(negative1))
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Dot returns the dot product of this Vector and the given Vector.
|
||||
func (v *Vector2) Dot(src d2interface.Vector) *big.Float {
|
||||
c := v.Clone()
|
||||
c.X().Mul(c.X(), src.X())
|
||||
c.Y().Mul(c.Y(), src.Y())
|
||||
|
||||
return c.X().Add(c.X(), c.Y())
|
||||
}
|
||||
|
||||
// Cross Calculate the cross product of this Vector and the given Vector.
|
||||
func (v *Vector2) Cross(src d2interface.Vector) *big.Float {
|
||||
c := v.Clone()
|
||||
c.X().Mul(c.X(), src.X())
|
||||
c.Y().Mul(c.Y(), src.Y())
|
||||
|
||||
return c.X().Sub(c.X(), c.Y())
|
||||
}
|
||||
|
||||
// Lerp Linearly interpolate between this Vector and the given Vector.
|
||||
func (v *Vector2) Lerp(
|
||||
src d2interface.Vector,
|
||||
t *big.Float,
|
||||
) d2interface.Vector {
|
||||
vc, sc := v.Clone(), src.Clone()
|
||||
x, y := vc.X(), vc.Y()
|
||||
v.x.Set(x.Add(x, t.Mul(t, sc.X().Sub(sc.X(), x))))
|
||||
v.y.Set(y.Add(y, t.Mul(t, sc.Y().Sub(sc.Y(), y))))
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Reset this Vector the zero vector (0, 0).
|
||||
func (v *Vector2) Reset() d2interface.Vector {
|
||||
v.x.SetFloat64(zero)
|
||||
v.y.SetFloat64(zero)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Limit the length (or magnitude) of this Vector
|
||||
func (v *Vector2) Limit(max *big.Float) d2interface.Vector {
|
||||
length := v.Length()
|
||||
|
||||
if max.Cmp(length) < 0 {
|
||||
v.Scale(length.Quo(max, length))
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Reflect this Vector off a line defined by a normal.
|
||||
func (v *Vector2) Reflect(normal d2interface.Vector) d2interface.Vector {
|
||||
clone := v.Clone()
|
||||
clone.Normalize()
|
||||
|
||||
two := big.NewFloat(2.0) // there's some matrix algebra magic here
|
||||
dot := v.Clone().Dot(normal)
|
||||
normal.Scale(two.Mul(two, dot))
|
||||
|
||||
return v.Subtract(normal)
|
||||
}
|
||||
|
||||
// Mirror reflect this Vector across another.
|
||||
func (v *Vector2) Mirror(axis d2interface.Vector) d2interface.Vector {
|
||||
return v.Reflect(axis).Negate()
|
||||
}
|
||||
|
||||
// Rotate this Vector by an angle amount.
|
||||
func (v *Vector2) Rotate(angle *big.Float) d2interface.Vector {
|
||||
// HACK we should do this only with big.Float, not float64
|
||||
// we are throwing away the precision here
|
||||
floatAngle, _ := angle.Float64()
|
||||
cos := math.Cos(floatAngle)
|
||||
sin := math.Sin(floatAngle)
|
||||
|
||||
oldX, _ := v.x.Float64()
|
||||
oldY, _ := v.y.Float64()
|
||||
|
||||
newX := big.NewFloat(cos*oldX - sin*oldY)
|
||||
newY := big.NewFloat(sin*oldX + cos*oldY)
|
||||
|
||||
v.Set(newX, newY)
|
||||
// Copy sets this vector's values to those of the given vector.
|
||||
func (v *Vector) Copy(o *Vector) *Vector {
|
||||
v.x = o.x
|
||||
v.y = o.y
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Floor rounds the vector down to the nearest whole numbers.
|
||||
func (v *Vector2) Floor() d2interface.Vector {
|
||||
var xi, yi big.Int
|
||||
v.x.Int(&xi)
|
||||
v.y.Int(&yi)
|
||||
v.X().SetInt(&xi)
|
||||
v.Y().SetInt(&yi)
|
||||
func (v *Vector) Floor() *Vector {
|
||||
v.x = math.Floor(v.x)
|
||||
v.y = math.Floor(v.y)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *Vector2) String() string {
|
||||
return fmt.Sprintf("Vector2{%s, %s}", v.x.Text('f', 5), v.y.Text('f', 5))
|
||||
// Clamp limits the values of v to those of a and b. If the values of v are between those of a and b they will be
|
||||
// unchanged.
|
||||
func (v *Vector) Clamp(a, b *Vector) *Vector {
|
||||
v.x = d2math.ClampFloat64(v.x, a.x, b.x)
|
||||
v.y = d2math.ClampFloat64(v.y, a.y, b.y)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Up returns a new vector (0, 1)
|
||||
func Up() d2interface.Vector {
|
||||
return New(0, 1)
|
||||
// Add the given vector to this vector.
|
||||
func (v *Vector) Add(o *Vector) *Vector {
|
||||
v.x += o.x
|
||||
v.y += o.y
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Down returns a new vector (0, -1)
|
||||
func Down() d2interface.Vector {
|
||||
return New(0, -1)
|
||||
// Subtract the given vector from this vector.
|
||||
func (v *Vector) Subtract(o *Vector) *Vector {
|
||||
v.x -= o.x
|
||||
v.y -= o.y
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Right returns a new vector (1, 0)
|
||||
func Right() d2interface.Vector {
|
||||
return New(1, 0)
|
||||
// Multiply this Vector by the given Vector.
|
||||
func (v *Vector) Multiply(o *Vector) *Vector {
|
||||
v.x *= o.x
|
||||
v.y *= o.y
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Left returns a new vector (-1, 0)
|
||||
func Left() d2interface.Vector {
|
||||
return New(-1, 0)
|
||||
// Scale multiplies both values of this vector by a single given value.
|
||||
func (v *Vector) Scale(s float64) *Vector {
|
||||
v.x *= s
|
||||
v.y *= s
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// One returns a new vector (1, 1)
|
||||
func One() d2interface.Vector {
|
||||
return New(1, 1)
|
||||
// Divide this vector by the given vector.
|
||||
func (v *Vector) Divide(o *Vector) *Vector {
|
||||
v.x /= o.x
|
||||
v.y /= o.y
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Zero returns a new vector (0, 0)
|
||||
func Zero() d2interface.Vector {
|
||||
return New(0, 0)
|
||||
// Abs sets the vector to it's absolute (positive) equivalent.
|
||||
func (v *Vector) Abs() *Vector {
|
||||
xm, ym := 1.0, 1.0
|
||||
if v.x < 0 {
|
||||
xm = -1
|
||||
}
|
||||
|
||||
if v.y < 0 {
|
||||
ym = -1
|
||||
}
|
||||
|
||||
v.x *= xm
|
||||
v.y *= ym
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Negate multiplies this vector by -1.
|
||||
func (v *Vector) Negate() *Vector {
|
||||
return v.Scale(-1)
|
||||
}
|
||||
|
||||
// Distance between this Vector's position and that of the given Vector.
|
||||
func (v *Vector) Distance(o Vector) float64 {
|
||||
delta := o.Clone()
|
||||
delta.Subtract(v)
|
||||
|
||||
return delta.Length()
|
||||
}
|
||||
|
||||
// Length (magnitude/quantity) of this Vector.
|
||||
func (v *Vector) Length() float64 {
|
||||
return math.Sqrt(v.Dot(v))
|
||||
}
|
||||
|
||||
// SetLength sets the length of this Vector without changing the direction.
|
||||
func (v *Vector) SetLength(length float64) *Vector {
|
||||
v.Normalize()
|
||||
v.Scale(length)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Lerp sets this vector to the linear interpolation between this and the given vector. The interp argument determines
|
||||
// the distance between the two vectors. An interp of 0 will return this vector and 1 will return the given vector.
|
||||
func (v *Vector) Lerp(o *Vector, interp float64) *Vector {
|
||||
v.x = d2math.Lerp(v.x, o.x, interp)
|
||||
v.y = d2math.Lerp(v.y, o.y, interp)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Dot returns the dot product of this Vector and the given Vector.
|
||||
func (v *Vector) Dot(o *Vector) float64 {
|
||||
return v.x*o.x + v.y*o.y
|
||||
}
|
||||
|
||||
// Cross returns the cross product of this Vector and the given Vector. Note: Cross product is specific to 3D space.
|
||||
// This a not cross product. It is the Z component of a 3D vector cross product calculation. The X and Y components use
|
||||
// the value of z which doesn't exist in 2D. See:
|
||||
// https://stackoverflow.com/questions/243945/calculating-a-2d-vectors-cross-product
|
||||
//
|
||||
// The sign of Cross indicates whether the direction between the points described by vectors v and o around the origin
|
||||
// (0,0) moves clockwise or anti-clockwise. The perspective is from the would-be position of positive Z and the
|
||||
// direction is from v to o.
|
||||
//
|
||||
// Negative = clockwise
|
||||
// Positive = anti-clockwise
|
||||
// 0 = vectors are identical.
|
||||
func (v *Vector) Cross(o Vector) float64 {
|
||||
return v.x*o.y - v.y*o.x
|
||||
}
|
||||
|
||||
// Normalize sets the vector length to 1 without changing the direction. The normalized vector may be scaled by the
|
||||
// float64 return value to restore it's original length.
|
||||
func (v *Vector) Normalize() float64 {
|
||||
multiplier := 1 / v.Length()
|
||||
v.Scale(multiplier)
|
||||
|
||||
return 1 / multiplier
|
||||
}
|
||||
|
||||
// Angle computes the unsigned angle in radians from this vector to the given vector. This angle will never exceed half
|
||||
// a full circle. For angles describing a full circumference use SignedAngle.
|
||||
func (v *Vector) Angle(o Vector) float64 {
|
||||
from := v.Clone()
|
||||
from.Normalize()
|
||||
|
||||
to := o.Clone()
|
||||
to.Normalize()
|
||||
|
||||
denominator := math.Sqrt(from.Length() * to.Length())
|
||||
dotClamped := d2math.ClampFloat64(from.Dot(&to)/denominator, -1, 1)
|
||||
|
||||
return math.Acos(dotClamped)
|
||||
}
|
||||
|
||||
// SignedAngle computes the signed (clockwise) angle in radians from this vector to the given vector.
|
||||
func (v *Vector) SignedAngle(o Vector) float64 {
|
||||
unsigned := v.Angle(o)
|
||||
sign := d2math.Sign(v.x*o.y - v.y*o.x)
|
||||
|
||||
if sign > 0 {
|
||||
return d2math.RadFull - unsigned
|
||||
}
|
||||
|
||||
return unsigned
|
||||
}
|
||||
|
||||
// Reflect sets this Vector to it's reflection off a line defined by the given normal.
|
||||
func (v *Vector) Reflect(normal Vector) *Vector {
|
||||
normal.Normalize()
|
||||
undo := v.Normalize()
|
||||
|
||||
// 1*Dot is the directional (ignoring length) difference between the vector and the normal. Therefore 2*Dot takes
|
||||
// us beyond the normal to the angle with the equivalent distance in the other direction i.e. the reflection.
|
||||
normal.Scale(two * v.Dot(&normal))
|
||||
v.Subtract(&normal)
|
||||
v.Scale(undo)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// ReflectSurface does the same thing as Reflect, except the given vector describes,
|
||||
// the surface line, not it's normal.
|
||||
func (v *Vector) ReflectSurface(surface Vector) *Vector {
|
||||
v.Reflect(surface).Negate()
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Rotate moves the vector around it's origin clockwise, by the given angle in radians.
|
||||
func (v *Vector) Rotate(angle float64) *Vector {
|
||||
a := -angle
|
||||
x := v.x*math.Cos(a) - v.y*math.Sin(a)
|
||||
y := v.x*math.Sin(a) + v.y*math.Cos(a)
|
||||
v.x = x
|
||||
v.y = y
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// NinetyAnti rotates this vector by 90 degrees anti-clockwise.
|
||||
func (v *Vector) NinetyAnti() *Vector {
|
||||
x := v.x
|
||||
v.x = v.y * -1
|
||||
v.y = x
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// NinetyClock rotates this vector by 90 degrees clockwise.
|
||||
func (v *Vector) NinetyClock() *Vector {
|
||||
y := v.y
|
||||
v.y = v.x * -1
|
||||
v.x = y
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (v Vector) String() string {
|
||||
return fmt.Sprintf("Vector{%.3f, %.3f}", v.x, v.y)
|
||||
}
|
||||
|
||||
// VectorUp returns a new vector (0, 1)
|
||||
func VectorUp() Vector {
|
||||
return NewVector(0, 1)
|
||||
}
|
||||
|
||||
// VectorDown returns a new vector (0, -1)
|
||||
func VectorDown() Vector {
|
||||
return NewVector(0, -1)
|
||||
}
|
||||
|
||||
// VectorRight returns a new vector (1, 0)
|
||||
func VectorRight() Vector {
|
||||
return NewVector(1, 0)
|
||||
}
|
||||
|
||||
// VectorLeft returns a new vector (-1, 0)
|
||||
func VectorLeft() Vector {
|
||||
return NewVector(-1, 0)
|
||||
}
|
||||
|
||||
// VectorOne returns a new vector (1, 1)
|
||||
func VectorOne() Vector {
|
||||
return NewVector(1, 1)
|
||||
}
|
||||
|
||||
// VectorZero returns a new vector (0, 0)
|
||||
func VectorZero() Vector {
|
||||
return NewVector(0, 0)
|
||||
}
|
||||
|
@ -1,33 +1,467 @@
|
||||
package d2vector
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
v := New(1, 1)
|
||||
want := New(1, 1)
|
||||
got := v.Clone()
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
)
|
||||
|
||||
func evaluateVector(description string, want, got Vector, t *testing.T) {
|
||||
if !got.Equals(want) {
|
||||
t.Errorf("wanted %s: got %s", want, got)
|
||||
t.Errorf("%s: wanted %s: got %s", description, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbs(t *testing.T) {
|
||||
v := New(-1, -1)
|
||||
want := New(1, 1)
|
||||
got := v.Abs()
|
||||
|
||||
if !got.Equals(want) {
|
||||
t.Errorf("wanted %s: got %s", want, got)
|
||||
func evaluateVectorApprox(description string, want, got Vector, t *testing.T) {
|
||||
if !got.EqualsApprox(want) {
|
||||
t.Errorf("%s: wanted %s: got %s", description, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateScalar(description string, want, got float64, t *testing.T) {
|
||||
if want != got {
|
||||
t.Errorf("%s: wanted %f: got %f", description, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateScalarApprox(description string, want, got float64, t *testing.T) {
|
||||
if d2math.CompareFloat64Fuzzy(want, got) != 0 {
|
||||
t.Errorf("%s: wanted %f: got %f", description, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateChanged(description string, original, clone Vector, t *testing.T) {
|
||||
if !original.Equals(clone) {
|
||||
t.Errorf("%s: changed vector %s to %s unexpectedly", description, clone, original)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEquals(t *testing.T) {
|
||||
a := NewVector(1, 2)
|
||||
b := NewVector(1, 2)
|
||||
|
||||
got := a.Equals(b)
|
||||
|
||||
if !got {
|
||||
t.Errorf("exact equality %s and %s: wanted true: got %t", a, b, got)
|
||||
}
|
||||
|
||||
c := NewVector(3, 4)
|
||||
|
||||
got = a.Equals(c)
|
||||
|
||||
if got {
|
||||
t.Errorf("exact equality %s and %s: wanted false: got %t", a, c, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqualsF(t *testing.T) {
|
||||
subEpsilon := d2math.Epsilon / 3
|
||||
|
||||
a := NewVector(1, 2)
|
||||
b := NewVector(1+subEpsilon, 2+subEpsilon)
|
||||
|
||||
got := a.EqualsApprox(b)
|
||||
|
||||
if !got {
|
||||
t.Errorf("approximate equality %s and %s: wanted true: got %t", a, b, got)
|
||||
}
|
||||
|
||||
c := NewVector(1+d2math.Epsilon, 2+d2math.Epsilon)
|
||||
|
||||
got = a.EqualsApprox(c)
|
||||
|
||||
if got {
|
||||
t.Errorf("approximate equality %s and %s: wanted false: got %t", a, c, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareF(t *testing.T) {
|
||||
subEpsilon := d2math.Epsilon / 3
|
||||
|
||||
f := NewVector(1+subEpsilon, 1+subEpsilon)
|
||||
c := NewVector(1, 1)
|
||||
xWant, yWant := 0, 0
|
||||
yGot, xGot := f.CompareApprox(c)
|
||||
|
||||
if xGot != xWant || yGot != yWant {
|
||||
t.Errorf("approximate comparison %s and %s: wanted (%d, %d): got (%d, %d)", f, c, xWant, yWant, xGot, yGot)
|
||||
}
|
||||
|
||||
f = NewVector(2, 2)
|
||||
c = NewVector(-1, 3)
|
||||
xWant, yWant = 1, -1
|
||||
xGot, yGot = f.CompareApprox(c)
|
||||
|
||||
if xGot != xWant || yGot != yWant {
|
||||
t.Errorf("approximate comparison %s and %s: wanted (%d, %d): got (%d, %d)", f, c, xWant, yWant, xGot, yGot)
|
||||
}
|
||||
|
||||
f = NewVector(2, 2)
|
||||
c = NewVector(3, -1)
|
||||
xWant, yWant = -1, 1
|
||||
xGot, yGot = f.CompareApprox(c)
|
||||
|
||||
if xGot != xWant || yGot != yWant {
|
||||
t.Errorf("approximate comparison %s and %s: wanted (%d, %d): got (%d, %d)", f, c, xWant, yWant, xGot, yGot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
v := NewVector(1, 1)
|
||||
want := NewVector(2, 3)
|
||||
got := v.Clone()
|
||||
got.Set(2, 3)
|
||||
|
||||
evaluateVector(fmt.Sprintf("set %s to (2, 3)", v), want, got, t)
|
||||
}
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
want := NewVector(1, 2)
|
||||
got := want.Clone()
|
||||
|
||||
evaluateVector(fmt.Sprintf("clone %s", want), want, got, t)
|
||||
}
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
want := NewVector(1, 2)
|
||||
got := NewVector(0, 0)
|
||||
got.Copy(&want)
|
||||
|
||||
evaluateVector(fmt.Sprintf("copy %s to %s", got, want), want, got, t)
|
||||
}
|
||||
|
||||
func TestFloor(t *testing.T) {
|
||||
v := New(1.6, 1.6)
|
||||
v := NewVector(1.6, 1.6)
|
||||
want := NewVector(1, 1)
|
||||
got := v.Clone()
|
||||
got.Floor()
|
||||
|
||||
want := New(1, 1)
|
||||
|
||||
if !v.Floor().Equals(want) {
|
||||
t.Errorf("want %s: got %s", want, v)
|
||||
}
|
||||
evaluateVector(fmt.Sprintf("round %s down", v), want, got, t)
|
||||
}
|
||||
|
||||
func TestClamp(t *testing.T) {
|
||||
v := NewVector(-10, 10)
|
||||
c := v.Clone()
|
||||
a := NewVector(2, 2)
|
||||
b := NewVector(7, 7)
|
||||
|
||||
want := NewVector(2, 7)
|
||||
got := v.Clamp(&a, &b)
|
||||
|
||||
evaluateVector(fmt.Sprintf("clamp %s between %s and %s", c, a, b), want, *got, t)
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
v := NewVector(1, 1)
|
||||
add := NewVector(0.5, 0.5)
|
||||
want := NewVector(1.5, 1.5)
|
||||
got := v.Clone()
|
||||
got.Add(&add)
|
||||
|
||||
evaluateVector(fmt.Sprintf("add %s to %s", add, v), want, got, t)
|
||||
}
|
||||
|
||||
func TestSubtract(t *testing.T) {
|
||||
v := NewVector(1, 1)
|
||||
subtract := NewVector(0.6, 0.6)
|
||||
want := NewVector(0.4, 0.4)
|
||||
got := v.Clone()
|
||||
got.Subtract(&subtract)
|
||||
|
||||
evaluateVector(fmt.Sprintf("subtract %s from %s", subtract, v), want, got, t)
|
||||
}
|
||||
|
||||
func TestMultiply(t *testing.T) {
|
||||
v := NewVector(1, 1)
|
||||
multiply := NewVector(2, 2)
|
||||
want := NewVector(2, 2)
|
||||
got := v.Clone()
|
||||
got.Multiply(&multiply)
|
||||
|
||||
evaluateVector(fmt.Sprintf("multiply %s by %s", v, multiply), want, got, t)
|
||||
}
|
||||
|
||||
func TestDivide(t *testing.T) {
|
||||
v := NewVector(1, 1)
|
||||
divide := NewVector(2, 2)
|
||||
want := NewVector(0.5, 0.5)
|
||||
got := v.Clone()
|
||||
got.Divide(÷)
|
||||
|
||||
evaluateVector(fmt.Sprintf("divide %s by %s", v, divide), want, got, t)
|
||||
}
|
||||
|
||||
func TestScale(t *testing.T) {
|
||||
v := NewVector(2, 3)
|
||||
want := NewVector(4, 6)
|
||||
got := v.Clone()
|
||||
got.Scale(2)
|
||||
|
||||
evaluateVector(fmt.Sprintf("scale %s by 2", v), want, got, t)
|
||||
}
|
||||
|
||||
func TestAbs(t *testing.T) {
|
||||
v := NewVector(-1, 1)
|
||||
want := NewVector(1, 1)
|
||||
got := v.Clone()
|
||||
got.Abs()
|
||||
|
||||
evaluateVector(fmt.Sprintf("absolute value of %s", v), want, got, t)
|
||||
}
|
||||
|
||||
func TestNegate(t *testing.T) {
|
||||
v := NewVector(-1, 1)
|
||||
want := NewVector(1, -1)
|
||||
got := v.Clone()
|
||||
got.Negate()
|
||||
|
||||
evaluateVector(fmt.Sprintf("inverse value of %s", v), want, got, t)
|
||||
}
|
||||
|
||||
func TestDistance(t *testing.T) {
|
||||
v := NewVector(1, 3)
|
||||
other := NewVector(1, -1)
|
||||
want := 4.0
|
||||
c := v.Clone()
|
||||
got := c.Distance(other)
|
||||
|
||||
evaluateScalar(fmt.Sprintf("distance from %s to %s", v, other), want, got, t)
|
||||
}
|
||||
|
||||
func TestLength(t *testing.T) {
|
||||
v := NewVector(2, 0)
|
||||
c := v.Clone()
|
||||
want := 2.0
|
||||
got := v.Length()
|
||||
|
||||
d := fmt.Sprintf("length of %s", c)
|
||||
|
||||
evaluateChanged(d, v, c, t)
|
||||
|
||||
evaluateScalar(d, want, got, t)
|
||||
}
|
||||
|
||||
func TestSetLength(t *testing.T) {
|
||||
v := NewVector(1, 1)
|
||||
c := v.Clone()
|
||||
want := 2.0
|
||||
got := v.SetLength(want).Length()
|
||||
|
||||
d := fmt.Sprintf("length of %s", c)
|
||||
|
||||
evaluateScalarApprox(d, want, got, t)
|
||||
}
|
||||
|
||||
func TestLerp(t *testing.T) {
|
||||
a := NewVector(0, 0)
|
||||
b := NewVector(-20, 10)
|
||||
|
||||
x := 0.3
|
||||
|
||||
want := NewVector(-6, 3)
|
||||
got := a.Lerp(&b, x)
|
||||
|
||||
evaluateVector(fmt.Sprintf("linear interpolation between %s and %s by %.2f", a, b, x), want, *got, t)
|
||||
}
|
||||
|
||||
func TestDot(t *testing.T) {
|
||||
v := NewVector(1, 1)
|
||||
c := v.Clone()
|
||||
want := 2.0
|
||||
got := v.Dot(&v)
|
||||
|
||||
d := fmt.Sprintf("dot product of %s", c)
|
||||
|
||||
evaluateChanged(d, v, c, t)
|
||||
|
||||
evaluateScalar(d, want, got, t)
|
||||
}
|
||||
|
||||
func TestCross(t *testing.T) {
|
||||
v := NewVector(1, 1)
|
||||
|
||||
clock := NewVector(1, 0)
|
||||
anti := NewVector(0, 1)
|
||||
|
||||
want := -1.0
|
||||
got := v.Cross(clock)
|
||||
|
||||
evaluateScalar(fmt.Sprintf("cross product of %s and %s", v, clock), want, got, t)
|
||||
|
||||
want = 1.0
|
||||
got = v.Cross(anti)
|
||||
|
||||
evaluateScalar(fmt.Sprintf("cross product of %s and %s", v, anti), want, got, t)
|
||||
}
|
||||
|
||||
func TestNormalize(t *testing.T) {
|
||||
v := NewVector(10, 0)
|
||||
c := v.Clone()
|
||||
want := NewVector(1, 0)
|
||||
|
||||
v.Normalize()
|
||||
|
||||
evaluateVector(fmt.Sprintf("normalize %s", c), want, v, t)
|
||||
|
||||
v = NewVector(0, 10)
|
||||
c = v.Clone()
|
||||
want = NewVector(0, 1)
|
||||
reverse := v.Normalize()
|
||||
|
||||
evaluateVector(fmt.Sprintf("normalize %s", c), want, v, t)
|
||||
|
||||
want = NewVector(0, 10)
|
||||
|
||||
v.Scale(reverse)
|
||||
|
||||
evaluateVector(fmt.Sprintf("reverse normalizing of %s", c), want, v, t)
|
||||
}
|
||||
|
||||
func TestAngle(t *testing.T) {
|
||||
v := NewVector(0, 1)
|
||||
c := v.Clone()
|
||||
other := NewVector(1, 0.3)
|
||||
|
||||
d := fmt.Sprintf("angle from %s to %s", c, other)
|
||||
|
||||
want := 1.2793395323170293
|
||||
got := v.Angle(other)
|
||||
|
||||
evaluateScalar(d, want, got, t)
|
||||
evaluateChanged(d, v, c, t)
|
||||
|
||||
other.Set(-1, 0.3)
|
||||
c = other.Clone()
|
||||
|
||||
d = fmt.Sprintf("angle from %s to %s", c, other)
|
||||
|
||||
got = v.Angle(other)
|
||||
|
||||
evaluateScalar(d, want, got, t)
|
||||
evaluateChanged(d, other, c, t)
|
||||
}
|
||||
|
||||
func TestSignedAngle(t *testing.T) {
|
||||
v := NewVector(0, 1)
|
||||
c := v.Clone()
|
||||
other := NewVector(1, 0.3)
|
||||
want := 1.2793395323170293
|
||||
got := v.SignedAngle(other)
|
||||
|
||||
d := fmt.Sprintf("angle from %s to %s", v, other)
|
||||
evaluateScalar(d, want, got, t)
|
||||
evaluateChanged(d, v, c, t)
|
||||
|
||||
other.Set(-1, 0.3)
|
||||
c = other.Clone()
|
||||
want = 5.0038457214660585
|
||||
got = v.SignedAngle(other)
|
||||
|
||||
d = fmt.Sprintf("angle from %s to %s", v, other)
|
||||
evaluateScalar(d, want, got, t)
|
||||
evaluateChanged(d, other, c, t)
|
||||
}
|
||||
|
||||
func TestReflect(t *testing.T) {
|
||||
rightDown := NewVector(1, -1)
|
||||
up := NewVector(0, 1)
|
||||
|
||||
want := NewVector(1, 1)
|
||||
got := rightDown.Reflect(up)
|
||||
|
||||
evaluateVector(fmt.Sprintf("reflect direction %s off surface with normal %s", rightDown, up), want, *got, t)
|
||||
}
|
||||
|
||||
func TestReflectSurface(t *testing.T) {
|
||||
rightDown := NewVector(1, -1)
|
||||
up := NewVector(0, 1)
|
||||
|
||||
want := NewVector(-1, -1)
|
||||
got := rightDown.ReflectSurface(up)
|
||||
|
||||
evaluateVector(fmt.Sprintf("reflect direction %s off surface with normal %s", rightDown, up), want, *got, t)
|
||||
}
|
||||
|
||||
func TestRotate(t *testing.T) {
|
||||
up := NewVector(0, 1)
|
||||
right := NewVector(1, 0)
|
||||
|
||||
c := right.Clone()
|
||||
angle := -up.SignedAngle(right)
|
||||
want := NewVector(0, 1)
|
||||
got := right.Rotate(angle)
|
||||
|
||||
evaluateVectorApprox(fmt.Sprintf("rotated %s by %.1f", c, angle*d2math.RadToDeg), want, *got, t)
|
||||
|
||||
c = up.Clone()
|
||||
angle -= d2math.RadFull
|
||||
want = NewVector(-1, 0)
|
||||
got = up.Rotate(angle)
|
||||
|
||||
evaluateVectorApprox(fmt.Sprintf("rotated %s by %.1f", c, angle*d2math.RadToDeg), want, *got, t)
|
||||
}
|
||||
|
||||
func TestNinetyAnti(t *testing.T) {
|
||||
v := NewVector(0, 1)
|
||||
c := v.Clone()
|
||||
|
||||
want := NewVector(-1, 0)
|
||||
got := v.NinetyAnti()
|
||||
|
||||
evaluateVector(fmt.Sprintf("rotated %s by 90 degrees clockwise", c), want, *got, t)
|
||||
}
|
||||
|
||||
func TestNinetyClock(t *testing.T) {
|
||||
v := NewVector(0, 1)
|
||||
c := v.Clone()
|
||||
|
||||
want := NewVector(1, 0)
|
||||
v = c.Clone()
|
||||
got := v.NinetyClock()
|
||||
|
||||
evaluateVector(fmt.Sprintf("rotated %s by 90 degrees anti-clockwise", c), want, *got, t)
|
||||
}
|
||||
|
||||
func TestVectorUp(t *testing.T) {
|
||||
got := VectorUp()
|
||||
want := NewVector(0, 1)
|
||||
|
||||
evaluateVector("create normalized vector with up direction", want, got, t)
|
||||
}
|
||||
|
||||
func TestVectorDown(t *testing.T) {
|
||||
got := VectorDown()
|
||||
want := NewVector(0, -1)
|
||||
|
||||
evaluateVector("create normalized vector with down direction", want, got, t)
|
||||
}
|
||||
|
||||
func TestVectorRight(t *testing.T) {
|
||||
got := VectorRight()
|
||||
want := NewVector(1, 0)
|
||||
|
||||
evaluateVector("create normalized vector with right direction", want, got, t)
|
||||
}
|
||||
|
||||
func TestVectorLeft(t *testing.T) {
|
||||
got := VectorLeft()
|
||||
want := NewVector(-1, 0)
|
||||
|
||||
evaluateVector("create normalized vector with left direction", want, got, t)
|
||||
}
|
||||
|
||||
func TestVectorOne(t *testing.T) {
|
||||
got := VectorOne()
|
||||
want := NewVector(1, 1)
|
||||
|
||||
evaluateVector("create vector with X and Y values of 1", want, got, t)
|
||||
}
|
||||
|
||||
func TestVectorZero(t *testing.T) {
|
||||
got := VectorZero()
|
||||
want := NewVector(0, 0)
|
||||
|
||||
evaluateVector("create vector with X and Y values of 0", want, got, t)
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
// Package d2math provides various math-related things
|
||||
// Package d2math provides mathmatical functions not included in Golang's standard math library.
|
||||
package d2math
|
||||
|
67
d2common/d2math/math.go
Normal file
67
d2common/d2math/math.go
Normal file
@ -0,0 +1,67 @@
|
||||
package d2math
|
||||
|
||||
import "math"
|
||||
|
||||
const (
|
||||
// Epsilon is used as the threshold for 'almost equal' operations.
|
||||
Epsilon float64 = 0.0001
|
||||
|
||||
// RadToDeg is used to convert anges in radians to degrees by multiplying the radians by RadToDeg. Similarly,degrees
|
||||
// are converted to radians when dividing by RadToDeg.
|
||||
RadToDeg float64 = 57.29578
|
||||
|
||||
// RadFull is the radian equivalent of 360 degrees.
|
||||
RadFull float64 = 6.283185253783088
|
||||
)
|
||||
|
||||
// CompareFloat64Fuzzy returns an integer between -1 and 1 describing
|
||||
// the comparison of floats a and b. 0 will be returned if the
|
||||
// absolute difference between a and b is less than Epsilon.
|
||||
func CompareFloat64Fuzzy(a, b float64) int {
|
||||
delta := a - b
|
||||
if math.Abs(delta) < Epsilon {
|
||||
return 0
|
||||
}
|
||||
|
||||
if delta > 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// ClampFloat64 returns a clamped to min and max.
|
||||
func ClampFloat64(a, min, max float64) float64 {
|
||||
if a > max {
|
||||
return max
|
||||
} else if a < min {
|
||||
return min
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// Sign returns the sign of a.
|
||||
func Sign(a float64) int {
|
||||
switch {
|
||||
case a < 0:
|
||||
return -1
|
||||
case a > 0:
|
||||
return +1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// Lerp returns the linear interpolation from a to b using interpolator x.
|
||||
func Lerp(a, b, x float64) float64 {
|
||||
return a + x*(b-a)
|
||||
}
|
||||
|
||||
// Unlerp returns the intepolator Lerp would require to return x when given
|
||||
// a and b. The x argument of this function can be thought of as the return
|
||||
// value of lerp. The return value of this function can be used as x in
|
||||
// Lerp.
|
||||
func Unlerp(a, b, x float64) float64 {
|
||||
return (x - a) / (b - a)
|
||||
}
|
113
d2common/d2math/math_test.go
Normal file
113
d2common/d2math/math_test.go
Normal file
@ -0,0 +1,113 @@
|
||||
package d2math
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompareFloat64Fuzzy(t *testing.T) {
|
||||
subEpsilon := Epsilon / 3
|
||||
|
||||
want := 0
|
||||
a, b := 1+subEpsilon, 1.0
|
||||
got := CompareFloat64Fuzzy(a, b)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("compare %.2f and %.2f: wanted %d: got %d", a, b, want, got)
|
||||
}
|
||||
|
||||
want = 1
|
||||
a, b = 2, 1.0
|
||||
got = CompareFloat64Fuzzy(a, b)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("compare %.2f and %.2f: wanted %d: got %d", a, b, want, got)
|
||||
}
|
||||
|
||||
want = -1
|
||||
a, b = -2, 1.0
|
||||
got = CompareFloat64Fuzzy(a, b)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("compare %.2f and %.2f: wanted %d: got %d", a, b, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClampFloat64(t *testing.T) {
|
||||
want := 0.5
|
||||
a := 0.5
|
||||
got := ClampFloat64(a, 0, 1)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("clamped %.2f between 0 and 1: wanted %.2f: got %.2f", a, want, got)
|
||||
}
|
||||
|
||||
want = 0.0
|
||||
a = -1.0
|
||||
got = ClampFloat64(a, 0, 1)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("clamped %.2f between 0 and 1: wanted %.2f: got %.2f", a, want, got)
|
||||
}
|
||||
|
||||
want = 1.0
|
||||
a = 2.0
|
||||
got = ClampFloat64(a, 0, 1)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("clamped %.2f between 0 and 1: wanted %.2f: got %.2f", a, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
want := 1
|
||||
a := 0.5
|
||||
got := Sign(a)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("sign of %.2f: wanted %df: got %d", a, want, got)
|
||||
}
|
||||
|
||||
want = -1
|
||||
a = -3
|
||||
got = Sign(a)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("sign of %.2f: wanted %df: got %d", a, want, got)
|
||||
}
|
||||
|
||||
want = 0
|
||||
a = 0.0
|
||||
got = Sign(a)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("sign of %.2f: wanted %df: got %d", a, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLerp(t *testing.T) {
|
||||
want := 3.0
|
||||
x := 0.3
|
||||
a, b := 0.0, 10.0
|
||||
|
||||
got := Lerp(a, b, x)
|
||||
|
||||
d := "linear interpolation between %.2f and %.2f with interpolator %.2f: wanted %.2f: got %.2f"
|
||||
|
||||
if got != want {
|
||||
t.Errorf(d, a, b, x, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnlerp(t *testing.T) {
|
||||
want := 0.3
|
||||
x := 3.0
|
||||
a, b := 0.0, 10.0
|
||||
|
||||
got := Unlerp(a, b, x)
|
||||
|
||||
d := "find the interpolator of %.2f between %.2f and %.2f: wanted %.2f: got %.2f"
|
||||
|
||||
if got != want {
|
||||
t.Errorf(d, x, a, b, want, got)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user