tins2020/projection.go

128 lines
4.0 KiB
Go

package tins2020
import (
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg/ui"
)
func mapToTile(q geom.PointF32) geom.Point {
return geom.Pt(int(geom.Round32(q.X)), int(geom.Round32(q.Y)))
}
type projection struct {
center geom.PointF32
zoom float32
zoomInv float32
windowInteractRect geom.Rectangle
windowVisibleRect geom.Rectangle
tileScreenDelta geom.PointF32
tileScreenDeltaInv geom.PointF32
tileScreenOffset geom.Point
tileScreenSize geom.Point
tileFitScreenSize geom.Point
windowCenter geom.Point
}
func newProjection() projection {
return projection{zoom: 1, tileScreenDelta: geom.PtF32(64, 32), tileScreenDeltaInv: geom.PtF32(1./64, 1./32)}
}
func (p *projection) mapToScreen(x, y int) geom.Point {
return p.mapToScreenF(float32(x), float32(y))
}
func (p *projection) mapToScreenF(x, y float32) geom.Point {
translated := geom.PtF32(x-p.center.X, y-p.center.Y)
return geom.Pt(p.windowCenter.X+int((translated.X-translated.Y)*64*p.zoom), p.windowCenter.Y+int((translated.X+translated.Y)*32*p.zoom))
}
func (p *projection) screenToMap(x, y int) geom.PointF32 {
pos := p.screenToMapRel(x-p.windowCenter.X, y-p.windowCenter.Y)
return p.center.Add(pos)
}
func (p *projection) screenToMapInt(x, y int) geom.Point {
pos := p.screenToMap(x, y)
return mapToTile(pos)
}
func (p *projection) screenToMapRel(x, y int) geom.PointF32 {
normX := p.zoomInv * float32(x)
normY := p.zoomInv * float32(y)
return geom.PtF32(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY))
}
func (p *projection) screenToTileFitRect(pos geom.Point) geom.Rectangle {
return geom.RectRel(pos.X-p.tileFitScreenSize.X, pos.Y-p.tileFitScreenSize.Y, 2*p.tileFitScreenSize.X, 2*p.tileFitScreenSize.Y)
}
func (p *projection) screenToTileRect(pos geom.Point) geom.Rectangle {
return geom.RectRel(pos.X-p.tileScreenOffset.X, pos.Y-p.tileScreenOffset.Y, p.tileScreenSize.X, p.tileScreenSize.Y)
}
func (p *projection) update(renderer ui.Renderer) {
p.zoomInv = 1 / p.zoom
p.tileScreenOffset = geom.Pt(int(p.zoom*64), int(p.zoom*112))
p.tileScreenSize = geom.Pt(int(p.zoom*128), int(p.zoom*160))
p.tileFitScreenSize = geom.Pt(int(p.zoom*64), int(p.zoom*32))
windowF32 := renderer.Size()
window := geom.Pt(int(windowF32.X), int(windowF32.Y))
p.windowCenter = geom.Pt(window.X/2, window.Y/2)
p.windowInteractRect = geom.Rect(buttonBarWidth, 64, window.X-buttonBarWidth, window.Y)
p.windowVisibleRect = geom.Rect(buttonBarWidth, 0, window.X-buttonBarWidth, window.Y+p.tileScreenSize.Y) // Adding a tile height to the bottom for trees that stick out from the cells below.
}
func (p *projection) visibleTiles(action func(int, int, geom.Point)) {
visible := p.windowVisibleRect
topLeft := p.screenToMap(visible.Min.X, visible.Min.Y)
topRight := p.screenToMap(visible.Max.X, visible.Min.Y)
bottomLeft := p.screenToMap(visible.Min.X, visible.Max.Y)
bottomRight := p.screenToMap(visible.Max.Y, visible.Max.Y)
minY, maxY := int(Floor32(topRight.Y)), int(Ceil32(bottomLeft.Y))
minX, maxX := int(Floor32(topLeft.X)), int(Ceil32(bottomRight.X))
for y := minY; y <= maxY; y++ {
for x := minX; x <= maxX; x++ {
pos := p.mapToScreen(x, y)
rectFit := p.screenToTileFitRect(pos)
if rectFit.Max.X < visible.Min.X || rectFit.Max.Y < visible.Min.Y {
continue
}
if rectFit.Min.X > visible.Max.X || rectFit.Min.Y > visible.Max.Y {
break
}
action(x, y, pos)
}
}
}
func (p *projection) Pan(ctx ui.Context, delta geom.PointF32) {
p.center = p.center.Add(delta.Mul(p.zoomInv))
p.update(ctx.Renderer())
}
func (p *projection) SetZoom(ctx ui.Context, center geom.PointF32, zoom float32) {
if p.zoom == zoom {
return
}
p.center = center.Sub(center.Sub(p.center).Mul(p.zoom / zoom))
p.zoom = zoom
p.update(ctx.Renderer())
}
func (p *projection) ZoomOut(ctx ui.Context, center geom.PointF32) {
if p.zoom <= .25 {
return
}
p.SetZoom(ctx, center, .5*p.zoom)
}
func (p *projection) ZoomIn(ctx ui.Context, center geom.PointF32) {
if p.zoom >= 2 {
return
}
p.SetZoom(ctx, center, 2*p.zoom)
}