mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-18 02:16:23 -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.
|
// 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)
|
||||||
|
@ -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) {
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
|
@ -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.
|
||||||
|
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))
|
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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user