Added IsometricProjection.
This commit is contained in:
parent
2238f8749a
commit
7793fe823f
158
play/isometricprojection.go
Normal file
158
play/isometricprojection.go
Normal file
@ -0,0 +1,158 @@
|
||||
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)
|
||||
}
|
36
play/isometricprojection_test.go
Normal file
36
play/isometricprojection_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
package play
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
func createIsometricProjection() *IsometricProjection {
|
||||
return NewIsometricProjection(geom.PtF32(23, 11), geom.RectRelF32(0, 0, 160, 160))
|
||||
}
|
||||
|
||||
func TestViewToTile(t *testing.T) {
|
||||
p := createIsometricProjection()
|
||||
assert.Equal(t, geom.PtF32(0, 0), p.ViewToTile(geom.PtF32(80, 80)))
|
||||
assert.Equal(t, geom.PtF32(-1, 1), p.ViewToTile(geom.PtF32(57, 80)))
|
||||
assert.Equal(t, geom.PtF32(2, 2), p.ViewToTile(geom.PtF32(80, 102)))
|
||||
assert.Equal(t, geom.PtF32(-1, -3), p.ViewToTile(geom.PtF32(103, 58)))
|
||||
}
|
||||
|
||||
func TestViewToTileInt(t *testing.T) {
|
||||
p := createIsometricProjection()
|
||||
assert.Equal(t, geom.Pt(0, 0), p.ViewToTileInt(geom.PtF32(80, 80)))
|
||||
assert.Equal(t, geom.Pt(0, 0), p.ViewToTileInt(geom.PtF32(69, 80)))
|
||||
assert.Equal(t, geom.Pt(-1, 1), p.ViewToTileInt(geom.PtF32(68, 80)))
|
||||
}
|
||||
|
||||
func TestTileToView(t *testing.T) {
|
||||
p := createIsometricProjection()
|
||||
assert.Equal(t, geom.PtF32(80, 80), p.TileToView(geom.PtF32(0, 0)))
|
||||
assert.Equal(t, geom.PtF32(57, 80), p.TileToView(geom.PtF32(-1, 1)))
|
||||
assert.Equal(t, geom.PtF32(80, 102), p.TileToView(geom.PtF32(2, 2)))
|
||||
assert.Equal(t, geom.PtF32(103, 58), p.TileToView(geom.PtF32(-1, -3)))
|
||||
}
|
Loading…
Reference in New Issue
Block a user