OpenDiablo2/d2common/d2math/d2vector/position.go

97 lines
3.2 KiB
Go

package d2vector
import (
"fmt"
"math"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
)
const (
subTilesPerTile float64 = 5 // The number of sub tiles that make up one map tile.
entityDirectionCount float64 = 64 // The Diablo equivalent of 360 degrees when dealing with entity rotation.
entityDirectionIncrement float64 = 8 // One 8th of 64. There 8 possible facing directions for entities.
// Note: there should be 16 facing directions for entities. See Position.DirectionTo()
)
// Position is a vector in world space. The stored value is the sub tile position.
type Position struct {
Vector
}
// NewPosition returns a Position struct with the given sub tile coordinates where 1 = 1 sub tile, with a fractional
// offset.
func NewPosition(x, y float64) Position {
p := Position{*NewVector(x, y)}
p.checkValues()
return p
}
// NewPositionTile returns a Position struct with the given tile coordinates where 1 = 1 tile, with a fractional offset.
func NewPositionTile(x, y float64) Position {
p := Position{*NewVector(x*subTilesPerTile, y*subTilesPerTile)}
p.checkValues()
return p
}
// Set sets this position to the given sub tile coordinates where 1 = 1 sub tile, with a fractional offset.
func (p *Position) Set(x, y float64) {
p.x, p.y = x, y
p.checkValues()
}
func (p *Position) checkValues() {
if math.IsNaN(p.x) || math.IsNaN(p.y) {
panic(fmt.Sprintf("float value is NaN: %s", p.Vector))
}
if math.IsInf(p.x, 0) || math.IsInf(p.y, 0) {
panic(fmt.Sprintf("float value is Inf: %s", p.Vector))
}
}
// World is the exact position where 1 = one map tile and 0.2 = one sub tile.
func (p *Position) World() *Vector {
c := p.Clone()
return c.DivideScalar(subTilesPerTile)
}
// Tile is the position of the current map tile. It is the floor of World(), always a whole number.
func (p *Position) Tile() *Vector {
return p.World().Floor()
}
// RenderOffset is the offset in sub tiles from the curren tile, + 1. This places the vector at the bottom vertex of an
// isometric diamond visually representing one sub tile. Sub tile indices increase to the lower right diagonal ('down')
// and to the lower left diagonal ('left') of the isometric grid. This renders the target one index above which visually
// is one tile below.
func (p *Position) RenderOffset() *Vector {
return p.SubTileOffset().AddScalar(1)
}
// SubTileOffset is the offset from the current map tile in sub tiles.
func (p *Position) SubTileOffset() *Vector {
t := p.Tile().Scale(subTilesPerTile)
c := p.Clone()
return c.Subtract(t)
}
// DirectionTo returns the entity direction from this vector to the given vector.
func (v *Vector) DirectionTo(target Vector) int {
direction := target.Clone()
direction.Subtract(v)
angle := direction.SignedAngle(VectorRight())
radiansPerDirection := d2math.RadFull / entityDirectionCount
// Note: The direction is always one increment out so we must subtract one increment.
// This might not work when we implement all 16 directions (as of this writing entities can only face one of 8
// directions). See entityDirectionIncrement.
newDirection := int((angle / radiansPerDirection) - entityDirectionIncrement)
return d2math.WrapInt(newDirection, int(entityDirectionCount))
}