1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-26 17:15:24 +00:00

Create viewport and camera types (#246)

This commit is contained in:
Alex Yatskov 2019-12-08 19:18:42 -08:00 committed by Tim Sarbin
parent 9a8e16c411
commit 1262c80e6b
6 changed files with 298 additions and 105 deletions

View File

@ -1,6 +1,8 @@
package d2scene package d2scene
import ( import (
"image/color"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum" "github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface" "github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource" "github.com/OpenDiablo2/D2Shared/d2common/d2resource"
@ -14,7 +16,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine" "github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
"image/color"
) )
type Game struct { type Game struct {
@ -128,9 +129,7 @@ func (v *Game) Update(tickTime float64) {
} }
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
mx, my := ebiten.CursorPosition() px, py := v.mapEngine.ScreenToIso(ebiten.CursorPosition())
px, py := d2helper.ScreenToIso(float64(mx)-v.mapEngine.OffsetX, float64(my)-v.mapEngine.OffsetY)
v.mapEngine.Hero.AnimatedEntity.SetTarget(px*5, py*5, 1) v.mapEngine.Hero.AnimatedEntity.SetTarget(px*5, py*5, 1)
} }

View File

@ -172,9 +172,9 @@ func (v *MapEngineTest) Unload() {
func (v *MapEngineTest) Render(screen *ebiten.Image) { func (v *MapEngineTest) Render(screen *ebiten.Image) {
v.mapEngine.Render(screen) v.mapEngine.Render(screen)
actualX := float64(v.uiManager.CursorX) - v.mapEngine.OffsetX actualX := v.uiManager.CursorX
actualY := float64(v.uiManager.CursorY) - v.mapEngine.OffsetY actualY := v.uiManager.CursorY
tileX, tileY := d2helper.ScreenToIso(actualX, actualY) tileX, tileY := v.mapEngine.ScreenToIso(actualX, actualY)
subtileX := int(math.Ceil(math.Mod((tileX*10), 10))) / 2 subtileX := int(math.Ceil(math.Mod((tileX*10), 10))) / 2
subtileY := int(math.Ceil(math.Mod((tileY*10), 10))) / 2 subtileY := int(math.Ceil(math.Mod((tileY*10), 10))) / 2
curRegion := v.mapEngine.GetRegionAt(int(tileX), int(tileY)) curRegion := v.mapEngine.GetRegionAt(int(tileX), int(tileY))
@ -182,8 +182,8 @@ func (v *MapEngineTest) Render(screen *ebiten.Image) {
return return
} }
line := fmt.Sprintf("%d, %d (Tile %d.%d, %d.%d)", line := fmt.Sprintf("%d, %d (Tile %d.%d, %d.%d)",
int(math.Ceil(actualX)), actualX,
int(math.Ceil(actualY)), actualY,
int(math.Floor(tileX))-curRegion.Rect.Left, int(math.Floor(tileX))-curRegion.Rect.Left,
subtileX, subtileX,
int(math.Floor(tileY))-curRegion.Rect.Top, int(math.Floor(tileY))-curRegion.Rect.Top,
@ -217,33 +217,22 @@ func (v *MapEngineTest) Update(tickTime float64) {
ctrlPressed := v.uiManager.KeyPressed(ebiten.KeyControl) ctrlPressed := v.uiManager.KeyPressed(ebiten.KeyControl)
shiftPressed := v.uiManager.KeyPressed(ebiten.KeyShift) 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.KeyDown) {
if v.uiManager.KeyPressed(ebiten.KeyShift) { v.mapEngine.MoveCameraBy(0, moveSpeed*tickTime)
v.mapEngine.OffsetY -= tickTime * 200
} else {
v.mapEngine.OffsetY -= tickTime * 800
}
} }
if v.uiManager.KeyPressed(ebiten.KeyUp) { if v.uiManager.KeyPressed(ebiten.KeyUp) {
if v.uiManager.KeyPressed(ebiten.KeyShift) { v.mapEngine.MoveCameraBy(0, -moveSpeed*tickTime)
v.mapEngine.OffsetY += tickTime * 200
} else {
v.mapEngine.OffsetY += tickTime * 800
}
} }
if v.uiManager.KeyPressed(ebiten.KeyLeft) { if v.uiManager.KeyPressed(ebiten.KeyLeft) {
if v.uiManager.KeyPressed(ebiten.KeyShift) { v.mapEngine.MoveCameraBy(-moveSpeed*tickTime, 0)
v.mapEngine.OffsetX += tickTime * 200
} else {
v.mapEngine.OffsetX += tickTime * 800
}
} }
if v.uiManager.KeyPressed(ebiten.KeyRight) { if v.uiManager.KeyPressed(ebiten.KeyRight) {
if v.uiManager.KeyPressed(ebiten.KeyShift) { v.mapEngine.MoveCameraBy(moveSpeed*tickTime, 0)
v.mapEngine.OffsetX -= tickTime * 200
} else {
v.mapEngine.OffsetX -= tickTime * 800
}
} }
if inpututil.IsKeyJustPressed(ebiten.KeyF7) { if inpututil.IsKeyJustPressed(ebiten.KeyF7) {

View File

@ -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
}

View File

@ -7,8 +7,6 @@ import (
"github.com/OpenDiablo2/D2Shared/d2common/d2enum" "github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface" "github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core" "github.com/OpenDiablo2/OpenDiablo2/d2core"
@ -36,27 +34,31 @@ type Engine struct {
fileProvider d2interface.FileProvider fileProvider d2interface.FileProvider
region int region int
regions []EngineRegion regions []EngineRegion
OffsetX float64
OffsetY float64
ShowTiles int ShowTiles int
Hero *d2core.Hero Hero *d2core.Hero
viewport *Viewport
camera Camera
} }
func CreateMapEngine(gameState *d2core.GameState, soundManager *d2audio.Manager, fileProvider d2interface.FileProvider) *Engine { func CreateMapEngine(gameState *d2core.GameState, soundManager *d2audio.Manager, fileProvider d2interface.FileProvider) *Engine {
result := &Engine{ engine := &Engine{
gameState: gameState, gameState: gameState,
soundManager: soundManager, soundManager: soundManager,
fileProvider: fileProvider, fileProvider: fileProvider,
regions: make([]EngineRegion, 0), regions: make([]EngineRegion, 0),
viewport: NewViewport(0, 0, 800, 600),
} }
return result
engine.viewport.SetCamera(&engine.camera)
return engine
} }
func (v *Engine) Region() *EngineRegion { func (v *Engine) Region() *EngineRegion {
return &v.regions[v.region] return &v.regions[v.region]
} }
func (v *Engine) SetRegion(region int) { func (v *Engine) SetRegion(region int) {
v.region = region v.region = region
} }
@ -65,8 +67,19 @@ func (v *Engine) GetRegion(regionIndex int) *EngineRegion {
} }
func (v *Engine) CenterCameraOn(x, y float64) { func (v *Engine) CenterCameraOn(x, y float64) {
v.OffsetX = -(x - 400) v.camera.MoveTo(x, y)
v.OffsetY = -(y - 300) }
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) { func (v *Engine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int, fileIndex int) {
@ -109,36 +122,31 @@ func (v *Engine) GenerateAct1Overworld() {
v.GenTilesCache(&v.regions[i]) v.GenTilesCache(&v.regions[i])
} }
sx, sy := d2helper.IsoToScreen(region.StartX, region.StartY, 0, 0) v.camera.MoveTo(v.viewport.IsoToWorld(region.StartX, region.StartY))
v.OffsetX = sx - 400
v.OffsetY = sy - 300
} }
func (v *Engine) GetRegionAt(x, y int) *EngineRegion { func (v *Engine) GetRegionAt(x, y int) *EngineRegion {
if v.regions == nil {
return nil
}
for _, region := range v.regions { for _, region := range v.regions {
if !region.Rect.IsInRect(x, y) { if region.Rect.IsInRect(x, y) {
continue return &region
} }
return &region
} }
return nil return nil
} }
func (v *Engine) Render(target *ebiten.Image) { func (v *Engine) Render(target *ebiten.Image) {
for _, region := range v.regions { for _, region := range v.regions {
// X position of leftmost point of region // 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 // 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 // X of right
right := (region.Rect.Right() - region.Rect.Top) * 80 right := float64((region.Rect.Right() - region.Rect.Top) * 80)
// Y of bottom // 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) v.RenderRegion(region, target)
} }
} }
@ -191,56 +199,61 @@ func (v *Engine) GenTilesCache(region *EngineRegion) {
func (v *Engine) RenderRegion(region EngineRegion, target *ebiten.Image) { func (v *Engine) RenderRegion(region EngineRegion, target *ebiten.Image) {
for tileIdx := range region.Tiles { 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 v.viewport.IsWorldTileVisbile(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top)) {
if sx > -160 && sy > -380 && sx <= 880 && sy <= 1240 {
region.Region.UpdateAnimations() 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 { 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 v.viewport.IsWorldTileVisbile(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top)) {
if sx > -160 && sy > -380 && sx <= 880 && sy <= 1240 { v.viewport.PushTranslation(float64(region.Tiles[tileIdx].offX), float64(region.Tiles[tileIdx].offY))
v.RenderPass2(region.Region, region.Tiles[tileIdx].offX, region.Tiles[tileIdx].offY, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target) v.RenderPass2(region.Region, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target)
v.viewport.PopTranslation()
} }
} }
for tileIdx := range region.Tiles { 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 v.viewport.IsWorldTileVisbile(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top)) {
if sx > -160 && sy > -380 && sx <= 880 && sy <= 1240 { v.viewport.PushTranslation(float64(region.Tiles[tileIdx].offX), float64(region.Tiles[tileIdx].offY))
v.RenderPass3(region.Region, region.Tiles[tileIdx].offX, region.Tiles[tileIdx].offY, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target) 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] tile := region.DS1.Tiles[y][x]
// Draw lower walls // Draw lower walls
for i := range tile.Walls { for i := range tile.Walls {
if !tile.Walls[i].Type.LowerWall() || tile.Walls[i].Prop1 == 0 || tile.Walls[i].Hidden { if !tile.Walls[i].Type.LowerWall() || tile.Walls[i].Prop1 == 0 || tile.Walls[i].Hidden {
continue 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 { for i := range tile.Floors {
if tile.Floors[i].Hidden || tile.Floors[i].Prop1 == 0 { if tile.Floors[i].Hidden || tile.Floors[i].Prop1 == 0 {
continue 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 { for i := range tile.Shadows {
if tile.Shadows[i].Hidden || tile.Shadows[i].Prop1 == 0 { if tile.Shadows[i].Hidden || tile.Shadows[i].Prop1 == 0 {
continue 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] tile := region.DS1.Tiles[y][x]
// Draw upper walls // 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 { if !tile.Walls[i].Type.UpperWall() || tile.Walls[i].Hidden {
continue 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 { for _, obj := range region.AnimationEntities {
if obj.TileX == x && obj.TileY == y { 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 { for _, npc := range region.NPCs {
if npc.AnimatedEntity.TileX == x && npc.AnimatedEntity.TileY == y { 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 { 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] tile := region.DS1.Tiles[y][x]
// Draw ceilings // Draw ceilings
for i := range tile.Walls { for i := range tile.Walls {
if tile.Walls[i].Type != d2enum.Roof { if tile.Walls[i].Type != d2enum.Roof {
continue 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 { if v.ShowTiles > 0 {
subtileColor := color.RGBA{80, 80, 255, 100} subtileColor := color.RGBA{80, 80, 255, 100}
tileColor := color.RGBA{255, 255, 255, 255} 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) screenX1, screenY1 := v.viewport.IsoToScreen(float64(x), float64(y))
ebitenutil.DrawLine(target, float64(offX)+v.OffsetX, float64(offY)+v.OffsetY, float64(offX)+v.OffsetX-80, float64(offY)+v.OffsetY+40, tileColor) 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.DrawLine(
ebitenutil.DebugPrintAt(target, coords, offX+int(v.OffsetX)-10, offY+int(v.OffsetY)+10) 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 { if v.ShowTiles > 1 {
for i := 1; i <= 4; i++ { for i := 1; i <= 4; i++ {
x := (16 * i) x := i * 16
y := (8 * i) y := i * 8
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(
ebitenutil.DrawLine(target, float64(offX+x)+v.OffsetX, float64(offY+y)+v.OffsetY, target,
float64(offX+x)+v.OffsetX-80, float64(offY+y)+v.OffsetY+40, subtileColor) 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] tile := region.DS1.Tiles[y][x]
for i := range tile.Floors { for i := range tile.Floors {
floorSpec := fmt.Sprintf("f: %v-%v", tile.Floors[i].Style, tile.Floors[i].Sequence) ebitenutil.DebugPrintAt(
ebitenutil.DebugPrintAt(target, floorSpec, offX+int(v.OffsetX)-20, offY+int(v.OffsetY)+10+((i+1)*14)) 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)
} }
} }
} }

View File

@ -12,9 +12,9 @@ import (
"github.com/OpenDiablo2/D2Shared/d2data/d2ds1" "github.com/OpenDiablo2/D2Shared/d2data/d2ds1"
"github.com/OpenDiablo2/OpenDiablo2/d2core" "github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/OpenDiablo2/d2corehelper"
"github.com/OpenDiablo2/D2Shared/d2helper" "github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2corehelper"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface" "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) { func (v *Region) RenderTile(viewport *Viewport, tileX, tileY int, layerType d2enum.RegionLayerType, layerIndex int, target *ebiten.Image) {
offsetX -= 80
switch layerType { switch layerType {
case d2enum.RegionLayerTypeFloors: 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: 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: 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 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 var img *ebiten.Image
if !tile.Animated { if !tile.Animated {
img = v.GetImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex) 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) log.Printf("Render called on uncached floor {%v,%v}", tile.Style, tile.Sequence)
return return
} }
viewport.PushTranslation(-80, float64(tile.YAdjust))
screenX, screenY := viewport.WorldToScreen(viewport.GetTranslation())
opts := &ebiten.DrawImageOptions{} opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(float64(offsetX), float64(offsetY)) opts.GeoM.Translate(float64(screenX), float64(screenY))
_ = target.DrawImage(img, opts) target.DrawImage(img, opts)
return 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) img := v.GetImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex)
if img == nil { if img == nil {
log.Printf("Render called on uncached wall {%v,%v,%v}", tile.Style, tile.Sequence, tile.Type) log.Printf("Render called on uncached wall {%v,%v,%v}", tile.Style, tile.Sequence, tile.Type)
return return
} }
viewport.PushTranslation(-80, float64(tile.YAdjust))
screenX, screenY := viewport.WorldToScreen(viewport.GetTranslation())
opts := &ebiten.DrawImageOptions{} opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(float64(offsetX), float64(offsetY+tile.YAdjust)) opts.GeoM.Translate(float64(screenX), float64(screenY))
target.DrawImage(img, opts) 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) img := v.GetImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex)
if img == nil { if img == nil {
log.Printf("Render called on uncached shadow {%v,%v}", tile.Style, tile.Sequence) log.Printf("Render called on uncached shadow {%v,%v}", tile.Style, tile.Sequence)
return return
} }
viewport.PushTranslation(-80, float64(tile.YAdjust))
screenX, screenY := viewport.WorldToScreen(viewport.GetTranslation())
opts := &ebiten.DrawImageOptions{} 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}) opts.ColorM = d2corehelper.ColorToColorM(color.RGBA{255, 255, 255, 160})
target.DrawImage(img, opts) target.DrawImage(img, opts)
viewport.PopTranslation()
} }
func (v *Region) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, tileYOffset int32, tileWidth int32) { func (v *Region) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, tileYOffset int32, tileWidth int32) {

View File

@ -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
}