From 48bde64a7edcc58ccccb787ee67d41c330fe6170 Mon Sep 17 00:00:00 2001 From: dk Date: Thu, 2 Jul 2020 23:26:59 -0700 Subject: [PATCH] vector implementation with big.Float (#527) --- d2common/d2interface/vector.go | 43 ++++ d2common/d2math/vector.go | 367 +++++++++++++++++++++++++++++++++ 2 files changed, 410 insertions(+) create mode 100644 d2common/d2interface/vector.go create mode 100644 d2common/d2math/vector.go diff --git a/d2common/d2interface/vector.go b/d2common/d2interface/vector.go new file mode 100644 index 00000000..574c2e8d --- /dev/null +++ b/d2common/d2interface/vector.go @@ -0,0 +1,43 @@ +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 +} diff --git a/d2common/d2math/vector.go b/d2common/d2math/vector.go new file mode 100644 index 00000000..75f4ca72 --- /dev/null +++ b/d2common/d2math/vector.go @@ -0,0 +1,367 @@ +package d2math + +import ( + "math" + "math/big" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" +) + +// I want to put these somewhere convenient... +// ZERO *Vector = &Vector{} +// ONE *Vector = &Vector{1, 1} +// RIGHT *Vector = &Vector{1, 0} +// LEFT *Vector = &Vector{-1, 0} +// UP *Vector = &Vector{0, -1} +// DOWN *Vector = &Vector{0, 1} + +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 +) + +// NewVector2 creates a new Vector +func NewVector2(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 2-dimensional vectors with +// big.Float components +type Vector2 struct { + x *big.Float + y *big.Float +} + +// X returns the x member of the Vector +func (v *Vector2) X() *big.Float { + return v.x +} + +// Y returns the y member of the Vector +func (v *Vector2) Y() *big.Float { + return v.y +} + +// 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 +} + +// 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 := &Vector2{} + 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 { + 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 +} + +// 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) +} + +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 +} + +// Calculate 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) + + return v +}