2020-06-21 18:40:37 -04:00
|
|
|
package d2maprenderer
|
2019-12-08 22:18:42 -05:00
|
|
|
|
|
|
|
import (
|
2020-09-08 15:58:35 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom"
|
2019-12-08 22:18:42 -05:00
|
|
|
"math"
|
|
|
|
)
|
|
|
|
|
|
|
|
type worldTrans struct {
|
|
|
|
x float64
|
|
|
|
y float64
|
|
|
|
}
|
|
|
|
|
2020-06-21 15:26:07 -04:00
|
|
|
const (
|
2020-07-26 14:52:54 -04:00
|
|
|
center = 0
|
|
|
|
left = 1
|
|
|
|
right = 2
|
|
|
|
tileWidth = 80
|
|
|
|
tileHeight = 40
|
|
|
|
half = 2
|
2020-06-21 15:26:07 -04:00
|
|
|
)
|
|
|
|
|
2020-07-18 23:37:35 -04:00
|
|
|
// Viewport is used for converting vectors between screen (pixel), orthogonal (Camera) and world (isometric) space.
|
2019-12-08 22:18:42 -05:00
|
|
|
type Viewport struct {
|
2020-09-08 15:58:35 -04:00
|
|
|
defaultScreenRect d2geom.Rectangle
|
|
|
|
screenRect d2geom.Rectangle
|
2020-06-21 15:26:07 -04:00
|
|
|
transStack []worldTrans
|
|
|
|
transCurrent worldTrans
|
|
|
|
camera *Camera
|
|
|
|
align int
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// NewViewport creates a new Viewport with the given parameters and returns a pointer to it.
|
2019-12-08 22:18:42 -05:00
|
|
|
func NewViewport(x, y, width, height int) *Viewport {
|
|
|
|
return &Viewport{
|
2020-09-08 15:58:35 -04:00
|
|
|
screenRect: d2geom.Rectangle{
|
2019-12-08 22:18:42 -05:00
|
|
|
Left: x,
|
|
|
|
Top: y,
|
|
|
|
Width: width,
|
2020-06-21 21:49:32 -04:00
|
|
|
Height: height,
|
2019-12-08 22:18:42 -05:00
|
|
|
},
|
2020-09-08 15:58:35 -04:00
|
|
|
defaultScreenRect: d2geom.Rectangle{
|
2020-06-21 15:26:07 -04:00
|
|
|
Left: x,
|
|
|
|
Top: y,
|
|
|
|
Width: width,
|
|
|
|
Height: height,
|
|
|
|
},
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-18 23:37:35 -04:00
|
|
|
// SetCamera sets the current Camera to the given value.
|
2019-12-08 22:18:42 -05:00
|
|
|
func (v *Viewport) SetCamera(camera *Camera) {
|
|
|
|
v.camera = camera
|
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// WorldToScreen returns the screen space for the given world coordinates as two integers.
|
2020-07-23 12:56:50 -04:00
|
|
|
func (v *Viewport) WorldToScreen(x, y float64) (screenX, screenY int) {
|
2019-12-13 00:33:11 -05:00
|
|
|
return v.OrthoToScreen(v.WorldToOrtho(x, y))
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// WorldToScreenF returns the screen space for the given world coordinates as two float64s.
|
2020-07-23 12:56:50 -04:00
|
|
|
func (v *Viewport) WorldToScreenF(x, y float64) (screenX, screenY float64) {
|
2020-06-25 00:39:09 -04:00
|
|
|
return v.OrthoToScreenF(v.WorldToOrtho(x, y))
|
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// ScreenToWorld returns the world position for the given screen coordinates.
|
2020-07-23 12:56:50 -04:00
|
|
|
func (v *Viewport) ScreenToWorld(x, y int) (worldX, worldY float64) {
|
2019-12-13 00:33:11 -05:00
|
|
|
return v.OrthoToWorld(v.ScreenToOrtho(x, y))
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// OrthoToWorld returns the world position for the given orthogonal coordinates.
|
2020-07-23 12:56:50 -04:00
|
|
|
func (v *Viewport) OrthoToWorld(x, y float64) (worldX, worldY float64) {
|
2020-07-26 14:52:54 -04:00
|
|
|
worldX = (x/80 + y/40) / half
|
|
|
|
worldY = (y/40 - x/80) / half
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2019-12-13 00:33:11 -05:00
|
|
|
return worldX, worldY
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// WorldToOrtho returns the orthogonal position for the given world coordinates.
|
2020-07-23 12:56:50 -04:00
|
|
|
func (v *Viewport) WorldToOrtho(x, y float64) (orthoX, orthoY float64) {
|
2020-07-26 14:52:54 -04:00
|
|
|
orthoX = (x - y) * tileWidth
|
|
|
|
orthoY = (x + y) * tileHeight
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2019-12-13 00:33:11 -05:00
|
|
|
return orthoX, orthoY
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// ScreenToOrtho returns the orthogonal position for the given screen coordinates.
|
2020-07-23 12:56:50 -04:00
|
|
|
func (v *Viewport) ScreenToOrtho(x, y int) (orthoX, orthoY float64) {
|
2019-12-08 22:18:42 -05:00
|
|
|
camX, camY := v.getCameraOffset()
|
2020-07-23 12:56:50 -04:00
|
|
|
orthoX = float64(x) + camX - float64(v.screenRect.Left)
|
|
|
|
orthoY = float64(y) + camY - float64(v.screenRect.Top)
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2020-07-23 12:56:50 -04:00
|
|
|
return orthoX, orthoY
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// OrthoToScreen returns the screen position for the given orthogonal coordinates as two ints.
|
2020-07-23 12:56:50 -04:00
|
|
|
func (v *Viewport) OrthoToScreen(x, y float64) (screenX, screenY int) {
|
2019-12-13 00:33:11 -05:00
|
|
|
camOrthoX, camOrthoY := v.getCameraOffset()
|
2020-07-23 12:56:50 -04:00
|
|
|
screenX = int(math.Floor(x - camOrthoX + float64(v.screenRect.Left)))
|
|
|
|
screenY = int(math.Floor(y - camOrthoY + float64(v.screenRect.Top)))
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2020-07-23 12:56:50 -04:00
|
|
|
return screenX, screenY
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// OrthoToScreenF returns the screen position for the given orthogonal coordinates as two float64s.
|
2020-07-23 12:56:50 -04:00
|
|
|
func (v *Viewport) OrthoToScreenF(x, y float64) (screenX, screenY float64) {
|
2020-06-25 00:39:09 -04:00
|
|
|
camOrthoX, camOrthoY := v.getCameraOffset()
|
2020-07-23 12:56:50 -04:00
|
|
|
screenX = x - camOrthoX + float64(v.screenRect.Left)
|
|
|
|
screenY = y - camOrthoY + float64(v.screenRect.Top)
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2020-07-23 12:56:50 -04:00
|
|
|
return screenX, screenY
|
2020-06-25 00:39:09 -04:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// IsTileVisible returns false if no part of the tile is within the game screen.
|
2019-12-13 00:33:11 -05:00
|
|
|
func (v *Viewport) IsTileVisible(x, y float64) bool {
|
|
|
|
orthoX1, orthoY1 := v.WorldToOrtho(x-3, y)
|
|
|
|
orthoX2, orthoY2 := v.WorldToOrtho(x+3, y)
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2019-12-13 00:33:11 -05:00
|
|
|
return v.IsOrthoRectVisible(orthoX1, orthoY1, orthoX2, orthoY2)
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// IsTileRectVisible returns false if none of the tiles rects are within the game screen.
|
2020-09-08 15:58:35 -04:00
|
|
|
func (v *Viewport) IsTileRectVisible(rect d2geom.Rectangle) bool {
|
2020-07-26 14:52:54 -04:00
|
|
|
left := float64((rect.Left - rect.Bottom()) * tileWidth)
|
|
|
|
top := float64((rect.Left + rect.Top) * tileHeight)
|
|
|
|
right := float64((rect.Right() - rect.Top) * tileWidth)
|
|
|
|
bottom := float64((rect.Right() + rect.Bottom()) * tileHeight)
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2019-12-13 00:33:11 -05:00
|
|
|
return v.IsOrthoRectVisible(left, top, right, bottom)
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// IsOrthoRectVisible returns false if the given orthogonal position is outside the game screen.
|
2019-12-13 00:33:11 -05:00
|
|
|
func (v *Viewport) IsOrthoRectVisible(x1, y1, x2, y2 float64) bool {
|
|
|
|
screenX1, screenY1 := v.OrthoToScreen(x1, y1)
|
|
|
|
screenX2, screenY2 := v.OrthoToScreen(x2, y2)
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2020-06-21 16:06:11 -04:00
|
|
|
return !(screenX1 >= v.defaultScreenRect.Width || screenX2 < 0 || screenY1 >= v.defaultScreenRect.Height || screenY2 < 0)
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// GetTranslationOrtho returns the viewport's current orthogonal space translation.
|
2020-07-23 12:56:50 -04:00
|
|
|
func (v *Viewport) GetTranslationOrtho() (orthoX, orthoY float64) {
|
2019-12-08 22:18:42 -05:00
|
|
|
return v.transCurrent.x, v.transCurrent.y
|
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// GetTranslationScreen returns the viewport's current screen space translation.
|
2020-07-23 12:56:50 -04:00
|
|
|
func (v *Viewport) GetTranslationScreen() (screenX, screenY int) {
|
2019-12-13 00:33:11 -05:00
|
|
|
return v.OrthoToScreen(v.transCurrent.x, v.transCurrent.y)
|
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// PushTranslationOrtho adds a new orthogonal translation to the stack.
|
2020-06-21 18:40:37 -04:00
|
|
|
func (v *Viewport) PushTranslationOrtho(x, y float64) *Viewport {
|
2019-12-08 22:18:42 -05:00
|
|
|
v.transStack = append(v.transStack, v.transCurrent)
|
|
|
|
v.transCurrent.x += x
|
|
|
|
v.transCurrent.y += y
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2020-06-21 18:40:37 -04:00
|
|
|
return v
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// PushTranslationWorld adds a new world translation to the stack, converting it to orthogonal space.
|
2019-12-13 00:33:11 -05:00
|
|
|
func (v *Viewport) PushTranslationWorld(x, y float64) {
|
|
|
|
v.PushTranslationOrtho(v.WorldToOrtho(x, y))
|
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// PushTranslationScreen adds a new screen translation to the stack, converting it to orthogonal space.
|
2019-12-13 00:33:11 -05:00
|
|
|
func (v *Viewport) PushTranslationScreen(x, y int) {
|
|
|
|
v.PushTranslationOrtho(v.ScreenToOrtho(x, y))
|
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// PopTranslation pops a translation from the stack.
|
2019-12-08 22:18:42 -05:00
|
|
|
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]
|
|
|
|
}
|
|
|
|
|
2020-07-23 12:56:50 -04:00
|
|
|
func (v *Viewport) getCameraOffset() (camX, camY float64) {
|
2019-12-08 22:18:42 -05:00
|
|
|
if v.camera != nil {
|
2020-07-18 23:37:35 -04:00
|
|
|
camPosition := v.camera.GetPosition()
|
|
|
|
camX, camY = camPosition.X(), camPosition.Y()
|
2019-12-08 22:18:42 -05:00
|
|
|
}
|
|
|
|
|
2020-07-26 14:52:54 -04:00
|
|
|
camX -= float64(v.screenRect.Width / half)
|
|
|
|
camY -= float64(v.screenRect.Height / half)
|
2019-12-08 22:18:42 -05:00
|
|
|
|
|
|
|
return camX, camY
|
|
|
|
}
|
2020-06-21 15:26:07 -04:00
|
|
|
|
|
|
|
func (v *Viewport) toLeft() {
|
|
|
|
if v.align == left {
|
|
|
|
return
|
|
|
|
}
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2020-07-26 14:52:54 -04:00
|
|
|
v.screenRect.Width = v.defaultScreenRect.Width / half
|
|
|
|
v.screenRect.Left = v.defaultScreenRect.Left + v.defaultScreenRect.Width/half
|
2020-06-21 16:06:11 -04:00
|
|
|
v.align = left
|
2020-06-21 15:26:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v *Viewport) toRight() {
|
|
|
|
if v.align == right {
|
|
|
|
return
|
|
|
|
}
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2020-07-26 14:52:54 -04:00
|
|
|
v.screenRect.Width = v.defaultScreenRect.Width / half
|
2020-06-21 16:06:11 -04:00
|
|
|
v.align = right
|
2020-06-21 15:26:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v *Viewport) resetAlign() {
|
|
|
|
if v.align == center {
|
|
|
|
return
|
|
|
|
}
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2020-06-21 15:26:07 -04:00
|
|
|
v.screenRect.Width = v.defaultScreenRect.Width
|
|
|
|
v.screenRect.Left = v.defaultScreenRect.Left
|
2020-06-21 16:06:11 -04:00
|
|
|
v.align = center
|
2020-06-21 15:26:07 -04:00
|
|
|
}
|