diff --git a/d2core/d2scene/game.go b/d2core/d2scene/game.go index 25adf40a..4315a38b 100644 --- a/d2core/d2scene/game.go +++ b/d2core/d2scene/game.go @@ -1,6 +1,8 @@ package d2scene import ( + "image/color" + "github.com/OpenDiablo2/D2Shared/d2common/d2enum" "github.com/OpenDiablo2/D2Shared/d2common/d2interface" "github.com/OpenDiablo2/D2Shared/d2common/d2resource" @@ -14,7 +16,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine" "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" "github.com/hajimehoshi/ebiten" - "image/color" ) type Game struct { @@ -128,9 +129,7 @@ func (v *Game) Update(tickTime float64) { } if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { - mx, my := ebiten.CursorPosition() - px, py := d2helper.ScreenToIso(float64(mx)-v.mapEngine.OffsetX, float64(my)-v.mapEngine.OffsetY) - + px, py := v.mapEngine.ScreenToIso(ebiten.CursorPosition()) v.mapEngine.Hero.AnimatedEntity.SetTarget(px*5, py*5, 1) } diff --git a/d2core/d2scene/map_engine_testing.go b/d2core/d2scene/map_engine_testing.go index 25b78b50..6cba8aec 100644 --- a/d2core/d2scene/map_engine_testing.go +++ b/d2core/d2scene/map_engine_testing.go @@ -172,9 +172,9 @@ func (v *MapEngineTest) Unload() { func (v *MapEngineTest) Render(screen *ebiten.Image) { v.mapEngine.Render(screen) - actualX := float64(v.uiManager.CursorX) - v.mapEngine.OffsetX - actualY := float64(v.uiManager.CursorY) - v.mapEngine.OffsetY - tileX, tileY := d2helper.ScreenToIso(actualX, actualY) + actualX := v.uiManager.CursorX + actualY := v.uiManager.CursorY + tileX, tileY := v.mapEngine.ScreenToIso(actualX, actualY) subtileX := int(math.Ceil(math.Mod((tileX*10), 10))) / 2 subtileY := int(math.Ceil(math.Mod((tileY*10), 10))) / 2 curRegion := v.mapEngine.GetRegionAt(int(tileX), int(tileY)) @@ -182,8 +182,8 @@ func (v *MapEngineTest) Render(screen *ebiten.Image) { return } line := fmt.Sprintf("%d, %d (Tile %d.%d, %d.%d)", - int(math.Ceil(actualX)), - int(math.Ceil(actualY)), + actualX, + actualY, int(math.Floor(tileX))-curRegion.Rect.Left, subtileX, int(math.Floor(tileY))-curRegion.Rect.Top, @@ -217,33 +217,22 @@ func (v *MapEngineTest) Update(tickTime float64) { ctrlPressed := v.uiManager.KeyPressed(ebiten.KeyControl) shiftPressed := v.uiManager.KeyPressed(ebiten.KeyShift) + var moveSpeed float64 = 800 + if v.uiManager.KeyPressed(ebiten.KeyShift) { + moveSpeed = 200 + } + if v.uiManager.KeyPressed(ebiten.KeyDown) { - if v.uiManager.KeyPressed(ebiten.KeyShift) { - v.mapEngine.OffsetY -= tickTime * 200 - } else { - v.mapEngine.OffsetY -= tickTime * 800 - } + v.mapEngine.MoveCameraBy(0, moveSpeed*tickTime) } if v.uiManager.KeyPressed(ebiten.KeyUp) { - if v.uiManager.KeyPressed(ebiten.KeyShift) { - v.mapEngine.OffsetY += tickTime * 200 - } else { - v.mapEngine.OffsetY += tickTime * 800 - } + v.mapEngine.MoveCameraBy(0, -moveSpeed*tickTime) } if v.uiManager.KeyPressed(ebiten.KeyLeft) { - if v.uiManager.KeyPressed(ebiten.KeyShift) { - v.mapEngine.OffsetX += tickTime * 200 - } else { - v.mapEngine.OffsetX += tickTime * 800 - } + v.mapEngine.MoveCameraBy(-moveSpeed*tickTime, 0) } if v.uiManager.KeyPressed(ebiten.KeyRight) { - if v.uiManager.KeyPressed(ebiten.KeyShift) { - v.mapEngine.OffsetX -= tickTime * 200 - } else { - v.mapEngine.OffsetX -= tickTime * 800 - } + v.mapEngine.MoveCameraBy(moveSpeed*tickTime, 0) } if inpututil.IsKeyJustPressed(ebiten.KeyF7) { diff --git a/d2render/d2mapengine/camera.go b/d2render/d2mapengine/camera.go new file mode 100644 index 00000000..f6086d5d --- /dev/null +++ b/d2render/d2mapengine/camera.go @@ -0,0 +1,20 @@ +package d2mapengine + +type Camera struct { + x float64 + y float64 +} + +func (c *Camera) MoveTo(x, y float64) { + c.x = x + c.y = y +} + +func (c *Camera) MoveBy(x, y float64) { + c.x += x + c.y += y +} + +func (c *Camera) GetPosition() (float64, float64) { + return c.x, c.y +} diff --git a/d2render/d2mapengine/engine.go b/d2render/d2mapengine/engine.go index 0fa2144b..f6cc145a 100644 --- a/d2render/d2mapengine/engine.go +++ b/d2render/d2mapengine/engine.go @@ -7,8 +7,6 @@ import ( "github.com/OpenDiablo2/D2Shared/d2common/d2enum" - "github.com/OpenDiablo2/D2Shared/d2helper" - "github.com/OpenDiablo2/D2Shared/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2core" @@ -36,27 +34,31 @@ type Engine struct { fileProvider d2interface.FileProvider region int regions []EngineRegion - OffsetX float64 - OffsetY float64 ShowTiles int Hero *d2core.Hero + + viewport *Viewport + camera Camera } func CreateMapEngine(gameState *d2core.GameState, soundManager *d2audio.Manager, fileProvider d2interface.FileProvider) *Engine { - result := &Engine{ + engine := &Engine{ gameState: gameState, soundManager: soundManager, fileProvider: fileProvider, regions: make([]EngineRegion, 0), + viewport: NewViewport(0, 0, 800, 600), } - return result + + engine.viewport.SetCamera(&engine.camera) + return engine } func (v *Engine) Region() *EngineRegion { return &v.regions[v.region] } -func (v *Engine) SetRegion(region int) { +func (v *Engine) SetRegion(region int) { v.region = region } @@ -65,8 +67,19 @@ func (v *Engine) GetRegion(regionIndex int) *EngineRegion { } func (v *Engine) CenterCameraOn(x, y float64) { - v.OffsetX = -(x - 400) - v.OffsetY = -(y - 300) + v.camera.MoveTo(x, y) +} + +func (v *Engine) MoveCameraBy(x, y float64) { + v.camera.MoveBy(x, y) +} + +func (v *Engine) ScreenToIso(x, y int) (float64, float64) { + return v.viewport.ScreenToIso(x, y) +} + +func (v *Engine) ScreenToWorld(x, y int) (float64, float64) { + return v.viewport.ScreenToWorld(x, y) } func (v *Engine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int, fileIndex int) { @@ -109,36 +122,31 @@ func (v *Engine) GenerateAct1Overworld() { v.GenTilesCache(&v.regions[i]) } - sx, sy := d2helper.IsoToScreen(region.StartX, region.StartY, 0, 0) - v.OffsetX = sx - 400 - v.OffsetY = sy - 300 + v.camera.MoveTo(v.viewport.IsoToWorld(region.StartX, region.StartY)) } func (v *Engine) GetRegionAt(x, y int) *EngineRegion { - if v.regions == nil { - return nil - } for _, region := range v.regions { - if !region.Rect.IsInRect(x, y) { - continue + if region.Rect.IsInRect(x, y) { + return ®ion } - return ®ion } + return nil } func (v *Engine) Render(target *ebiten.Image) { for _, region := range v.regions { // X position of leftmost point of region - left := (region.Rect.Left - region.Rect.Bottom()) * 80 + left := float64((region.Rect.Left - region.Rect.Bottom()) * 80) // Y position of top of region - top := (region.Rect.Left + region.Rect.Top) * 40 + top := float64((region.Rect.Left + region.Rect.Top) * 40) // X of right - right := (region.Rect.Right() - region.Rect.Top) * 80 + right := float64((region.Rect.Right() - region.Rect.Top) * 80) // Y of bottom - bottom := (region.Rect.Right() + region.Rect.Bottom()) * 40 + bottom := float64((region.Rect.Right() + region.Rect.Bottom()) * 40) - if -v.OffsetX+800 > float64(left) && -v.OffsetX < float64(right) && -v.OffsetY+600 > float64(top) && -v.OffsetY < float64(bottom) { + if v.viewport.IsWorldRectVisible(left, top, right, bottom) { v.RenderRegion(region, target) } } @@ -191,56 +199,61 @@ func (v *Engine) GenTilesCache(region *EngineRegion) { func (v *Engine) RenderRegion(region EngineRegion, target *ebiten.Image) { for tileIdx := range region.Tiles { - sx, sy := d2helper.IsoToScreen(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top), v.OffsetX, v.OffsetY) - if sx > -160 && sy > -380 && sx <= 880 && sy <= 1240 { + if v.viewport.IsWorldTileVisbile(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top)) { region.Region.UpdateAnimations() - v.RenderPass1(region.Region, region.Tiles[tileIdx].offX, region.Tiles[tileIdx].offY, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target) - if v.ShowTiles > 0 { - v.DrawTileLines(region.Region, region.Tiles[tileIdx].offX, region.Tiles[tileIdx].offY, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target) - } + v.viewport.PushTranslation(float64(region.Tiles[tileIdx].offX), float64(region.Tiles[tileIdx].offY)) + v.RenderPass1(region.Region, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target) + v.DrawTileLines(region.Region, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target) + v.viewport.PopTranslation() } } + for tileIdx := range region.Tiles { - sx, sy := d2helper.IsoToScreen(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top), v.OffsetX, v.OffsetY) - if sx > -160 && sy > -380 && sx <= 880 && sy <= 1240 { - v.RenderPass2(region.Region, region.Tiles[tileIdx].offX, region.Tiles[tileIdx].offY, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target) + if v.viewport.IsWorldTileVisbile(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top)) { + v.viewport.PushTranslation(float64(region.Tiles[tileIdx].offX), float64(region.Tiles[tileIdx].offY)) + v.RenderPass2(region.Region, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target) + v.viewport.PopTranslation() } } + for tileIdx := range region.Tiles { - sx, sy := d2helper.IsoToScreen(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top), v.OffsetX, v.OffsetY) - if sx > -160 && sy > -380 && sx <= 880 && sy <= 1240 { - v.RenderPass3(region.Region, region.Tiles[tileIdx].offX, region.Tiles[tileIdx].offY, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target) + if v.viewport.IsWorldTileVisbile(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top)) { + v.viewport.PushTranslation(float64(region.Tiles[tileIdx].offX), float64(region.Tiles[tileIdx].offY)) + v.RenderPass3(region.Region, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target) + v.viewport.PopTranslation() } } } -func (v *Engine) RenderPass1(region *Region, offX, offY, x, y int, target *ebiten.Image) { +func (v *Engine) RenderPass1(region *Region, x, y int, target *ebiten.Image) { tile := region.DS1.Tiles[y][x] // Draw lower walls for i := range tile.Walls { if !tile.Walls[i].Type.LowerWall() || tile.Walls[i].Prop1 == 0 || tile.Walls[i].Hidden { continue } - region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeWalls, i, target) + + region.RenderTile(v.viewport, x, y, d2enum.RegionLayerTypeWalls, i, target) } for i := range tile.Floors { if tile.Floors[i].Hidden || tile.Floors[i].Prop1 == 0 { continue } - region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeFloors, i, target) + + region.RenderTile(v.viewport, x, y, d2enum.RegionLayerTypeFloors, i, target) } for i := range tile.Shadows { if tile.Shadows[i].Hidden || tile.Shadows[i].Prop1 == 0 { continue } - region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeShadows, i, target) - } + region.RenderTile(v.viewport, x, y, d2enum.RegionLayerTypeShadows, i, target) + } } -func (v *Engine) RenderPass2(region *Region, offX, offY, x, y int, target *ebiten.Image) { +func (v *Engine) RenderPass2(region *Region, x, y int, target *ebiten.Image) { tile := region.DS1.Tiles[y][x] // Draw upper walls @@ -248,63 +261,108 @@ func (v *Engine) RenderPass2(region *Region, offX, offY, x, y int, target *ebite if !tile.Walls[i].Type.UpperWall() || tile.Walls[i].Hidden { continue } - region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeWalls, i, target) + + region.RenderTile(v.viewport, x, y, d2enum.RegionLayerTypeWalls, i, target) } + screenX, screenY := v.viewport.WorldToScreen(v.viewport.GetTranslation()) + for _, obj := range region.AnimationEntities { if obj.TileX == x && obj.TileY == y { - obj.Render(target, offX+int(v.OffsetX), offY+int(v.OffsetY)) + obj.Render(target, screenX, screenY) } } + for _, npc := range region.NPCs { if npc.AnimatedEntity.TileX == x && npc.AnimatedEntity.TileY == y { - npc.Render(target, offX+int(v.OffsetX), offY+int(v.OffsetY)) + npc.Render(target, screenX, screenY) } } + if v.Hero != nil && v.Hero.AnimatedEntity.TileX == x && v.Hero.AnimatedEntity.TileY == y { - v.Hero.Render(target, offX+int(v.OffsetX), offY+int(v.OffsetY)) + v.Hero.Render(target, screenX, screenY) } } -func (v *Engine) RenderPass3(region *Region, offX, offY, x, y int, target *ebiten.Image) { +func (v *Engine) RenderPass3(region *Region, x, y int, target *ebiten.Image) { tile := region.DS1.Tiles[y][x] // Draw ceilings for i := range tile.Walls { if tile.Walls[i].Type != d2enum.Roof { continue } - region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeWalls, i, target) + + region.RenderTile(v.viewport, x, y, d2enum.RegionLayerTypeWalls, i, target) } } -func (v *Engine) DrawTileLines(region *Region, offX, offY, x, y int, target *ebiten.Image) { +func (v *Engine) DrawTileLines(region *Region, x, y int, target *ebiten.Image) { if v.ShowTiles > 0 { subtileColor := color.RGBA{80, 80, 255, 100} tileColor := color.RGBA{255, 255, 255, 255} - ebitenutil.DrawLine(target, float64(offX)+v.OffsetX, float64(offY)+v.OffsetY, float64(offX)+v.OffsetX+80, float64(offY)+v.OffsetY+40, tileColor) - ebitenutil.DrawLine(target, float64(offX)+v.OffsetX, float64(offY)+v.OffsetY, float64(offX)+v.OffsetX-80, float64(offY)+v.OffsetY+40, tileColor) + screenX1, screenY1 := v.viewport.IsoToScreen(float64(x), float64(y)) + screenX2, screenY2 := v.viewport.IsoToScreen(float64(x+1), float64(y)) + screenX3, screenY3 := v.viewport.IsoToScreen(float64(x), float64(y+1)) - coords := fmt.Sprintf("%v,%v", x, y) - ebitenutil.DebugPrintAt(target, coords, offX+int(v.OffsetX)-10, offY+int(v.OffsetY)+10) + ebitenutil.DrawLine( + target, + float64(screenX1), + float64(screenY1), + float64(screenX2), + float64(screenY2), + tileColor, + ) + + ebitenutil.DrawLine( + target, + float64(screenX1), + float64(screenY1), + float64(screenX3), + float64(screenY3), + tileColor, + ) + + ebitenutil.DebugPrintAt( + target, + fmt.Sprintf("%v,%v", x, y), + screenX1-10, + screenY1+10, + ) if v.ShowTiles > 1 { for i := 1; i <= 4; i++ { - x := (16 * i) - y := (8 * i) - ebitenutil.DrawLine(target, float64(offX-x)+v.OffsetX, float64(offY+y)+v.OffsetY, - float64(offX-x)+v.OffsetX+80, float64(offY+y)+v.OffsetY+40, subtileColor) - ebitenutil.DrawLine(target, float64(offX+x)+v.OffsetX, float64(offY+y)+v.OffsetY, - float64(offX+x)+v.OffsetX-80, float64(offY+y)+v.OffsetY+40, subtileColor) + x := i * 16 + y := i * 8 + + ebitenutil.DrawLine( + target, + float64(screenX1-x), + float64(screenY1+y), + float64(screenX1-x+80), + float64(screenY1+y+40), + subtileColor, + ) + + ebitenutil.DrawLine( + target, + float64(screenX1+x), + float64(screenY1+y), + float64(screenX1+x-80), + float64(screenY1+y+40), + subtileColor, + ) } tile := region.DS1.Tiles[y][x] for i := range tile.Floors { - floorSpec := fmt.Sprintf("f: %v-%v", tile.Floors[i].Style, tile.Floors[i].Sequence) - ebitenutil.DebugPrintAt(target, floorSpec, offX+int(v.OffsetX)-20, offY+int(v.OffsetY)+10+((i+1)*14)) + ebitenutil.DebugPrintAt( + target, + fmt.Sprintf("f: %v-%v", tile.Floors[i].Style, tile.Floors[i].Sequence), + screenX1-20, + screenY1+10+(i+1)*14, + ) } - // wallSpec := fmt.Sprintf("w: %v-%v", tile.Walls[0].Style, tile.Walls[0].Sequence) - // ebitenutil.DebugPrintAt(target, wallSpec, offX+int(v.OffsetX)-20, offY+int(v.OffsetY)+34) } } } diff --git a/d2render/d2mapengine/region.go b/d2render/d2mapengine/region.go index d327db5a..c1bf89bc 100644 --- a/d2render/d2mapengine/region.go +++ b/d2render/d2mapengine/region.go @@ -12,9 +12,9 @@ import ( "github.com/OpenDiablo2/D2Shared/d2data/d2ds1" "github.com/OpenDiablo2/OpenDiablo2/d2core" + "github.com/OpenDiablo2/OpenDiablo2/d2corehelper" "github.com/OpenDiablo2/D2Shared/d2helper" - "github.com/OpenDiablo2/OpenDiablo2/d2corehelper" "github.com/OpenDiablo2/D2Shared/d2common/d2interface" @@ -150,15 +150,14 @@ func (v *Region) UpdateAnimations() { } } -func (v *Region) RenderTile(offsetX, offsetY, tileX, tileY int, layerType d2enum.RegionLayerType, layerIndex int, target *ebiten.Image) { - offsetX -= 80 +func (v *Region) RenderTile(viewport *Viewport, tileX, tileY int, layerType d2enum.RegionLayerType, layerIndex int, target *ebiten.Image) { switch layerType { case d2enum.RegionLayerTypeFloors: - v.renderFloor(v.DS1.Tiles[tileY][tileX].Floors[layerIndex], offsetX, offsetY, target, tileX, tileY) + v.renderFloor(v.DS1.Tiles[tileY][tileX].Floors[layerIndex], viewport, target, tileX, tileY) case d2enum.RegionLayerTypeWalls: - v.renderWall(v.DS1.Tiles[tileY][tileX].Walls[layerIndex], offsetX, offsetY, target, tileX, tileY) + v.renderWall(v.DS1.Tiles[tileY][tileX].Walls[layerIndex], viewport, target, tileX, tileY) case d2enum.RegionLayerTypeShadows: - v.renderShadow(v.DS1.Tiles[tileY][tileX].Shadows[layerIndex], offsetX, offsetY, target, tileX, tileY) + v.renderShadow(v.DS1.Tiles[tileY][tileX].Shadows[layerIndex], viewport, target, tileX, tileY) } } @@ -212,7 +211,7 @@ func (v *Region) getTiles(style, sequence, tileType int32, x, y int, seed int64) return tiles } -func (v *Region) renderFloor(tile d2ds1.FloorShadowRecord, offsetX, offsetY int, target *ebiten.Image, tileX, tileY int) { +func (v *Region) renderFloor(tile d2ds1.FloorShadowRecord, viewport *Viewport, target *ebiten.Image, tileX, tileY int) { var img *ebiten.Image if !tile.Animated { img = v.GetImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex) @@ -223,33 +222,44 @@ func (v *Region) renderFloor(tile d2ds1.FloorShadowRecord, offsetX, offsetY int, log.Printf("Render called on uncached floor {%v,%v}", tile.Style, tile.Sequence) return } + + viewport.PushTranslation(-80, float64(tile.YAdjust)) + screenX, screenY := viewport.WorldToScreen(viewport.GetTranslation()) opts := &ebiten.DrawImageOptions{} - opts.GeoM.Translate(float64(offsetX), float64(offsetY)) - _ = target.DrawImage(img, opts) - return + opts.GeoM.Translate(float64(screenX), float64(screenY)) + target.DrawImage(img, opts) + viewport.PopTranslation() } -func (v *Region) renderWall(tile d2ds1.WallRecord, offsetX, offsetY int, target *ebiten.Image, tileX, tileY int) { +func (v *Region) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target *ebiten.Image, tileX, tileY int) { img := v.GetImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex) if img == nil { log.Printf("Render called on uncached wall {%v,%v,%v}", tile.Style, tile.Sequence, tile.Type) return } + + viewport.PushTranslation(-80, float64(tile.YAdjust)) + screenX, screenY := viewport.WorldToScreen(viewport.GetTranslation()) opts := &ebiten.DrawImageOptions{} - opts.GeoM.Translate(float64(offsetX), float64(offsetY+tile.YAdjust)) + opts.GeoM.Translate(float64(screenX), float64(screenY)) target.DrawImage(img, opts) + viewport.PopTranslation() } -func (v *Region) renderShadow(tile d2ds1.FloorShadowRecord, offsetX, offsetY int, target *ebiten.Image, tileX, tileY int) { +func (v *Region) renderShadow(tile d2ds1.FloorShadowRecord, viewport *Viewport, target *ebiten.Image, tileX, tileY int) { img := v.GetImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex) if img == nil { log.Printf("Render called on uncached shadow {%v,%v}", tile.Style, tile.Sequence) return } + + viewport.PushTranslation(-80, float64(tile.YAdjust)) + screenX, screenY := viewport.WorldToScreen(viewport.GetTranslation()) opts := &ebiten.DrawImageOptions{} - opts.GeoM.Translate(float64(offsetX), float64(offsetY+tile.YAdjust)) + opts.GeoM.Translate(float64(screenX), float64(screenY)) opts.ColorM = d2corehelper.ColorToColorM(color.RGBA{255, 255, 255, 160}) target.DrawImage(img, opts) + viewport.PopTranslation() } func (v *Region) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, tileYOffset int32, tileWidth int32) { diff --git a/d2render/d2mapengine/viewport.go b/d2render/d2mapengine/viewport.go new file mode 100644 index 00000000..dc129209 --- /dev/null +++ b/d2render/d2mapengine/viewport.go @@ -0,0 +1,117 @@ +package d2mapengine + +import ( + "math" + + "github.com/OpenDiablo2/D2Shared/d2common" +) + +type worldTrans struct { + x float64 + y float64 +} + +type Viewport struct { + screenRect d2common.Rectangle + transStack []worldTrans + transCurrent worldTrans + camera *Camera +} + +func NewViewport(x, y, width, height int) *Viewport { + return &Viewport{ + screenRect: d2common.Rectangle{ + Left: x, + Top: y, + Width: width, + Height: height, + }, + } +} + +func (v *Viewport) SetCamera(camera *Camera) { + v.camera = camera +} + +func (v *Viewport) IsoToScreen(x, y float64) (int, int) { + return v.WorldToScreen(v.IsoToWorld(x, y)) +} + +func (v *Viewport) ScreenToIso(x, y int) (float64, float64) { + return v.WorldToIso(v.ScreenToWorld(x, y)) +} + +func (v *Viewport) WorldToIso(x, y float64) (float64, float64) { + isoX := (x/80 + y/40) / 2 + isoY := (y/40 - x/80) / 2 + return isoX, isoY +} + +func (v *Viewport) IsoToWorld(x, y float64) (float64, float64) { + worldX := (x - y) * 80 + worldY := (x + y) * 40 + return worldX, worldY +} + +func (v *Viewport) ScreenToWorld(x, y int) (float64, float64) { + camX, camY := v.getCameraOffset() + screenX := float64(x) + camX - float64(v.screenRect.Left) + screenY := float64(y) + camY - float64(v.screenRect.Top) + return screenX, screenY +} + +func (v *Viewport) WorldToScreen(x, y float64) (int, int) { + camX, camY := v.getCameraOffset() + worldX := int(math.Floor(x - camX + float64(v.screenRect.Left))) + worldY := int(math.Floor(y - camY + float64(v.screenRect.Top))) + return worldX, worldY +} + +func (v *Viewport) IsWorldTileVisbile(x, y float64) bool { + worldX1, worldY1 := v.IsoToWorld(x-2, y) + worldX2, worldY2 := v.IsoToWorld(x+2, y) + return v.IsWorldRectVisible(worldX1, worldY1, worldX2, worldY2) +} + +func (v *Viewport) IsWorldPointVisible(x, y float64) bool { + screenX, screenY := v.WorldToScreen(x, y) + return screenX >= 0 && screenX < v.screenRect.Width && screenY >= 0 && screenY < v.screenRect.Height +} + +func (v *Viewport) IsWorldRectVisible(x1, y1, x2, y2 float64) bool { + screenX1, screenY1 := v.WorldToScreen(x1, y1) + screenX2, screenY2 := v.WorldToScreen(x2, y2) + return !(screenX1 >= v.screenRect.Width || screenX2 < 0 || screenY1 >= v.screenRect.Height || screenY2 < 0) +} + +func (v *Viewport) GetTranslation() (float64, float64) { + return v.transCurrent.x, v.transCurrent.y +} + +func (v *Viewport) PushTranslation(x, y float64) { + v.transStack = append(v.transStack, v.transCurrent) + v.transCurrent.x += x + v.transCurrent.y += y +} + +func (v *Viewport) PopTranslation() { + count := len(v.transStack) + if count == 0 { + panic("empty stack") + } + + v.transCurrent = v.transStack[count-1] + v.transStack = v.transStack[:count-1] +} + +func (v *Viewport) getCameraOffset() (float64, float64) { + var camX, camY float64 + if v.camera != nil { + camX, camY = v.camera.GetPosition() + } + + camX -= float64(v.screenRect.Width / 2) + camY -= float64(v.screenRect.Height / 2) + + return camX, camY +}