Vector method pointers (#621)

* All Vector methods which operate on the vector return pointers to it.

* All Vector methods which take vectors take Vector pointers.
This commit is contained in:
danhale-git 2020-07-25 14:36:54 +01:00 committed by GitHub
parent 89b031d2b7
commit 259c6e7e76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 88 additions and 95 deletions

View File

@ -1,5 +1,5 @@
// Package d2vector provides an implementation of a 2D Euclidean vector using float64 to store the two values.
/*
Vector uses d2math.Epsilon for approximate equality and comparison. Note: SetLength and Rotate do not (per the unit
tests) return exact values but ones within Epsilon range of the expected value.*/
Vector uses d2math.Epsilon for approximate equality and comparison. Note: SetLength, Reflect, ReflectSurface and Rotate
do not (per their unit tests) return exact values but ones within Epsilon range of the expected value.*/
package d2vector

View File

@ -22,15 +22,15 @@ type Position struct {
// NewPosition returns a Position struct with the given sub tile coordinates where 1 = 1 sub tile, with a fractional
// offset.
func NewPosition(x, y float64) Position {
p := Position{NewVector(x, y)}
p := Position{*NewVector(x, y)}
p.checkValues()
return p
}
// NewPosition returns a Position struct with the given tile coordinates where 1 = 1 tile, with a fractional offset.
// NewPositionTile returns a Position struct with the given tile coordinates where 1 = 1 tile, with a fractional offset.
func NewPositionTile(x, y float64) Position {
p := Position{NewVector(x*subTilesPerTile, y*subTilesPerTile)}
p := Position{*NewVector(x*subTilesPerTile, y*subTilesPerTile)}
p.checkValues()
return p

View File

@ -42,11 +42,11 @@ func TestNewPosition(t *testing.T) {
}
func validate(description string, t *testing.T, original, got, want, unchanged Vector) {
if !got.EqualsApprox(want) {
if !got.EqualsApprox(&want) {
t.Errorf("%s: want %s: got %s", description, want, got)
}
if !original.EqualsApprox(unchanged) {
if !original.EqualsApprox(&unchanged) {
t.Errorf("Position value %s was incorrectly changed to %s when calling this method", unchanged, original)
}
}
@ -57,7 +57,7 @@ func TestPosition_World(t *testing.T) {
got := p.World()
want := NewVector(1, 2)
validate("world position", t, p.Vector, *got, want, unchanged)
validate("world position", t, p.Vector, *got, *want, *unchanged)
}
func TestPosition_Tile(t *testing.T) {
@ -66,7 +66,7 @@ func TestPosition_Tile(t *testing.T) {
got := p.Tile()
want := NewVector(4, 4)
validate("tile position", t, p.Vector, *got, want, unchanged)
validate("tile position", t, p.Vector, *got, *want, *unchanged)
}
func TestPosition_RenderOffset(t *testing.T) {
@ -75,5 +75,5 @@ func TestPosition_RenderOffset(t *testing.T) {
got := p.RenderOffset()
want := NewVector(3.1, 5.2)
validate("offset from sub tile", t, p.Vector, *got, want, unchanged)
validate("offset from sub tile", t, p.Vector, *got, *want, *unchanged)
}

View File

@ -15,8 +15,8 @@ type Vector struct {
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}
func NewVector(x, y float64) *Vector {
return &Vector{x, y}
}
// X returns the x value of this vector.
@ -30,19 +30,19 @@ func (v *Vector) Y() float64 {
}
// Equals returns true if the float64 values of this vector are exactly equal to the given Vector.
func (v *Vector) Equals(o Vector) bool {
func (v *Vector) Equals(o *Vector) bool {
return v.x == o.x && v.y == o.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 {
func (v *Vector) EqualsApprox(o *Vector) bool {
return d2math.EqualsApprox(v.x, o.x) && d2math.EqualsApprox(v.y, o.y)
}
// 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) {
func (v *Vector) CompareApprox(o *Vector) (x, y int) {
return d2math.CompareApprox(v.x, o.x),
d2math.CompareApprox(v.y, o.y)
}
@ -61,7 +61,7 @@ func (v *Vector) Set(x, y float64) *Vector {
}
// Clone returns a new a copy of this Vector.
func (v *Vector) Clone() Vector {
func (v *Vector) Clone() *Vector {
return NewVector(v.x, v.y)
}
@ -165,7 +165,7 @@ func (v *Vector) Negate() *Vector {
}
// Distance between this Vector's position and that of the given Vector.
func (v *Vector) Distance(o Vector) float64 {
func (v *Vector) Distance(o *Vector) float64 {
delta := o.Clone()
delta.Subtract(v)
@ -178,7 +178,7 @@ func (v *Vector) Length() float64 {
}
// SetLength sets the length of this Vector without changing the direction. The length will be exact within
// d2math.Epsilon. See d2math.EqualsApprox.
// d2math.Epsilon.
func (v *Vector) SetLength(length float64) *Vector {
v.Normalize()
v.Scale(length)
@ -212,26 +212,25 @@ func (v *Vector) Dot(o *Vector) float64 {
// Negative = clockwise
// Positive = anti-clockwise
// 0 = vectors are identical.
func (v *Vector) Cross(o Vector) float64 {
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 {
func (v *Vector) Normalize() *Vector {
if v.IsZero() {
return 0
return v
}
multiplier := 1 / v.Length()
v.Scale(multiplier)
v.Scale(1 / v.Length())
return 1 / multiplier
return v
}
// 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 {
func (v *Vector) Angle(o *Vector) float64 {
if v.IsZero() || o.IsZero() {
return 0
}
@ -243,13 +242,13 @@ func (v *Vector) Angle(o Vector) float64 {
to.Normalize()
denominator := math.Sqrt(from.Length() * to.Length())
dotClamped := d2math.Clamp(from.Dot(&to)/denominator, -1, 1)
dotClamped := d2math.Clamp(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 {
func (v *Vector) SignedAngle(o *Vector) float64 {
unsigned := v.Angle(o)
sign := d2math.Sign(v.x*o.y - v.y*o.x)
@ -260,22 +259,22 @@ func (v *Vector) SignedAngle(o Vector) float64 {
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 {
// Reflect sets this Vector to it's reflection off a line defined by the given normal. The result will be exact within
// d2math.Epsilon.
func (v *Vector) Reflect(normal *Vector) *Vector {
normal.Normalize()
undo := v.Normalize()
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)
normal.Scale(two * v.Dot(normal))
v.Subtract(normal)
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 {
func (v *Vector) ReflectSurface(surface *Vector) *Vector {
v.Reflect(surface).Negate()
return v
@ -316,31 +315,31 @@ func (v Vector) String() string {
}
// VectorUp returns a new vector (0, 1)
func VectorUp() Vector {
func VectorUp() *Vector {
return NewVector(0, 1)
}
// VectorDown returns a new vector (0, -1)
func VectorDown() Vector {
func VectorDown() *Vector {
return NewVector(0, -1)
}
// VectorRight returns a new vector (1, 0)
func VectorRight() Vector {
func VectorRight() *Vector {
return NewVector(1, 0)
}
// VectorLeft returns a new vector (-1, 0)
func VectorLeft() Vector {
func VectorLeft() *Vector {
return NewVector(-1, 0)
}
// VectorOne returns a new vector (1, 1)
func VectorOne() Vector {
func VectorOne() *Vector {
return NewVector(1, 1)
}
// VectorZero returns a new vector (0, 0)
func VectorZero() Vector {
func VectorZero() *Vector {
return NewVector(0, 0)
}

View File

@ -116,10 +116,10 @@ func BenchmarkVector_CompareApprox(b *testing.B) {
}
func TestVector_IsZero(t *testing.T) {
testIsZero(NewVector(0, 0), true, t)
testIsZero(NewVector(1, 0), false, t)
testIsZero(NewVector(0, 1), false, t)
testIsZero(NewVector(1, 1), false, t)
testIsZero(*NewVector(0, 0), true, t)
testIsZero(*NewVector(1, 0), false, t)
testIsZero(*NewVector(0, 1), false, t)
testIsZero(*NewVector(1, 1), false, t)
}
func testIsZero(v Vector, want bool, t *testing.T) {
@ -169,14 +169,14 @@ func BenchmarkVector_Clone(b *testing.B) {
v := NewVector(1, 1)
for n := 0; n < b.N; n++ {
outVector = v.Clone()
outVector = *v.Clone()
}
}
func TestVector_Copy(t *testing.T) {
want := NewVector(1, 2)
got := NewVector(0, 0)
got.Copy(&want)
got.Copy(want)
if !got.Equals(want) {
t.Errorf("copy %s to %s: want %s: got %s", got, want, want, got)
@ -188,7 +188,7 @@ func BenchmarkVector_Copy(b *testing.B) {
o := NewVector(2, 2)
for n := 0; n < b.N; n++ {
v.Copy(&o)
v.Copy(o)
}
}
@ -218,7 +218,7 @@ func TestVector_Clamp(t *testing.T) {
b := NewVector(7, 7)
want := NewVector(2, 7)
got := v.Clamp(&a, &b)
got := v.Clamp(a, b)
if !got.Equals(want) {
t.Errorf("clamp %s between %s and %s: want %s: got %s", c, a, b, want, *got)
@ -231,7 +231,7 @@ func BenchmarkVector_Clamp(b *testing.B) {
max := NewVector(1, 1)
for n := 0; n < b.N; n++ {
v.Clamp(&min, &max)
v.Clamp(min, max)
}
}
@ -240,7 +240,7 @@ func TestVector_Add(t *testing.T) {
add := NewVector(0.5, 3)
want := NewVector(1.5, 5)
got := v.Clone()
got.Add(&add)
got.Add(add)
if !got.Equals(want) {
t.Errorf("add %s to %s: want %s: got %s", add, v, want, got)
@ -252,7 +252,7 @@ func BenchmarkVector_Add(b *testing.B) {
o := NewVector(0.01, 0.01)
for n := 0; n < b.N; n++ {
v.Add(&o)
v.Add(o)
}
}
@ -281,7 +281,7 @@ func TestVector_Subtract(t *testing.T) {
subtract := NewVector(0.6, 0.6)
want := NewVector(0.4, 0.4)
got := v.Clone()
got.Subtract(&subtract)
got.Subtract(subtract)
if !got.Equals(want) {
t.Errorf("subtract %s from %s: want %s: got %s", subtract, v, want, got)
@ -293,7 +293,7 @@ func BenchmarkVector_Subtract(b *testing.B) {
o := NewVector(0.01, 0.01)
for n := 0; n < b.N; n++ {
v.Subtract(&o)
v.Subtract(o)
}
}
@ -302,7 +302,7 @@ func TestVector_Multiply(t *testing.T) {
multiply := NewVector(2, 2)
want := NewVector(2, 2)
got := v.Clone()
got.Multiply(&multiply)
got.Multiply(multiply)
if !got.Equals(want) {
t.Errorf("multiply %s by %s: want %s: got %s", v, multiply, want, got)
@ -314,7 +314,7 @@ func BenchmarkVector_Multiply(b *testing.B) {
o := NewVector(0.01, 0.01)
for n := 0; n < b.N; n++ {
v.Multiply(&o)
v.Multiply(o)
}
}
@ -323,7 +323,7 @@ func TestVector_Divide(t *testing.T) {
divide := NewVector(2, 4)
want := NewVector(0.5, 2)
got := v.Clone()
got.Divide(&divide)
got.Divide(divide)
if !got.Equals(want) {
t.Errorf("divide %s by %s: want %s: got %s", v, divide, want, got)
@ -335,7 +335,7 @@ func BenchmarkVector_Divide(b *testing.B) {
o := NewVector(3, 3)
for n := 0; n < b.N; n++ {
v.Divide(&o)
v.Divide(o)
}
}
@ -488,7 +488,7 @@ func TestVector_Lerp(t *testing.T) {
interp := 0.3
want := NewVector(-6, 3)
got := a.Lerp(&b, interp)
got := a.Lerp(b, interp)
if !got.Equals(want) {
t.Errorf("linear interpolation between %s and %s by %.2f: want %s: got %s", a, b, interp, want, got)
@ -500,7 +500,7 @@ func BenchmarkVector_Lerp(b *testing.B) {
t := NewVector(1000, 1000)
for n := 0; n < b.N; n++ {
v.Lerp(&t, 1.01)
v.Lerp(t, 1.01)
}
}
@ -508,7 +508,7 @@ func TestVector_Dot(t *testing.T) {
v := NewVector(1, 1)
c := v.Clone()
want := 2.0
got := c.Dot(&c)
got := c.Dot(c)
d := fmt.Sprintf("dot product of %s", v)
@ -525,7 +525,7 @@ func BenchmarkVector_Dot(b *testing.B) {
v := NewVector(1, 1)
for n := 0; n < b.N; n++ {
outFloat = v.Dot(&v)
outFloat = v.Dot(v)
}
}
@ -573,20 +573,12 @@ func TestVector_Normalize(t *testing.T) {
v = NewVector(0, 10)
c = v.Clone()
want = NewVector(0, 1)
reverse := c.Normalize()
c.Normalize()
if !want.Equals(c) {
t.Errorf("normalize %s: want %s: got %s", v, want, c)
}
want = NewVector(0, 10)
c.Scale(reverse)
if !want.Equals(c) {
t.Errorf("reverse normalizing of %s: want %s: got %s", v, want, c)
}
v = NewVector(0, 0)
c = v.Clone()
want = NewVector(0, 0)
@ -602,7 +594,7 @@ func BenchmarkVector_Normalize(b *testing.B) {
v := NewVector(1, 1)
for n := 0; n < b.N; n++ {
outFloat = v.Normalize()
outVector = *v.Normalize()
}
}
@ -696,6 +688,7 @@ func TestVector_Reflect(t *testing.T) {
up := NewVector(0, 1)
want := NewVector(1, 1)
want.Normalize()
v.Reflect(up)
@ -719,6 +712,7 @@ func TestVector_ReflectSurface(t *testing.T) {
up := NewVector(0, 1)
want := NewVector(-1, -1)
want.Normalize()
v.ReflectSurface(up)
@ -745,7 +739,7 @@ func TestVector_Rotate(t *testing.T) {
want := NewVector(0, 1)
got := right.Rotate(angle)
if !want.EqualsApprox(*got) {
if !want.EqualsApprox(got) {
t.Errorf("rotated %s by %.1f: want %s: got %s", c, angle*d2math.RadToDeg, want, got)
}
@ -754,7 +748,7 @@ func TestVector_Rotate(t *testing.T) {
want = NewVector(-1, 0)
got = up.Rotate(angle)
if !want.EqualsApprox(*got) {
if !want.EqualsApprox(got) {
t.Errorf("rotated %s by %.1f: want %s: got %s", c, angle*d2math.RadToDeg, want, got)
}
}
@ -775,7 +769,7 @@ func TestVector_NinetyAnti(t *testing.T) {
want := NewVector(-1, 0)
got := v.NinetyAnti()
if !want.Equals(*got) {
if !want.Equals(got) {
t.Errorf("rotated %s by 90 degrees clockwise: want %s: got %s", c, want, *got)
}
}
@ -796,7 +790,7 @@ func TestVector_NinetyClock(t *testing.T) {
v = c.Clone()
got := v.NinetyClock()
if !want.Equals(*got) {
if !want.Equals(got) {
t.Errorf("rotated %s by 90 degrees anti-clockwise: want %s: got %s", c, want, *got)
}
}

View File

@ -17,7 +17,7 @@ func (m *MapEngine) PathFind(start, dest d2vector.Position) []d2vector.Position
// checkLos finds out if there is a clear line of sight between two points
func (m *MapEngine) checkLos(start, end d2vector.Position) (bool, d2vector.Position) {
dv := d2vector.Position{Vector: end.Clone()}
dv := d2vector.Position{Vector: *end.Clone()}
dv.Subtract(&start.Vector)
dx := dv.X()
dy := dv.Y()

View File

@ -25,7 +25,7 @@ func newMapEntity(x, y int) mapEntity {
return mapEntity{
Position: pos,
Target: pos,
velocity: d2vector.NewVector(0, 0),
velocity: *d2vector.VectorZero(),
}
}
@ -75,7 +75,7 @@ func (m *mapEntity) Step(tickTime float64) {
v := m.velocity.Clone() // Create a new vector
for {
applyVelocity(&m.Position.Vector, &v, &m.Target.Vector) // Pass the new vector to the function which alters it
applyVelocity(&m.Position.Vector, v, &m.Target.Vector) // Pass the new vector to the function which alters it
if m.atTarget() {
m.nextPath()
@ -89,7 +89,7 @@ func (m *mapEntity) Step(tickTime float64) {
// atTarget returns true if the distance between entity and target is almost zero.
func (m *mapEntity) atTarget() bool {
return m.Position.EqualsApprox(m.Target.Vector)
return m.Position.EqualsApprox(&m.Target.Vector)
}
// setVelocity returns a vector describing the given length and the direction to the current target.
@ -104,7 +104,7 @@ func (m *mapEntity) setVelocity(length float64) {
// next node.
func applyVelocity(position, velocity, target *d2vector.Vector) {
// Set velocity values to zero if almost zero
x, y := position.CompareApprox(*target)
x, y := position.CompareApprox(target)
vx, vy := velocity.X(), velocity.Y()
if x == 0 {
@ -121,7 +121,7 @@ func applyVelocity(position, velocity, target *d2vector.Vector) {
dest.Add(velocity)
destDistance := position.Distance(dest)
targetDistance := position.Distance(*target)
targetDistance := position.Distance(target)
if destDistance > targetDistance {
// Destination overshot target. Set position to target and velocity to overshot amount.
@ -129,7 +129,7 @@ func applyVelocity(position, velocity, target *d2vector.Vector) {
velocity.Copy(dest.Subtract(target))
} else {
// At or before target, set position to destination and velocity to zero.
position.Copy(&dest)
position.Copy(dest)
velocity.Set(0, 0)
}
}

View File

@ -68,9 +68,9 @@ func TestMapEntity_Step(t *testing.T) {
// change in position
change.Scale(float64(stepCount))
want := change.Add(&start)
want := change.Add(start)
if !e.Position.EqualsApprox(*want) {
if !e.Position.EqualsApprox(want) {
t.Errorf("entity position after %d steps: want %s: got %s", stepCount, want, e.Position.Vector)
}

View File

@ -27,7 +27,7 @@ func (c *Camera) SetTarget(target *d2vector.Position) {
func (c *Camera) MoveTargetBy(vector *d2vector.Vector) {
if c.target == nil {
v := c.position.Clone()
c.target = &d2vector.Position{Vector: v}
c.target = &d2vector.Position{Vector: *v}
}
c.target.Add(vector)

View File

@ -349,7 +349,7 @@ func (mr *MapRenderer) renderEntityDebug(target d2interface.Surface) {
world := pos
x, y := world.X()/5, world.Y()/5
velocity := e.GetVelocity()
velocity = velocity.Clone()
velocity = *velocity.Clone()
vx, vy := mr.viewport.WorldToOrtho(velocity.X(), velocity.Y())
screenX, screenY := mr.viewport.WorldToScreen(x, y)

View File

@ -140,5 +140,5 @@ func (ob *Object) GetPosition() d2vector.Position {
// GetVelocity returns the object's velocity vector
func (ob *Object) GetVelocity() d2vector.Vector {
return d2vector.NewVector(0, 0)
return *d2vector.VectorZero()
}

View File

@ -379,28 +379,28 @@ func (met *MapEngineTest) OnKeyRepeat(event d2interface.KeyEvent) bool {
if event.Key() == d2enum.KeyDown {
v := d2vector.NewVector(0, moveSpeed)
met.mapRenderer.MoveCameraTargetBy(&v)
met.mapRenderer.MoveCameraTargetBy(v)
return true
}
if event.Key() == d2enum.KeyUp {
v := d2vector.NewVector(0, -moveSpeed)
met.mapRenderer.MoveCameraTargetBy(&v)
met.mapRenderer.MoveCameraTargetBy(v)
return true
}
if event.Key() == d2enum.KeyRight {
v := d2vector.NewVector(moveSpeed, 0)
met.mapRenderer.MoveCameraTargetBy(&v)
met.mapRenderer.MoveCameraTargetBy(v)
return true
}
if event.Key() == d2enum.KeyLeft {
v := d2vector.NewVector(-moveSpeed, 0)
met.mapRenderer.MoveCameraTargetBy(&v)
met.mapRenderer.MoveCameraTargetBy(v)
return true
}

View File

@ -164,28 +164,28 @@ func (g *GameControls) OnKeyRepeat(event d2interface.KeyEvent) bool {
if event.Key() == d2enum.KeyDown {
v := d2vector.NewVector(0, moveSpeed)
g.mapRenderer.MoveCameraTargetBy(&v)
g.mapRenderer.MoveCameraTargetBy(v)
return true
}
if event.Key() == d2enum.KeyUp {
v := d2vector.NewVector(0, -moveSpeed)
g.mapRenderer.MoveCameraTargetBy(&v)
g.mapRenderer.MoveCameraTargetBy(v)
return true
}
if event.Key() == d2enum.KeyRight {
v := d2vector.NewVector(moveSpeed, 0)
g.mapRenderer.MoveCameraTargetBy(&v)
g.mapRenderer.MoveCameraTargetBy(v)
return true
}
if event.Key() == d2enum.KeyLeft {
v := d2vector.NewVector(-moveSpeed, 0)
g.mapRenderer.MoveCameraTargetBy(&v)
g.mapRenderer.MoveCameraTargetBy(v)
return true
}