mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-14 08:16:06 -05: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:
parent
3bdbd5c358
commit
921d44a70c
@ -150,18 +150,14 @@ func (v *Vector) DivideScalar(s float64) *Vector {
|
||||
|
||||
// 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
|
||||
v.x = -v.x
|
||||
}
|
||||
|
||||
if v.y < 0 {
|
||||
ym = -1
|
||||
v.y = -v.y
|
||||
}
|
||||
|
||||
v.x *= xm
|
||||
v.y *= ym
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
@ -183,7 +179,8 @@ func (v *Vector) Length() float64 {
|
||||
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 {
|
||||
v.Normalize()
|
||||
v.Scale(length)
|
||||
|
@ -7,6 +7,19 @@ import (
|
||||
"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) {
|
||||
if !got.Equals(want) {
|
||||
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)
|
||||
}
|
||||
|
||||
func TestAbs(t *testing.T) {
|
||||
func TestVector_Abs(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)
|
||||
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)
|
||||
want := NewVector(1, -1)
|
||||
got := v.Clone()
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
c := v.Clone()
|
||||
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)
|
||||
c := v.Clone()
|
||||
want := 2.0
|
||||
got := v.SetLength(want).Length()
|
||||
got := c.SetLength(want).Length()
|
||||
|
||||
d := fmt.Sprintf("length of %s", c)
|
||||
|
||||
evaluateScalarApprox(d, want, got, t)
|
||||
if !d2math.EqualsApprox(got, want) {
|
||||
t.Errorf("set length of %s to %.3f :want %.3f: got %.3f", v, want, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
b := NewVector(-20, 10)
|
||||
|
||||
x := 0.3
|
||||
interp := 0.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)
|
||||
c := v.Clone()
|
||||
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)
|
||||
|
||||
clock := NewVector(1, 0)
|
||||
@ -322,88 +409,156 @@ func TestCross(t *testing.T) {
|
||||
want := -1.0
|
||||
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
|
||||
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)
|
||||
c := v.Clone()
|
||||
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)
|
||||
c = v.Clone()
|
||||
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)
|
||||
|
||||
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)
|
||||
c = v.Clone()
|
||||
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)
|
||||
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)
|
||||
got := c.Angle(other)
|
||||
|
||||
evaluateScalar(d, want, got, t)
|
||||
evaluateChanged(d, v, c, t)
|
||||
d := fmt.Sprintf("angle from %s to %s", v, other)
|
||||
|
||||
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)
|
||||
c = other.Clone()
|
||||
co := other.Clone()
|
||||
got = c.Angle(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)
|
||||
evaluateChanged(d, other, c, t)
|
||||
if !co.Equals(other) {
|
||||
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)
|
||||
c := v.Clone()
|
||||
other := NewVector(1, 0.3)
|
||||
want := 1.2793395323170293
|
||||
got := v.SignedAngle(other)
|
||||
got := c.SignedAngle(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)
|
||||
c = other.Clone()
|
||||
co := other.Clone()
|
||||
want = 5.0038457214660585
|
||||
got = v.SignedAngle(other)
|
||||
got = c.SignedAngle(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) {
|
||||
|
@ -1,7 +1,5 @@
|
||||
package d2math
|
||||
|
||||
import "math"
|
||||
|
||||
const (
|
||||
// Epsilon is used as the threshold for 'almost equal' operations.
|
||||
Epsilon float64 = 0.0001
|
||||
@ -16,7 +14,7 @@ const (
|
||||
|
||||
// EqualsApprox returns true if the difference between a and b is less than Epsilon.
|
||||
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
|
||||
@ -24,7 +22,8 @@ func EqualsApprox(a, b float64) bool {
|
||||
// absolute difference between a and b is less than Epsilon.
|
||||
func CompareFloat64Fuzzy(a, b float64) int {
|
||||
delta := a - b
|
||||
if math.Abs(delta) < Epsilon {
|
||||
|
||||
if Abs(delta) < Epsilon {
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -35,6 +34,15 @@ func CompareFloat64Fuzzy(a, b float64) int {
|
||||
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.
|
||||
func ClampFloat64(a, min, max float64) float64 {
|
||||
if a > max {
|
||||
|
@ -17,7 +17,7 @@ type AnimatedEntity struct {
|
||||
// CreateAnimatedEntity creates an instance of AnimatedEntity
|
||||
func CreateAnimatedEntity(x, y int, animation d2interface.Animation) *AnimatedEntity {
|
||||
entity := &AnimatedEntity{
|
||||
mapEntity: createMapEntity(x, y),
|
||||
mapEntity: newMapEntity(x, y),
|
||||
animation: animation,
|
||||
}
|
||||
entity.mapEntity.directioner = entity.rotate
|
||||
@ -30,7 +30,7 @@ func (ae *AnimatedEntity) Render(target d2interface.Surface) {
|
||||
renderOffset := ae.Position.RenderOffset()
|
||||
target.PushTranslation(
|
||||
int((renderOffset.X()-renderOffset.Y())*16),
|
||||
int(((renderOffset.X() + renderOffset.Y()) * 8)),
|
||||
int(((renderOffset.X()+renderOffset.Y())*8)-5),
|
||||
)
|
||||
|
||||
defer target.Pop()
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
type mapEntity struct {
|
||||
Position d2vector.Position
|
||||
Target d2vector.Position
|
||||
velocity d2vector.Vector
|
||||
|
||||
Speed float64
|
||||
path []d2astar.Pather
|
||||
@ -20,8 +21,8 @@ type mapEntity struct {
|
||||
directioner func(direction int)
|
||||
}
|
||||
|
||||
// createMapEntity creates an instance of mapEntity
|
||||
func createMapEntity(x, y int) mapEntity {
|
||||
// newMapEntity creates an instance of mapEntity
|
||||
func newMapEntity(x, y int) mapEntity {
|
||||
locX, locY := float64(x), float64(y)
|
||||
|
||||
return mapEntity{
|
||||
@ -43,6 +44,7 @@ func (m *mapEntity) GetLayer() int {
|
||||
func (m *mapEntity) SetPath(path []d2astar.Pather, done func()) {
|
||||
m.path = path
|
||||
m.done = done
|
||||
m.nextPath()
|
||||
}
|
||||
|
||||
// ClearPath clears the entity movement path.
|
||||
@ -60,14 +62,9 @@ func (m *mapEntity) GetSpeed() float64 {
|
||||
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.
|
||||
func (m *mapEntity) Step(tickTime float64) {
|
||||
if m.IsAtTarget() {
|
||||
if m.atTarget() && !m.hasPath() {
|
||||
if m.done != nil {
|
||||
m.done()
|
||||
m.done = nil
|
||||
@ -76,47 +73,39 @@ func (m *mapEntity) Step(tickTime float64) {
|
||||
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 {
|
||||
// Add the velocity to the position and set new velocity to remainder
|
||||
applyVelocity(&m.Position.Vector, &velocity, &m.Target.Vector)
|
||||
applyVelocity(&m.Position.Vector, &m.velocity, &m.Target.Vector)
|
||||
|
||||
// New position is at target
|
||||
if m.Position.EqualsApprox(m.Target.Vector) {
|
||||
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 m.atTarget() {
|
||||
m.nextPath()
|
||||
}
|
||||
|
||||
if velocity.IsZero() {
|
||||
if m.velocity.IsZero() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// velocity returns a vector describing the change in position this tick.
|
||||
func (m *mapEntity) velocity(tickTime float64) d2vector.Vector {
|
||||
length := tickTime * m.Speed
|
||||
v := m.Target.Vector.Clone()
|
||||
v.Subtract(&m.Position.Vector)
|
||||
v.SetLength(length)
|
||||
|
||||
return v
|
||||
// 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)
|
||||
}
|
||||
|
||||
// applyVelocity adds velocity to position. If the new position extends beyond target from the original position, the
|
||||
// position is set to the target and velocity is set to the overshot amount.
|
||||
// setVelocity returns a vector describing the given length and the direction to the current target.
|
||||
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) {
|
||||
// Set velocity values to zero if almost zero
|
||||
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.
|
||||
func (m *mapEntity) HasPathFinding() bool {
|
||||
// Returns false if the path has ended.
|
||||
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
|
||||
}
|
||||
|
||||
// SetTarget sets target coordinates and changes animation based on proximity and direction.
|
||||
func (m *mapEntity) SetTarget(tx, ty float64, done func()) {
|
||||
// setTarget sets target coordinates and changes animation based on proximity and direction.
|
||||
func (m *mapEntity) setTarget(tx, ty float64, done func()) {
|
||||
// Set the target
|
||||
m.Target.Set(tx, ty)
|
||||
m.done = done
|
||||
|
||||
// Update the direction
|
||||
if m.directioner != nil {
|
||||
d := m.Position.DirectionTo(m.Target.Vector)
|
||||
|
||||
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.
|
||||
|
93
d2core/d2map/d2mapentity/map_entity_test.go
Normal file
93
d2core/d2map/d2mapentity/map_entity_test.go
Normal 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)
|
||||
}
|
||||
}
|
@ -54,7 +54,7 @@ func (m *Missile) SetRadians(angle float64, done func()) {
|
||||
x := m.Position.X() + (r * math.Cos(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
|
||||
|
@ -31,7 +31,7 @@ type NPC struct {
|
||||
// CreateNPC creates a new NPC and returns a pointer to it.
|
||||
func CreateNPC(x, y int, monstat *d2datadict.MonStatsRecord, direction int) *NPC {
|
||||
result := &NPC{
|
||||
mapEntity: createMapEntity(x, y),
|
||||
mapEntity: newMapEntity(x, y),
|
||||
HasPaths: false,
|
||||
monstatRecord: monstat,
|
||||
monstatEx: d2datadict.MonStats2[monstat.ExtraDataKey],
|
||||
@ -75,7 +75,7 @@ func (v *NPC) Render(target d2interface.Surface) {
|
||||
renderOffset := v.Position.RenderOffset()
|
||||
target.PushTranslation(
|
||||
int((renderOffset.X()-renderOffset.Y())*16),
|
||||
int(((renderOffset.X() + renderOffset.Y()) * 8)),
|
||||
int(((renderOffset.X()+renderOffset.Y())*8)-5),
|
||||
)
|
||||
|
||||
defer target.Pop()
|
||||
@ -116,7 +116,7 @@ func (v *NPC) Advance(tickTime float64) {
|
||||
// If at the target, set target to the next path.
|
||||
v.isDone = false
|
||||
path := v.NextPath()
|
||||
v.SetTarget(
|
||||
v.setTarget(
|
||||
float64(path.X),
|
||||
float64(path.Y),
|
||||
v.next,
|
||||
@ -159,7 +159,7 @@ func (v *NPC) next() {
|
||||
// rotate sets direction and changes animation
|
||||
func (v *NPC) rotate(direction int) {
|
||||
var newMode d2enum.MonsterAnimationMode
|
||||
if !v.IsAtTarget() {
|
||||
if !v.atTarget() {
|
||||
newMode = d2enum.MonsterAnimationModeWalk
|
||||
} else {
|
||||
newMode = d2enum.MonsterAnimationModeNeutral
|
||||
|
@ -57,7 +57,7 @@ func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero
|
||||
|
||||
result := &Player{
|
||||
Id: id,
|
||||
mapEntity: createMapEntity(x, y),
|
||||
mapEntity: newMapEntity(x, y),
|
||||
composite: composite,
|
||||
Equipment: equipment,
|
||||
Stats: stats,
|
||||
@ -147,7 +147,7 @@ func (v *Player) Render(target d2interface.Surface) {
|
||||
renderOffset := v.Position.RenderOffset()
|
||||
target.PushTranslation(
|
||||
int((renderOffset.X()-renderOffset.Y())*16),
|
||||
int(((renderOffset.X() + renderOffset.Y()) * 8)),
|
||||
int(((renderOffset.X()+renderOffset.Y())*8)-5),
|
||||
)
|
||||
|
||||
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.
|
||||
func (v *Player) GetAnimationMode() d2enum.PlayerAnimationMode {
|
||||
if v.IsRunning() && !v.IsAtTarget() {
|
||||
if v.IsRunning() && !v.atTarget() {
|
||||
return d2enum.PlayerAnimationModeRun
|
||||
}
|
||||
|
||||
if v.IsInTown() {
|
||||
if !v.IsAtTarget() {
|
||||
if !v.atTarget() {
|
||||
return d2enum.PlayerAnimationModeTownWalk
|
||||
}
|
||||
|
||||
return d2enum.PlayerAnimationModeTownNeutral
|
||||
}
|
||||
|
||||
if !v.IsAtTarget() {
|
||||
if !v.atTarget() {
|
||||
return d2enum.PlayerAnimationModeWalk
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user