1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-18 21:25: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
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)
}

View File

@ -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) {

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/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 &region
}
return &region
}
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)
}
}
}

View File

@ -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) {

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
}