1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-13 03:00:42 +00:00

mapEntity.Step() benchmark and improvements. (#593)

* Initial test and benchmark for mapEntity.Step().

* Removed `&& !m.hasPath()` from mapEntity.atTarget

* Direction is now updated when the path node changes mid-step and target is updated when path is set.

* Test improvements.

* Deleted old benchmark function and tidying.

* d2math.Abs added.

* Abs benchmark and optimisation.

* Negate and Distance benchmark.

* Length and SetLength benchmark.

* Lerp and Dot benchmark.

* Cross and Normalize benchmark.

* Angle and SignedAngle benchmark.

* Trivial change to Vector.Abs()
This commit is contained in:
danhale-git 2020-07-16 17:06:08 +01:00 committed by GitHub
parent 3bdbd5c358
commit 921d44a70c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 389 additions and 119 deletions

View File

@ -150,18 +150,14 @@ func (v *Vector) DivideScalar(s float64) *Vector {
// Abs sets the vector to it's absolute (positive) equivalent. // Abs sets the vector to it's absolute (positive) equivalent.
func (v *Vector) Abs() *Vector { func (v *Vector) Abs() *Vector {
xm, ym := 1.0, 1.0
if v.x < 0 { if v.x < 0 {
xm = -1 v.x = -v.x
} }
if v.y < 0 { if v.y < 0 {
ym = -1 v.y = -v.y
} }
v.x *= xm
v.y *= ym
return v return v
} }
@ -183,7 +179,8 @@ func (v *Vector) Length() float64 {
return math.Sqrt(v.Dot(v)) return math.Sqrt(v.Dot(v))
} }
// SetLength sets the length of this Vector without changing the direction. // SetLength sets the length of this Vector without changing the direction. The length will be exact within
// d2math.Epsilon. See d2math.EqualsApprox.
func (v *Vector) SetLength(length float64) *Vector { func (v *Vector) SetLength(length float64) *Vector {
v.Normalize() v.Normalize()
v.Scale(length) v.Scale(length)

View File

@ -7,6 +7,19 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
) )
/*func TestMain(m *testing.M) {
setup()
os.Exit(m.Run())
}*/
var outVector Vector
var outFloat float64
/*func setup() {
}*/
// TODO: Remove these evaluate functions. Throwing test errors outside the relevant functions means otherwise handly links to failed tests now point here which is annoying.
func evaluateVector(description string, want, got Vector, t *testing.T) { func evaluateVector(description string, want, got Vector, t *testing.T) {
if !got.Equals(want) { if !got.Equals(want) {
t.Errorf("%s: wanted %s: got %s", description, want, got) t.Errorf("%s: wanted %s: got %s", description, want, got)
@ -236,84 +249,158 @@ func TestScale(t *testing.T) {
evaluateVector(fmt.Sprintf("scale %s by 2", v), want, got, t) evaluateVector(fmt.Sprintf("scale %s by 2", v), want, got, t)
} }
func TestAbs(t *testing.T) { func TestVector_Abs(t *testing.T) {
v := NewVector(-1, 1) v := NewVector(-1, 1)
want := NewVector(1, 1) want := NewVector(1, 1)
got := v.Clone() got := v.Clone()
got.Abs() got.Abs()
evaluateVector(fmt.Sprintf("absolute value of %s", v), want, got, t) if !want.Equals(got) {
t.Errorf("absolute value of %s: want %s: got %s", v, want, got)
}
} }
func TestNegate(t *testing.T) { func BenchmarkVector_Abs(b *testing.B) {
v := NewVector(-1, -1)
for n := 0; n < b.N; n++ {
outVector = *v.Abs()
}
}
func TestVector_Negate(t *testing.T) {
v := NewVector(-1, 1) v := NewVector(-1, 1)
want := NewVector(1, -1) want := NewVector(1, -1)
got := v.Clone() got := v.Clone()
got.Negate() got.Negate()
evaluateVector(fmt.Sprintf("inverse value of %s", v), want, got, t) if !want.Equals(got) {
t.Errorf("inverse value of %s: want %s: got %s", v, want, got)
}
} }
func TestDistance(t *testing.T) { func BenchmarkVector_Negate(b *testing.B) {
v := NewVector(1, 1)
for n := 0; n < b.N; n++ {
outVector = *v.Negate()
}
}
func TestVector_Distance(t *testing.T) {
v := NewVector(1, 3) v := NewVector(1, 3)
other := NewVector(1, -1) other := NewVector(1, -1)
want := 4.0 want := 4.0
c := v.Clone() c := v.Clone()
got := c.Distance(other) got := c.Distance(other)
evaluateScalar(fmt.Sprintf("distance from %s to %s", v, other), want, got, t) if got != want {
t.Errorf("distance from %s to %s: want %.3f: got %.3f", v, other, want, got)
}
} }
func TestLength(t *testing.T) { func BenchmarkVector_Distance(b *testing.B) {
v := NewVector(1, 1)
d := NewVector(2, 2)
for n := 0; n < b.N; n++ {
outFloat = v.Distance(d)
}
}
func TestVector_Length(t *testing.T) {
v := NewVector(2, 0) v := NewVector(2, 0)
c := v.Clone() c := v.Clone()
want := 2.0 want := 2.0
got := v.Length() got := c.Length()
d := fmt.Sprintf("length of %s", c) d := fmt.Sprintf("length of %s", v)
evaluateChanged(d, v, c, t) if !c.Equals(v) {
t.Errorf("%s: changed vector %s to %s unexpectedly", d, v, c)
}
evaluateScalar(d, want, got, t) if got != want {
t.Errorf("%s: want %.3f: got %.3f", d, want, got)
}
} }
func TestSetLength(t *testing.T) { func BenchmarkVector_Length(b *testing.B) {
v := NewVector(1, 1)
for n := 0; n < b.N; n++ {
outFloat = v.Length()
}
}
func TestVector_SetLength(t *testing.T) {
v := NewVector(1, 1) v := NewVector(1, 1)
c := v.Clone() c := v.Clone()
want := 2.0 want := 2.0
got := v.SetLength(want).Length() got := c.SetLength(want).Length()
d := fmt.Sprintf("length of %s", c) if !d2math.EqualsApprox(got, want) {
t.Errorf("set length of %s to %.3f :want %.3f: got %.3f", v, want, want, got)
evaluateScalarApprox(d, want, got, t) }
} }
func TestLerp(t *testing.T) { func BenchmarkVector_SetLength(b *testing.B) {
v := NewVector(1, 1)
for n := 0; n < b.N; n++ {
v.SetLength(5)
}
}
func TestVector_Lerp(t *testing.T) {
a := NewVector(0, 0) a := NewVector(0, 0)
b := NewVector(-20, 10) b := NewVector(-20, 10)
x := 0.3 interp := 0.3
want := NewVector(-6, 3) want := NewVector(-6, 3)
got := a.Lerp(&b, x) got := a.Lerp(&b, interp)
evaluateVector(fmt.Sprintf("linear interpolation between %s and %s by %.2f", a, b, x), want, *got, t) if !got.Equals(want) {
t.Errorf("linear interpolation between %s and %s by %.2f: want %s: got %s", a, b, interp, want, got)
}
} }
func TestDot(t *testing.T) { func BenchmarkVector_Lerp(b *testing.B) {
v := NewVector(1, 1)
t := NewVector(1000, 1000)
for n := 0; n < b.N; n++ {
v.Lerp(&t, 1.01)
}
}
func TestVector_Dot(t *testing.T) {
v := NewVector(1, 1) v := NewVector(1, 1)
c := v.Clone() c := v.Clone()
want := 2.0 want := 2.0
got := v.Dot(&v) got := c.Dot(&c)
d := fmt.Sprintf("dot product of %s", c) d := fmt.Sprintf("dot product of %s", v)
evaluateChanged(d, v, c, t) if !c.Equals(v) {
t.Errorf("%s: changed vector %s to %s unexpectedly", d, v, c)
}
evaluateScalar(d, want, got, t) if got != want {
t.Errorf("%s: want %.3f: got %.3f", d, want, got)
}
} }
func TestCross(t *testing.T) { func BenchmarkVector_Dot(b *testing.B) {
v := NewVector(1, 1)
for n := 0; n < b.N; n++ {
outFloat = v.Dot(&v)
}
}
func TestVector_Cross(t *testing.T) {
v := NewVector(1, 1) v := NewVector(1, 1)
clock := NewVector(1, 0) clock := NewVector(1, 0)
@ -322,88 +409,156 @@ func TestCross(t *testing.T) {
want := -1.0 want := -1.0
got := v.Cross(clock) got := v.Cross(clock)
evaluateScalar(fmt.Sprintf("cross product of %s and %s", v, clock), want, got, t) if got != want {
t.Errorf("cross product of %s and %s: want %.3f: got %.3f", v, clock, want, got)
}
want = 1.0 want = 1.0
got = v.Cross(anti) got = v.Cross(anti)
evaluateScalar(fmt.Sprintf("cross product of %s and %s", v, anti), want, got, t) if got != want {
t.Errorf("cross product of %s and %s: want %.3f: got %.3f", v, clock, want, got)
}
} }
func TestNormalize(t *testing.T) { func BenchmarkVector_Cross(b *testing.B) {
v := NewVector(1, 1)
o := NewVector(0, 1)
for n := 0; n < b.N; n++ {
outFloat = v.Cross(o)
}
}
func TestVector_Normalize(t *testing.T) {
v := NewVector(10, 0) v := NewVector(10, 0)
c := v.Clone() c := v.Clone()
want := NewVector(1, 0) want := NewVector(1, 0)
v.Normalize() c.Normalize()
evaluateVector(fmt.Sprintf("normalize %s", c), want, v, t) if !want.Equals(c) {
t.Errorf("normalize %s: want %s: got %s", v, want, c)
}
v = NewVector(0, 10) v = NewVector(0, 10)
c = v.Clone() c = v.Clone()
want = NewVector(0, 1) want = NewVector(0, 1)
reverse := v.Normalize() reverse := c.Normalize()
evaluateVector(fmt.Sprintf("normalize %s", c), want, v, t) if !want.Equals(c) {
t.Errorf("normalize %s: want %s: got %s", v, want, c)
}
want = NewVector(0, 10) want = NewVector(0, 10)
v.Scale(reverse) c.Scale(reverse)
evaluateVector(fmt.Sprintf("reverse normalizing of %s", c), want, v, t) if !want.Equals(c) {
t.Errorf("reverse normalizing of %s: want %s: got %s", v, want, c)
}
v = NewVector(0, 0) v = NewVector(0, 0)
c = v.Clone() c = v.Clone()
want = NewVector(0, 0) want = NewVector(0, 0)
v.Normalize() c.Normalize()
evaluateVector(fmt.Sprintf("normalize zero vector should do nothing %s", c), want, v, t) if !want.Equals(c) {
t.Errorf("normalize zero vector %s should do nothing: want %s: got %s", v, want, c)
}
} }
func TestAngle(t *testing.T) { func BenchmarkVector_Normalize(b *testing.B) {
v := NewVector(1, 1)
for n := 0; n < b.N; n++ {
v.Normalize()
}
}
func TestVector_Angle(t *testing.T) {
v := NewVector(0, 1) v := NewVector(0, 1)
c := v.Clone() c := v.Clone()
other := NewVector(1, 0.3) other := NewVector(1, 0.3)
d := fmt.Sprintf("angle from %s to %s", c, other)
want := 1.2793395323170293 want := 1.2793395323170293
got := v.Angle(other) got := c.Angle(other)
evaluateScalar(d, want, got, t) d := fmt.Sprintf("angle from %s to %s", v, other)
evaluateChanged(d, v, c, t)
if got != want {
t.Errorf("%s: want %g: got %g", d, want, got)
}
if !c.Equals(v) {
t.Errorf("%s: changed vector %s to %s unexpectedly", d, v, c)
}
other.Set(-1, 0.3) other.Set(-1, 0.3)
c = other.Clone() co := other.Clone()
got = c.Angle(other)
d = fmt.Sprintf("angle from %s to %s", c, other) d = fmt.Sprintf("angle from %s to %s", c, other)
got = v.Angle(other) if got != want {
t.Errorf("%s: want %g: got %g", d, want, got)
}
evaluateScalar(d, want, got, t) if !co.Equals(other) {
evaluateChanged(d, other, c, t) t.Errorf("%s: changed vector %s to %s unexpectedly", d, co, other)
}
} }
func TestSignedAngle(t *testing.T) { func BenchmarkVector_Angle(b *testing.B) {
v := NewVector(1, 1)
o := NewVector(0, 1)
for n := 0; n < b.N; n++ {
outFloat = v.Angle(o)
}
}
func TestVector_SignedAngle(t *testing.T) {
v := NewVector(0, 1) v := NewVector(0, 1)
c := v.Clone() c := v.Clone()
other := NewVector(1, 0.3) other := NewVector(1, 0.3)
want := 1.2793395323170293 want := 1.2793395323170293
got := v.SignedAngle(other) got := c.SignedAngle(other)
d := fmt.Sprintf("angle from %s to %s", v, other) d := fmt.Sprintf("angle from %s to %s", v, other)
evaluateScalar(d, want, got, t)
evaluateChanged(d, v, c, t) if got != want {
t.Errorf("%s: want %g: got %g", d, want, got)
}
if !c.Equals(v) {
t.Errorf("%s: changed vector %s to %s unexpectedly", d, v, c)
}
other.Set(-1, 0.3) other.Set(-1, 0.3)
c = other.Clone() co := other.Clone()
want = 5.0038457214660585 want = 5.0038457214660585
got = v.SignedAngle(other) got = c.SignedAngle(other)
d = fmt.Sprintf("angle from %s to %s", v, other) d = fmt.Sprintf("angle from %s to %s", v, other)
evaluateScalar(d, want, got, t)
evaluateChanged(d, other, c, t) if got != want {
t.Errorf("%s: want %g: got %g", d, want, got)
}
if !co.Equals(other) {
t.Errorf("%s: changed vector %s to %s unexpectedly", d, co, other)
}
}
func BenchmarkVector_SignedAngle(b *testing.B) {
v := NewVector(1, 1)
o := NewVector(0, 1)
for n := 0; n < b.N; n++ {
outFloat = v.SignedAngle(o)
}
} }
func TestReflect(t *testing.T) { func TestReflect(t *testing.T) {

View File

@ -1,7 +1,5 @@
package d2math package d2math
import "math"
const ( const (
// Epsilon is used as the threshold for 'almost equal' operations. // Epsilon is used as the threshold for 'almost equal' operations.
Epsilon float64 = 0.0001 Epsilon float64 = 0.0001
@ -16,7 +14,7 @@ const (
// EqualsApprox returns true if the difference between a and b is less than Epsilon. // EqualsApprox returns true if the difference between a and b is less than Epsilon.
func EqualsApprox(a, b float64) bool { func EqualsApprox(a, b float64) bool {
return math.Abs(a-b) < Epsilon return Abs(a-b) < Epsilon
} }
// CompareFloat64Fuzzy returns an integer between -1 and 1 describing // CompareFloat64Fuzzy returns an integer between -1 and 1 describing
@ -24,7 +22,8 @@ func EqualsApprox(a, b float64) bool {
// absolute difference between a and b is less than Epsilon. // absolute difference between a and b is less than Epsilon.
func CompareFloat64Fuzzy(a, b float64) int { func CompareFloat64Fuzzy(a, b float64) int {
delta := a - b delta := a - b
if math.Abs(delta) < Epsilon {
if Abs(delta) < Epsilon {
return 0 return 0
} }
@ -35,6 +34,15 @@ func CompareFloat64Fuzzy(a, b float64) int {
return -1 return -1
} }
// Abs returns the absolute value of a. It is a less CPU intensive version of the standard library math.Abs().
func Abs(a float64) float64 {
if a < 0 {
return a * -1
}
return a
}
// ClampFloat64 returns a clamped to min and max. // ClampFloat64 returns a clamped to min and max.
func ClampFloat64(a, min, max float64) float64 { func ClampFloat64(a, min, max float64) float64 {
if a > max { if a > max {

View File

@ -17,7 +17,7 @@ type AnimatedEntity struct {
// CreateAnimatedEntity creates an instance of AnimatedEntity // CreateAnimatedEntity creates an instance of AnimatedEntity
func CreateAnimatedEntity(x, y int, animation d2interface.Animation) *AnimatedEntity { func CreateAnimatedEntity(x, y int, animation d2interface.Animation) *AnimatedEntity {
entity := &AnimatedEntity{ entity := &AnimatedEntity{
mapEntity: createMapEntity(x, y), mapEntity: newMapEntity(x, y),
animation: animation, animation: animation,
} }
entity.mapEntity.directioner = entity.rotate entity.mapEntity.directioner = entity.rotate
@ -30,7 +30,7 @@ func (ae *AnimatedEntity) Render(target d2interface.Surface) {
renderOffset := ae.Position.RenderOffset() renderOffset := ae.Position.RenderOffset()
target.PushTranslation( target.PushTranslation(
int((renderOffset.X()-renderOffset.Y())*16), int((renderOffset.X()-renderOffset.Y())*16),
int(((renderOffset.X() + renderOffset.Y()) * 8)), int(((renderOffset.X()+renderOffset.Y())*8)-5),
) )
defer target.Pop() defer target.Pop()

View File

@ -11,6 +11,7 @@ import (
type mapEntity struct { type mapEntity struct {
Position d2vector.Position Position d2vector.Position
Target d2vector.Position Target d2vector.Position
velocity d2vector.Vector
Speed float64 Speed float64
path []d2astar.Pather path []d2astar.Pather
@ -20,8 +21,8 @@ type mapEntity struct {
directioner func(direction int) directioner func(direction int)
} }
// createMapEntity creates an instance of mapEntity // newMapEntity creates an instance of mapEntity
func createMapEntity(x, y int) mapEntity { func newMapEntity(x, y int) mapEntity {
locX, locY := float64(x), float64(y) locX, locY := float64(x), float64(y)
return mapEntity{ return mapEntity{
@ -43,6 +44,7 @@ func (m *mapEntity) GetLayer() int {
func (m *mapEntity) SetPath(path []d2astar.Pather, done func()) { func (m *mapEntity) SetPath(path []d2astar.Pather, done func()) {
m.path = path m.path = path
m.done = done m.done = done
m.nextPath()
} }
// ClearPath clears the entity movement path. // ClearPath clears the entity movement path.
@ -60,14 +62,9 @@ func (m *mapEntity) GetSpeed() float64 {
return m.Speed return m.Speed
} }
// IsAtTarget returns true if the distance between entity and target is almost zero.
func (m *mapEntity) IsAtTarget() bool {
return m.Position.EqualsApprox(m.Target.Vector) && !m.HasPathFinding()
}
// Step moves the entity along it's path by one tick. If the path is complete it calls entity.done() then returns. // Step moves the entity along it's path by one tick. If the path is complete it calls entity.done() then returns.
func (m *mapEntity) Step(tickTime float64) { func (m *mapEntity) Step(tickTime float64) {
if m.IsAtTarget() { if m.atTarget() && !m.hasPath() {
if m.done != nil { if m.done != nil {
m.done() m.done()
m.done = nil m.done = nil
@ -76,47 +73,39 @@ func (m *mapEntity) Step(tickTime float64) {
return return
} }
velocity := m.velocity(tickTime) // Set velocity (speed and direction)
m.setVelocity(tickTime * m.Speed)
// This loop handles the situation where the velocity exceeds the distance to the current target. Each repitition applies
// the remaining velocity in the direction of the next path target.
for { for {
// Add the velocity to the position and set new velocity to remainder applyVelocity(&m.Position.Vector, &m.velocity, &m.Target.Vector)
applyVelocity(&m.Position.Vector, &velocity, &m.Target.Vector)
// New position is at target if m.atTarget() {
if m.Position.EqualsApprox(m.Target.Vector) { m.nextPath()
if len(m.path) > 0 {
// Set next path node
m.SetTarget(m.path[0].(*d2common.PathTile).X*5, m.path[0].(*d2common.PathTile).Y*5, m.done)
if len(m.path) > 1 {
m.path = m.path[1:]
} else {
m.path = []d2astar.Pather{}
}
} else {
// End of path.
m.Position.Copy(&m.Target.Vector)
}
} }
if velocity.IsZero() { if m.velocity.IsZero() {
break break
} }
} }
} }
// velocity returns a vector describing the change in position this tick. // atTarget returns true if the distance between entity and target is almost zero.
func (m *mapEntity) velocity(tickTime float64) d2vector.Vector { func (m *mapEntity) atTarget() bool {
length := tickTime * m.Speed return m.Position.EqualsApprox(m.Target.Vector)
v := m.Target.Vector.Clone()
v.Subtract(&m.Position.Vector)
v.SetLength(length)
return v
} }
// applyVelocity adds velocity to position. If the new position extends beyond target from the original position, the // setVelocity returns a vector describing the given length and the direction to the current target.
// position is set to the target and velocity is set to the overshot amount. func (m *mapEntity) setVelocity(length float64) {
m.velocity.Copy(&m.Target.Vector)
m.velocity.Subtract(&m.Position.Vector)
m.velocity.SetLength(length)
}
// applyVelocity adds velocity to position. If the new position extends beyond the target: Target is set to the next
// path node, Position is set to target and velocity is set to the over-extended length with the direction of to the
// next node.
func applyVelocity(position, velocity, target *d2vector.Vector) { func applyVelocity(position, velocity, target *d2vector.Vector) {
// Set velocity values to zero if almost zero // Set velocity values to zero if almost zero
x, y := position.CompareApprox(*target) x, y := position.CompareApprox(*target)
@ -149,21 +138,49 @@ func applyVelocity(position, velocity, target *d2vector.Vector) {
} }
} }
// HasPathFinding returns false if the length of the entity movement path is 0. // Returns false if the path has ended.
func (m *mapEntity) HasPathFinding() bool { func (m *mapEntity) nextPath() {
if m.hasPath() {
// Set next path node
m.setTarget(
m.path[0].(*d2common.PathTile).X*5,
m.path[0].(*d2common.PathTile).Y*5,
m.done,
)
if len(m.path) > 1 {
m.path = m.path[1:]
} else {
m.path = []d2astar.Pather{}
}
} else {
// End of path.
m.Position.Copy(&m.Target.Vector)
}
}
// hasPath returns false if the length of the entity movement path is 0.
func (m *mapEntity) hasPath() bool {
return len(m.path) > 0 return len(m.path) > 0
} }
// SetTarget sets target coordinates and changes animation based on proximity and direction. // setTarget sets target coordinates and changes animation based on proximity and direction.
func (m *mapEntity) SetTarget(tx, ty float64, done func()) { func (m *mapEntity) setTarget(tx, ty float64, done func()) {
// Set the target
m.Target.Set(tx, ty) m.Target.Set(tx, ty)
m.done = done m.done = done
// Update the direction
if m.directioner != nil { if m.directioner != nil {
d := m.Position.DirectionTo(m.Target.Vector) d := m.Position.DirectionTo(m.Target.Vector)
m.directioner(d) m.directioner(d)
} }
// Update the velocity direction
if !m.velocity.IsZero() {
m.setVelocity(m.velocity.Length())
}
} }
// GetPosition returns the entity's current tile position, always a whole number. // GetPosition returns the entity's current tile position, always a whole number.

View File

@ -0,0 +1,93 @@
package d2mapentity
import (
"os"
"testing"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2astar"
)
var stepEntity mapEntity
const (
normalTickTime float64 = 0.05
)
func TestMain(m *testing.M) {
setup()
os.Exit(m.Run())
}
func setup() {
setupBenchmarkMapEntityStep()
}
func entity() mapEntity {
return newMapEntity(10, 10)
}
func movingEntity() mapEntity {
e := entity()
e.SetSpeed(9)
newPath := path(10, e.Position)
e.SetPath(newPath, func() {})
return e
}
func path(length int, origin d2vector.Position) []d2astar.Pather {
path := make([]d2astar.Pather, length)
for i := 0; i < length; i++ {
origin.AddScalar(float64(i+1) / 5)
tile := origin.World()
path[i] = pathTile(tile.X(), tile.Y())
}
return path
}
func pathTile(x, y float64) *d2common.PathTile {
return &d2common.PathTile{X: x, Y: y}
}
func TestMapEntity_Step(t *testing.T) {
stepCount := 10
e := movingEntity()
start := e.Position.Clone()
for i := 0; i < stepCount; i++ {
e.Step(normalTickTime)
}
// velocity
change := d2vector.NewVector(0, 0)
change.Copy(&e.Target.Vector)
change.Subtract(&e.Position.Vector)
change.SetLength(e.Speed * normalTickTime)
// change in position
change.Scale(float64(stepCount))
want := change.Add(&start)
if !e.Position.EqualsApprox(*want) {
t.Errorf("entity position after %d steps: want %s: got %s", stepCount, want, e.Position.Vector)
}
if e.Position.Equals(start) {
t.Errorf("entity did not move, still at position %s", start)
}
}
func setupBenchmarkMapEntityStep() {
stepEntity = movingEntity()
}
func BenchmarkMapEntity_Step(b *testing.B) {
for n := 0; n < b.N; n++ {
stepEntity.Step(normalTickTime)
}
}

View File

@ -54,7 +54,7 @@ func (m *Missile) SetRadians(angle float64, done func()) {
x := m.Position.X() + (r * math.Cos(angle)) x := m.Position.X() + (r * math.Cos(angle))
y := m.Position.Y() + (r * math.Sin(angle)) y := m.Position.Y() + (r * math.Sin(angle))
m.SetTarget(x, y, done) m.setTarget(x, y, done)
} }
// Advance is called once per frame and processes a // Advance is called once per frame and processes a

View File

@ -31,7 +31,7 @@ type NPC struct {
// CreateNPC creates a new NPC and returns a pointer to it. // CreateNPC creates a new NPC and returns a pointer to it.
func CreateNPC(x, y int, monstat *d2datadict.MonStatsRecord, direction int) *NPC { func CreateNPC(x, y int, monstat *d2datadict.MonStatsRecord, direction int) *NPC {
result := &NPC{ result := &NPC{
mapEntity: createMapEntity(x, y), mapEntity: newMapEntity(x, y),
HasPaths: false, HasPaths: false,
monstatRecord: monstat, monstatRecord: monstat,
monstatEx: d2datadict.MonStats2[monstat.ExtraDataKey], monstatEx: d2datadict.MonStats2[monstat.ExtraDataKey],
@ -75,7 +75,7 @@ func (v *NPC) Render(target d2interface.Surface) {
renderOffset := v.Position.RenderOffset() renderOffset := v.Position.RenderOffset()
target.PushTranslation( target.PushTranslation(
int((renderOffset.X()-renderOffset.Y())*16), int((renderOffset.X()-renderOffset.Y())*16),
int(((renderOffset.X() + renderOffset.Y()) * 8)), int(((renderOffset.X()+renderOffset.Y())*8)-5),
) )
defer target.Pop() defer target.Pop()
@ -116,7 +116,7 @@ func (v *NPC) Advance(tickTime float64) {
// If at the target, set target to the next path. // If at the target, set target to the next path.
v.isDone = false v.isDone = false
path := v.NextPath() path := v.NextPath()
v.SetTarget( v.setTarget(
float64(path.X), float64(path.X),
float64(path.Y), float64(path.Y),
v.next, v.next,
@ -159,7 +159,7 @@ func (v *NPC) next() {
// rotate sets direction and changes animation // rotate sets direction and changes animation
func (v *NPC) rotate(direction int) { func (v *NPC) rotate(direction int) {
var newMode d2enum.MonsterAnimationMode var newMode d2enum.MonsterAnimationMode
if !v.IsAtTarget() { if !v.atTarget() {
newMode = d2enum.MonsterAnimationModeWalk newMode = d2enum.MonsterAnimationModeWalk
} else { } else {
newMode = d2enum.MonsterAnimationModeNeutral newMode = d2enum.MonsterAnimationModeNeutral

View File

@ -57,7 +57,7 @@ func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero
result := &Player{ result := &Player{
Id: id, Id: id,
mapEntity: createMapEntity(x, y), mapEntity: newMapEntity(x, y),
composite: composite, composite: composite,
Equipment: equipment, Equipment: equipment,
Stats: stats, Stats: stats,
@ -147,7 +147,7 @@ func (v *Player) Render(target d2interface.Surface) {
renderOffset := v.Position.RenderOffset() renderOffset := v.Position.RenderOffset()
target.PushTranslation( target.PushTranslation(
int((renderOffset.X()-renderOffset.Y())*16), int((renderOffset.X()-renderOffset.Y())*16),
int(((renderOffset.X() + renderOffset.Y()) * 8)), int(((renderOffset.X()+renderOffset.Y())*8)-5),
) )
defer target.Pop() defer target.Pop()
@ -159,19 +159,19 @@ func (v *Player) Render(target d2interface.Surface) {
// GetAnimationMode returns the current animation mode based on what the player is doing and where they are. // GetAnimationMode returns the current animation mode based on what the player is doing and where they are.
func (v *Player) GetAnimationMode() d2enum.PlayerAnimationMode { func (v *Player) GetAnimationMode() d2enum.PlayerAnimationMode {
if v.IsRunning() && !v.IsAtTarget() { if v.IsRunning() && !v.atTarget() {
return d2enum.PlayerAnimationModeRun return d2enum.PlayerAnimationModeRun
} }
if v.IsInTown() { if v.IsInTown() {
if !v.IsAtTarget() { if !v.atTarget() {
return d2enum.PlayerAnimationModeTownWalk return d2enum.PlayerAnimationModeTownWalk
} }
return d2enum.PlayerAnimationModeTownNeutral return d2enum.PlayerAnimationModeTownNeutral
} }
if !v.IsAtTarget() { if !v.atTarget() {
return d2enum.PlayerAnimationModeWalk return d2enum.PlayerAnimationModeWalk
} }