From 362147848dda5bc33ad35237aaf9a70dc2479f3e Mon Sep 17 00:00:00 2001 From: lord Date: Tue, 21 Jul 2020 05:51:09 -0700 Subject: [PATCH] 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 --- d2common/d2interface/map_entity.go | 5 +- d2core/d2map/d2mapentity/map_entity.go | 12 ++-- d2core/d2map/d2mapentity/missile.go | 8 +++ d2core/d2map/d2mapentity/npc.go | 11 ++++ d2core/d2map/d2mapentity/player.go | 14 ++++- d2core/d2map/d2maprenderer/renderer.go | 76 ++++++++++++++++++++------ d2core/d2object/object.go | 16 ++++-- 7 files changed, 112 insertions(+), 30 deletions(-) diff --git a/d2common/d2interface/map_entity.go b/d2common/d2interface/map_entity.go index 926cd438..6d852db4 100644 --- a/d2common/d2interface/map_entity.go +++ b/d2common/d2interface/map_entity.go @@ -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 diff --git a/d2core/d2map/d2mapentity/map_entity.go b/d2core/d2map/d2mapentity/map_entity.go index 8d569084..cf5a5953 100644 --- a/d2core/d2map/d2mapentity/map_entity.go +++ b/d2core/d2map/d2mapentity/map_entity.go @@ -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 } } diff --git a/d2core/d2map/d2mapentity/missile.go b/d2core/d2map/d2mapentity/missile.go index 056df43f..199a56d1 100644 --- a/d2core/d2map/d2mapentity/missile.go +++ b/d2core/d2map/d2mapentity/missile.go @@ -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( diff --git a/d2core/d2map/d2mapentity/npc.go b/d2core/d2map/d2mapentity/npc.go index fa778a16..7923e7e5 100644 --- a/d2core/d2map/d2mapentity/npc.go +++ b/d2core/d2map/d2mapentity/npc.go @@ -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 +} diff --git a/d2core/d2map/d2mapentity/player.go b/d2core/d2map/d2mapentity/player.go index 67af600a..d5174bf0 100644 --- a/d2core/d2map/d2mapentity/player.go +++ b/d2core/d2map/d2mapentity/player.go @@ -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 +} diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index e23b4369..e1d73fa3 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -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) diff --git a/d2core/d2object/object.go b/d2core/d2object/object.go index 57943546..0861e37b 100644 --- a/d2core/d2object/object.go +++ b/d2core/d2object/object.go @@ -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) +}