diff --git a/d2common/d2math/d2vector/position.go b/d2common/d2math/d2vector/position.go index abcbaaa6..3606436b 100644 --- a/d2common/d2math/d2vector/position.go +++ b/d2common/d2math/d2vector/position.go @@ -13,13 +13,21 @@ type Position struct { } // NewPosition creates a new Position at the given float64 world position. -func NewPosition(x, y float64) *Position { - p := &Position{NewVector(x, y)} +func NewPosition(x, y float64) Position { + p := Position{NewVector(x, y)} p.checkValues() return p } +// EntityPosition returns a Position struct based on the given entity spawn point. +// The value given should be the one set in d2mapstamp.Stamp.Entities: +// (tileOffsetX*5)+object.X, (tileOffsetY*5)+object.Y +// TODO: This probably doesn't support positions in between sub tiles so will only be suitable for spawning entities from map generation, not for multiplayer syncing. +func EntityPosition(x, y int) Position { + return NewPosition(float64(x)/5, float64(y)/5) +} + // Set sets this position to the given x and y values. func (p *Position) Set(x, y float64) { p.x, p.y = x, y @@ -37,36 +45,47 @@ func (p *Position) checkValues() { } // World is the position, where 1 = one map tile. +// unused func (p *Position) World() *Vector { return &p.Vector } -// Tile is the tile position, always a whole number. +// Tile is the tile position, always a whole number. (tileX, tileY) func (p *Position) Tile() *Vector { c := p.World().Clone() return c.Floor() } // TileOffset is the offset from the tile position, always < 1. +// unused func (p *Position) TileOffset() *Vector { c := p.World().Clone() return c.Subtract(p.Tile()) } -// SubWorld is the position, where 5 = one map tile. +// SubWorld is the position, where 5 = one map tile. (locationX, locationY) func (p *Position) SubWorld() *Vector { c := p.World().Clone() return c.Scale(subTilesPerTile) } // SubTile is the tile position in sub tiles, always a multiple of 5. +// unused func (p *Position) SubTile() *Vector { - c := p.Tile().Clone() - return c.Scale(subTilesPerTile) + return p.Tile().Scale(subTilesPerTile) } // SubTileOffset is the offset from the sub tile position in sub tiles, always < 1. +// unused func (p *Position) SubTileOffset() *Vector { - c := p.SubWorld().Clone() - return c.Subtract(p.SubTile()) + return p.SubWorld().Subtract(p.SubTile()) +} + +// TODO: understand this and maybe improve/remove it +// SubTileOffset() + 1. It's used for rendering. It seems to always do this: +// v.offsetX+int((v.subcellX-v.subcellY)*16), +// v.offsetY+int(((v.subcellX+v.subcellY)*8)-5), +// ^ Maybe similar to Viewport.OrthoToWorld? (subCellX, subCellY) +func (p *Position) SubCell() *Vector { + return p.SubTileOffset().AddScalar(1) } diff --git a/d2common/d2math/d2vector/position_test.go b/d2common/d2math/d2vector/position_test.go index 085d7c4f..ed44d6ea 100644 --- a/d2common/d2math/d2vector/position_test.go +++ b/d2common/d2math/d2vector/position_test.go @@ -1,12 +1,49 @@ package d2vector import ( + "math" + "math/rand" "testing" ) -func validate(t *testing.T, original, got, want, unchanged Vector) { +func TestEntityPosition(t *testing.T) { + x, y := rand.Intn(1000), rand.Intn(1000) + pos := EntityPosition(x, y) + locX, locY := float64(x), float64(y) + + // old coordinate values Position equivalent + locationX := locX // .SubWord().X() + locationY := locY // .SubWord().Y() + tileX := float64(x / 5) // .Tile().X() + tileY := float64(y / 5) // .Tile().Y() + subcellX := 1 + math.Mod(locX, 5) // .SubCell().X() + subcellY := 1 + math.Mod(locY, 5) // .SubCell().Y() + + want := NewVector(tileX, tileY) + got := pos.Tile() + + if !got.Equals(want) { + t.Errorf("world position should match old value: got %s: want %s", got, want) + } + + want = NewVector(subcellX, subcellY) + got = pos.SubCell() + + if !got.Equals(want) { + t.Errorf("sub cell position should match old value: got %s: want %s", got, want) + } + + want = NewVector(locationX, locationY) + got = pos.SubWorld() + + if !got.Equals(want) { + t.Errorf("sub tile position should match old value: got %s: want %s", got, want) + } +} + +func validate(description string, t *testing.T, original, got, want, unchanged Vector) { if !got.EqualsApprox(want) { - t.Errorf("want %s: got %s", want, got) + t.Errorf("%s: want %s: got %s", description, want, got) } if !original.EqualsApprox(unchanged) { @@ -20,7 +57,7 @@ func TestTile(t *testing.T) { want := NewVector(1, 1) unchanged := NewVector(1.6, 1.6) - validate(t, p.Vector, *got, want, unchanged) + validate("tile position", t, p.Vector, *got, want, unchanged) } func TestTileOffset(t *testing.T) { @@ -29,7 +66,7 @@ func TestTileOffset(t *testing.T) { want := NewVector(0.6, 0.6) unchanged := NewVector(1.6, 1.6) - validate(t, p.Vector, *got, want, unchanged) + validate("tile offset", t, p.Vector, *got, want, unchanged) } func TestSubWorld(t *testing.T) { @@ -38,7 +75,7 @@ func TestSubWorld(t *testing.T) { want := NewVector(5, 5) unchanged := NewVector(1, 1) - validate(t, p.Vector, *got, want, unchanged) + validate("sub tile world position", t, p.Vector, *got, want, unchanged) } func TestSubTile(t *testing.T) { @@ -47,7 +84,7 @@ func TestSubTile(t *testing.T) { want := NewVector(5, 5) unchanged := NewVector(1, 1) - validate(t, p.Vector, *got, want, unchanged) + validate("sub tile with offset", t, p.Vector, *got, want, unchanged) } func TestSubTileOffset(t *testing.T) { @@ -56,5 +93,5 @@ func TestSubTileOffset(t *testing.T) { want := NewVector(0.5, 0.5) unchanged := NewVector(1.1, 1.1) - validate(t, p.Vector, *got, want, unchanged) + validate("offset from sub tile", t, p.Vector, *got, want, unchanged) } diff --git a/d2common/d2math/d2vector/vector.go b/d2common/d2math/d2vector/vector.go index 6a57ef91..e5524697 100644 --- a/d2common/d2math/d2vector/vector.go +++ b/d2common/d2math/d2vector/vector.go @@ -20,6 +20,16 @@ func NewVector(x, y float64) Vector { return Vector{x, y} } +// X returns the x value of this vector. +func (v *Vector) X() float64 { + return v.x +} + +// Y returns the y value of this vector. +func (v *Vector) Y() float64 { + return v.y +} + // Equals returns true if the float64 values of this vector are exactly equal to the given Vector. func (v *Vector) Equals(o Vector) bool { return v.x == o.x && v.y == o.y @@ -85,6 +95,14 @@ func (v *Vector) Add(o *Vector) *Vector { return v } +// AddScalar the given vector to this vector. +func (v *Vector) AddScalar(s float64) *Vector { + v.x += s + v.y += s + + return v +} + // Subtract the given vector from this vector. func (v *Vector) Subtract(o *Vector) *Vector { v.x -= o.x @@ -117,6 +135,14 @@ func (v *Vector) Divide(o *Vector) *Vector { return v } +// DivideScalar divides this vector by the given float64 value. +func (v *Vector) DivideScalar(s float64) *Vector { + v.x /= s + v.y /= s + + return v +} + // Abs sets the vector to it's absolute (positive) equivalent. func (v *Vector) Abs() *Vector { xm, ym := 1.0, 1.0 diff --git a/d2common/d2math/d2vector/vector_test.go b/d2common/d2math/d2vector/vector_test.go index e8ad7dae..fecc8b4a 100644 --- a/d2common/d2math/d2vector/vector_test.go +++ b/d2common/d2math/d2vector/vector_test.go @@ -154,15 +154,25 @@ func TestClamp(t *testing.T) { } func TestAdd(t *testing.T) { - v := NewVector(1, 1) - add := NewVector(0.5, 0.5) - want := NewVector(1.5, 1.5) + v := NewVector(1, 2) + add := NewVector(0.5, 3) + want := NewVector(1.5, 5) got := v.Clone() got.Add(&add) evaluateVector(fmt.Sprintf("add %s to %s", add, v), want, got, t) } +func TestAddScalar(t *testing.T) { + v := NewVector(1, -1) + add := 0.5 + want := NewVector(1.5, -0.5) + got := v.Clone() + got.AddScalar(add) + + evaluateVector(fmt.Sprintf("add %.2f to %s", add, v), want, got, t) +} + func TestSubtract(t *testing.T) { v := NewVector(1, 1) subtract := NewVector(0.6, 0.6) @@ -184,15 +194,25 @@ func TestMultiply(t *testing.T) { } func TestDivide(t *testing.T) { - v := NewVector(1, 1) - divide := NewVector(2, 2) - want := NewVector(0.5, 0.5) + v := NewVector(1, 8) + divide := NewVector(2, 4) + want := NewVector(0.5, 2) got := v.Clone() got.Divide(÷) evaluateVector(fmt.Sprintf("divide %s by %s", v, divide), want, got, t) } +func TestDivideScalar(t *testing.T) { + v := NewVector(1, 2) + divide := 2.0 + want := NewVector(0.5, 1.0) + got := v.Clone() + got.DivideScalar(divide) + + evaluateVector(fmt.Sprintf("divide %s by %.2f", v, divide), want, got, t) +} + func TestScale(t *testing.T) { v := NewVector(2, 3) want := NewVector(4, 6)