159 lines
6.0 KiB
Go
159 lines
6.0 KiB
Go
|
package play
|
||
|
|
||
|
import (
|
||
|
"opslag.de/schobers/geom"
|
||
|
)
|
||
|
|
||
|
// IsometricProjection represents an 2D area (view) that contains isometric tiles.
|
||
|
type IsometricProjection struct {
|
||
|
center geom.PointF32 // tile coordinate
|
||
|
zoom float32 // factor a tile is blown up (negative is smaller, possitive is larger)
|
||
|
zoomInverse float32 // 1/zoom; calculated
|
||
|
|
||
|
tileSize geom.PointF32 // size of a single tile (maximum width & height difference of its corners)
|
||
|
viewBounds geom.RectangleF32 // bounds of the view (screen coordinates)
|
||
|
viewCenter geom.PointF32 // center of view; calculated
|
||
|
|
||
|
tileSizeTransformed geom.PointF32 // calculated
|
||
|
tileToViewTransformation geom.PointF32 // calculated
|
||
|
viewToTileTransformation geom.PointF32 // calculated
|
||
|
}
|
||
|
|
||
|
// NewIsometricProjection creates a new isometric projection. By default the tile with the coordinate (0, 0) will be centered in the viewBounds. The tile size is represented with maximum width & height difference of its corners.
|
||
|
func NewIsometricProjection(tileSize geom.PointF32, viewBounds geom.RectangleF32) *IsometricProjection {
|
||
|
p := &IsometricProjection{zoom: 1, tileSize: tileSize, viewBounds: viewBounds}
|
||
|
p.update()
|
||
|
return p
|
||
|
}
|
||
|
|
||
|
func (p *IsometricProjection) update() {
|
||
|
if p.zoom == 0 {
|
||
|
p.zoom = 1
|
||
|
}
|
||
|
p.zoomInverse = 1 / p.zoom
|
||
|
|
||
|
p.viewCenter = p.viewBounds.Center()
|
||
|
|
||
|
p.tileSizeTransformed = p.tileSize.Mul(p.zoom)
|
||
|
p.tileToViewTransformation = p.tileSize.Mul(.5 * p.zoom)
|
||
|
p.viewToTileTransformation = geom.PtF32(1/p.tileSizeTransformed.X, 1/p.tileSizeTransformed.Y)
|
||
|
}
|
||
|
|
||
|
// Center gives back the coordinate of the center tile
|
||
|
func (p *IsometricProjection) Center() geom.PointF32 { return p.center }
|
||
|
|
||
|
// Enumerate enumerates all tiles in the set view bounds and calls action for every tile.
|
||
|
func (p *IsometricProjection) Enumerate(action func(tile geom.PointF32, view geom.PointF32)) {
|
||
|
p.EnumerateInt(func(tile geom.Point, view geom.PointF32) {
|
||
|
action(tile.ToF32(), view)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// EnumerateInt enumerates all tiles in the set view bounds and calls action for every tile.
|
||
|
func (p *IsometricProjection) EnumerateInt(action func(tile geom.Point, view geom.PointF32)) {
|
||
|
visible := p.viewBounds
|
||
|
visible.Max.Y += p.tileSize.Y * p.zoom
|
||
|
topLeft := p.ViewToTile(geom.PtF32(visible.Min.X, visible.Min.Y))
|
||
|
topRight := p.ViewToTile(geom.PtF32(visible.Max.X, visible.Min.Y))
|
||
|
bottomLeft := p.ViewToTile(geom.PtF32(visible.Min.X, visible.Max.Y))
|
||
|
bottomRight := p.ViewToTile(geom.PtF32(visible.Max.X, visible.Max.Y))
|
||
|
minY, maxY := int(geom.Floor32(topRight.Y)), int(geom.Ceil32(bottomLeft.Y))
|
||
|
minX, maxX := int(geom.Floor32(topLeft.X)), int(geom.Ceil32(bottomRight.X))
|
||
|
tileOffset := p.tileSizeTransformed.Mul(.5)
|
||
|
for y := minY; y <= maxY; y++ {
|
||
|
for x := minX; x <= maxX; x++ {
|
||
|
tile := geom.Pt(x, y)
|
||
|
view := p.TileToView(tile.ToF32())
|
||
|
if view.X+tileOffset.X < visible.Min.X || view.Y+tileOffset.Y < visible.Min.Y {
|
||
|
continue
|
||
|
}
|
||
|
if view.X-tileOffset.X > visible.Max.X || view.Y-tileOffset.Y > visible.Max.Y {
|
||
|
break
|
||
|
}
|
||
|
action(tile, view)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MoveCenterTo moves the center of the projection to the given tile.
|
||
|
func (p *IsometricProjection) MoveCenterTo(tile geom.PointF32) {
|
||
|
p.center = tile
|
||
|
p.update()
|
||
|
}
|
||
|
|
||
|
// Pan translates the center of the projection with the given delta in view coordinates.
|
||
|
func (p *IsometricProjection) Pan(delta geom.PointF32) {
|
||
|
p.MoveCenterTo(p.center.Add(delta.Mul(p.zoomInverse)))
|
||
|
}
|
||
|
|
||
|
// SetTileSize sets the size of a single tile (maximum width & height difference of its corners).
|
||
|
func (p *IsometricProjection) SetTileSize(size geom.PointF32) {
|
||
|
p.tileSize = size
|
||
|
p.update()
|
||
|
}
|
||
|
|
||
|
// SetViewBounds sets the bounds of the view coordinates. Used to calculate the center with & for calculating the visible tiles.
|
||
|
func (p *IsometricProjection) SetViewBounds(bounds geom.RectangleF32) {
|
||
|
p.viewBounds = bounds
|
||
|
p.update()
|
||
|
}
|
||
|
|
||
|
// SetZoom changes the zoom to and keeps the around (tile) coordinate on the same position.
|
||
|
func (p *IsometricProjection) SetZoom(around geom.PointF32, zoom float32) {
|
||
|
if p.zoom == zoom {
|
||
|
return
|
||
|
}
|
||
|
p.center = around.Sub(around.Sub(p.center).Mul(p.zoom / zoom))
|
||
|
p.zoom = zoom
|
||
|
p.update()
|
||
|
}
|
||
|
|
||
|
// TileInt gives the integer tile coordinate.
|
||
|
func (p *IsometricProjection) TileInt(tile geom.PointF32) geom.Point {
|
||
|
return geom.Pt(int(geom.Round32(tile.X)), int(geom.Round32(tile.Y)))
|
||
|
}
|
||
|
|
||
|
// TileToView transforms the tile coordinate to the corresponding view coordinate.
|
||
|
func (p *IsometricProjection) TileToView(tile geom.PointF32) geom.PointF32 {
|
||
|
translated := tile.Sub(p.center)
|
||
|
return p.viewCenter.Add2D((translated.X-translated.Y)*p.tileToViewTransformation.X, (translated.X+translated.Y)*p.tileToViewTransformation.Y)
|
||
|
}
|
||
|
|
||
|
// ViewCenter returns the center of the view (calculated from the set view bounds).
|
||
|
func (p *IsometricProjection) ViewCenter() geom.PointF32 { return p.viewCenter }
|
||
|
|
||
|
// ViewToTile transforms the view coordinate to the corresponding tile coordinate.
|
||
|
func (p *IsometricProjection) ViewToTile(view geom.PointF32) geom.PointF32 {
|
||
|
return p.ViewToTileRelative(view.Sub(p.viewCenter)).Add(p.center)
|
||
|
}
|
||
|
|
||
|
// ViewToTileInt transforms the view coordinate to the corresponding integer tile coordinate.
|
||
|
func (p *IsometricProjection) ViewToTileInt(view geom.PointF32) geom.Point {
|
||
|
tile := p.ViewToTile(view)
|
||
|
return p.TileInt(tile)
|
||
|
}
|
||
|
|
||
|
// ViewToTileRelative transforms the relative (to 0,0) view coordinate to the corresponding tile coordinate
|
||
|
func (p *IsometricProjection) ViewToTileRelative(view geom.PointF32) geom.PointF32 {
|
||
|
return geom.PtF32(view.X*p.viewToTileTransformation.X+view.Y*p.viewToTileTransformation.Y, -view.X*p.viewToTileTransformation.X+view.Y*p.viewToTileTransformation.Y)
|
||
|
}
|
||
|
|
||
|
// Zoom returns the current zoom.
|
||
|
func (p *IsometricProjection) Zoom() float32 { return p.zoom }
|
||
|
|
||
|
// ZoomIn zooms in around the given tile coordinate.
|
||
|
func (p *IsometricProjection) ZoomIn(around geom.PointF32) {
|
||
|
if p.zoom >= 2 {
|
||
|
return
|
||
|
}
|
||
|
p.SetZoom(around, 2*p.zoom)
|
||
|
}
|
||
|
|
||
|
// ZoomOut zooms in around the given tile coordinate.
|
||
|
func (p *IsometricProjection) ZoomOut(around geom.PointF32) {
|
||
|
if p.zoom <= .25 {
|
||
|
return
|
||
|
}
|
||
|
p.SetZoom(around, .5*p.zoom)
|
||
|
}
|