entity debug rendering (#609)

* entity debug rendering, with command

needed to add getter method to MapEntity interface, so that's the reasoning for changing objects/npcs/player/etc

this currently brings allocs/s up to 10 (you can check by running `fps` command, se we need to look into d2debugutils and how it handles drawing text

* reverting velocity copy, broke the map entity tests. debug display wont show velocity currently.

* adding velocity debug
This commit is contained in:
lord 2020-07-21 05:51:09 -07:00 committed by GitHub
parent d0c6cd61dd
commit 362147848d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 30 deletions

View File

@ -1,10 +1,13 @@
package d2interface
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
// MapEntity is something that can be positioned on and rendered on the game map
type MapEntity interface {
Render(target Surface)
Advance(tickTime float64)
GetPosition() (float64, float64)
GetPosition() d2vector.Position
GetVelocity() d2vector.Vector
GetLayer() int
GetPositionF() (float64, float64)
Name() string

View File

@ -26,6 +26,7 @@ func newMapEntity(x, y int) mapEntity {
return mapEntity{
Position: pos,
Target: pos,
velocity: d2vector.NewVector(0, 0),
}
}
@ -65,22 +66,23 @@ func (m *mapEntity) Step(tickTime float64) {
m.done = nil
}
m.velocity.SetLength(0)
return
}
// 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.
v := m.velocity.Clone() // Create a new vector
for {
applyVelocity(&m.Position.Vector, &m.velocity, &m.Target.Vector)
applyVelocity(&m.Position.Vector, &v, &m.Target.Vector) // Pass the new vector to the function which alters it
if m.atTarget() {
m.nextPath()
}
if m.velocity.IsZero() {
if v.IsZero() { // Check if the new vector is zero (keeping this as m.velocity.IsZero() would break the test (infinite loop)
break
}
}

View File

@ -19,6 +19,14 @@ type Missile struct {
record *d2datadict.MissileRecord
}
func (m *Missile) GetPosition() d2vector.Position {
return m.AnimatedEntity.Position
}
func (m *Missile) GetVelocity() d2vector.Vector {
return m.AnimatedEntity.velocity
}
// CreateMissile creates a new Missile and initializes it's animation.
func CreateMissile(x, y int, record *d2datadict.MissileRecord) (*Missile, error) {
animation, err := d2asset.LoadAnimation(

View File

@ -1,6 +1,7 @@
package d2mapentity
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
"math/rand"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
@ -206,3 +207,13 @@ func (v *NPC) Selectable() bool {
func (v *NPC) Name() string {
return v.name
}
// GetPosition returns the NPC's position
func (v *NPC) GetPosition() d2vector.Position {
return v.mapEntity.Position
}
// GetVelocity returns the NPC's velocity vector
func (v *NPC) GetVelocity() d2vector.Vector {
return v.mapEntity.velocity
}

View File

@ -1,10 +1,10 @@
package d2mapentity
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
@ -223,3 +223,13 @@ func (v *Player) Selectable() bool {
// Players are selectable when in town
return v.IsInTown()
}
// GetPosition returns the entity's position
func (p *Player) GetPosition() d2vector.Position {
return p.mapEntity.Position
}
// GetVelocity returns the entity's velocity vector
func (p *Player) GetVelocity() d2vector.Vector {
return p.mapEntity.velocity
}

View File

@ -17,14 +17,16 @@ import (
// MapRenderer manages the game viewport and Camera. It requests tile and entity data from MapEngine and renders it.
type MapRenderer struct {
renderer d2interface.Renderer // Used for drawing operations
mapEngine *d2mapengine.MapEngine // The map engine that is being rendered
palette d2interface.Palette // The palette used for this map
viewport *Viewport // Used for rendering offsets
Camera Camera // Used to determine where on the map we are rendering
debugVisLevel int // Debug visibility index (0=none, 1=tiles, 2=sub-tiles)
lastFrameTime float64 // The last time the map was rendered
currentFrame int // Current render frame (for animations)
renderer d2interface.Renderer // Used for drawing operations
mapEngine *d2mapengine.MapEngine // The map engine that is being rendered
palette d2interface.Palette // The palette used for this map
viewport *Viewport // Used for rendering offsets
Camera Camera // Used to determine where on the map we are rendering
mapDebugVisLevel int // Map debug visibility index (0=none, 1=tiles,
// 2=sub-tiles)
entityDebugVisLevel int // Entity Debug visibility index (0=none, 1=vectors)
lastFrameTime float64 // The last time the map was rendered
currentFrame int // Current render frame (for animations)
}
// CreateMapRenderer creates a new MapRenderer, sets the required fields and returns a pointer to it.
@ -36,12 +38,16 @@ func CreateMapRenderer(renderer d2interface.Renderer, mapEngine *d2mapengine.Map
}
result.Camera = Camera{}
startPosition := d2vector.NewPosition(0,0)
startPosition := d2vector.NewPosition(0, 0)
result.Camera.position = &startPosition
result.viewport.SetCamera(&result.Camera)
term.BindAction("mapdebugvis", "set map debug visualization level", func(level int) {
result.debugVisLevel = level
result.mapDebugVisLevel = level
})
term.BindAction("entitydebugvis", "set entity debug visualization level", func(level int) {
result.entityDebugVisLevel = level
})
if mapEngine.LevelType().ID != 0 {
@ -87,12 +93,16 @@ func (mr *MapRenderer) Render(target d2interface.Surface) {
mr.renderPass1(target, startX, startY, endX, endY)
mr.renderPass2(target, startX, startY, endX, endY)
if mr.debugVisLevel > 0 {
mr.renderDebug(mr.debugVisLevel, target, startX, startY, endX, endY)
if mr.mapDebugVisLevel > 0 {
mr.renderMapDebug(mr.mapDebugVisLevel, target, startX, startY, endX, endY)
}
mr.renderPass3(target, startX, startY, endX, endY)
mr.renderPass4(target, startX, startY, endX, endY)
if mr.entityDebugVisLevel > 0 {
mr.renderEntityDebug(target)
}
}
// MoveCameraTo sets the position of the Camera to the given x and y coordinates.
@ -146,7 +156,9 @@ func (mr *MapRenderer) renderPass2(target d2interface.Surface, startX, startY, e
// TODO: Do not loop over every entity every frame
for _, mapEntity := range *mr.mapEngine.Entities() {
entityX, entityY := mapEntity.GetPosition()
pos := mapEntity.GetPosition()
vec := pos.World()
entityX, entityY := vec.X(), vec.Y()
if mapEntity.GetLayer() != 1 {
continue
@ -176,7 +188,9 @@ func (mr *MapRenderer) renderPass3(target d2interface.Surface, startX, startY, e
// TODO: Do not loop over every entity every frame
for _, mapEntity := range *mr.mapEngine.Entities() {
entityX, entityY := mapEntity.GetPosition()
pos := mapEntity.GetPosition()
vec := pos.World()
entityX, entityY := vec.X(), vec.Y()
if mapEntity.GetLayer() == 1 {
continue
@ -299,16 +313,46 @@ func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadowRecord, target d2inter
target.Render(img)
}
func (mr *MapRenderer) renderDebug(debugVisLevel int, target d2interface.Surface, startX, startY, endX, endY int) {
func (mr *MapRenderer) renderMapDebug(mapDebugVisLevel int, target d2interface.Surface, startX, startY, endX, endY int) {
for tileY := startY; tileY < endY; tileY++ {
for tileX := startX; tileX < endX; tileX++ {
mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY))
mr.renderTileDebug(tileX, tileY, debugVisLevel, target)
mr.renderTileDebug(tileX, tileY, mapDebugVisLevel, target)
mr.viewport.PopTranslation()
}
}
}
func (mr *MapRenderer) renderEntityDebug(target d2interface.Surface) {
entities := *mr.mapEngine.Entities()
for idx := range entities {
e := entities[idx]
pos := e.GetPosition()
world := pos
x, y := world.X()/5, world.Y()/5
velocity := e.GetVelocity()
velocity = velocity.Clone()
// velocity.Scale(60) // arbitrary scale value, just to make it easy to see
vx, vy := mr.viewport.WorldToOrtho(velocity.X(), velocity.Y())
screenX, screenY := mr.viewport.WorldToScreen(x, y)
offX, offY := 40, -40
mr.viewport.PushTranslationWorld(x, y)
target.PushTranslation(screenX, screenY)
target.DrawLine(offX, offY, color.RGBA{255, 255, 255, 128})
target.PushTranslation(offX+10, offY-20)
target.PushTranslation(-10, -10)
target.DrawRect(200, 50, color.RGBA{0, 0, 0, 64})
target.Pop()
target.DrawTextf("World (%.2f, %.2f)\nVelocity (%.2f, %.2f)", x, y, vx, vy)
target.Pop()
target.DrawLine(int(vx), int(vy), color.RGBA{64, 255, 0, 255})
target.Pop()
mr.viewport.PopTranslation()
}
}
// WorldToScreen returns the screen (pixel) position for the given isometric world position as two ints.
func (mr *MapRenderer) WorldToScreen(x, y float64) (int, int) {
return mr.viewport.WorldToScreen(x, y)

View File

@ -122,12 +122,6 @@ func (ob *Object) GetLayer() int {
return ob.drawLayer
}
// GetPosition of the object
func (ob *Object) GetPosition() (x, y float64) {
w := ob.Position.Tile()
return w.X(), w.Y()
}
// GetPositionF of the object but differently
func (ob *Object) GetPositionF() (x, y float64) {
w := ob.Position.World()
@ -138,3 +132,13 @@ func (ob *Object) GetPositionF() (x, y float64) {
func (ob *Object) Name() string {
return ob.name
}
// GetPosition returns the object's position
func (ob *Object) GetPosition() d2vector.Position {
return ob.Position
}
// GetVelocity returns the object's velocity vector
func (ob *Object) GetVelocity() d2vector.Vector {
return d2vector.NewVector(0, 0)
}