Refactored terrainRenderer to use play.IsometricProjection.

This commit is contained in:
Sander Schobers 2020-05-23 10:18:14 +02:00
parent f065317c76
commit 4519417b4e
2 changed files with 56 additions and 175 deletions

View File

@ -1,127 +0,0 @@
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.X, 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)
}

View File

@ -3,6 +3,8 @@ package tins2020
import ( import (
"fmt" "fmt"
"opslag.de/schobers/zntg/play"
"opslag.de/schobers/geom" "opslag.de/schobers/geom"
"opslag.de/schobers/zntg/ui" "opslag.de/schobers/zntg/ui"
) )
@ -12,29 +14,28 @@ type terrainRenderer struct {
game *Game game *Game
hover *geom.Point hover *geom.Point
project projection viewBounds geom.RectangleF32
interactBounds geom.RectangleF32
isometric *play.IsometricProjection
drag ui.Dragable drag ui.Dragable
} }
func NewTerrainRenderer(game *Game) ui.Control { func NewTerrainRenderer(game *Game) ui.Control {
renderer := &terrainRenderer{game: game, project: newProjection()} renderer := &terrainRenderer{game: game, isometric: play.NewIsometricProjection(geom.PtF32(128, 64), geom.RectF32(0, 0, 100, 100))}
renderer.game.CenterChanged().AddHandler(func(ctx ui.Context, state interface{}) { renderer.game.CenterChanged().AddHandler(func(ctx ui.Context, state interface{}) {
center := state.(geom.Point) center := state.(geom.Point)
renderer.project.center = center.ToF32() renderer.isometric.MoveCenterTo(center.ToF32())
renderer.project.update(ctx.Renderer())
}) })
// renderer.project.update(ctx.Renderer)
return renderer return renderer
} }
func (r *terrainRenderer) Arrange(ctx ui.Context, _ geom.RectangleF32, _ geom.PointF32, _ ui.Control) { func (r *terrainRenderer) Arrange(ctx ui.Context, bounds geom.RectangleF32, _ geom.PointF32, _ ui.Control) {
r.project.update(ctx.Renderer()) r.viewBounds = geom.RectF32(buttonBarWidth, 0, bounds.Dx()-buttonBarWidth, bounds.Dy())
} r.isometric.SetViewBounds(r.viewBounds)
r.interactBounds = r.viewBounds
func (r *terrainRenderer) Init(ctx ui.Context) error { r.interactBounds.Min.Y += 64
return nil
} }
func isControlKeyDown(ctx ui.Context) bool { func isControlKeyDown(ctx ui.Context) bool {
@ -46,7 +47,7 @@ func (r *terrainRenderer) Handle(ctx ui.Context, event ui.Event) bool {
switch e := event.(type) { switch e := event.(type) {
case *ui.MouseButtonDownEvent: case *ui.MouseButtonDownEvent:
pos := e.Pos() pos := e.Pos()
if pos.ToInt().In(r.project.windowInteractRect) { if pos.In(r.interactBounds) {
controlKeyDown := isControlKeyDown(ctx) controlKeyDown := isControlKeyDown(ctx)
if e.Button == ui.MouseButtonMiddle || (e.Button == ui.MouseButtonLeft && controlKeyDown) { if e.Button == ui.MouseButtonMiddle || (e.Button == ui.MouseButtonLeft && controlKeyDown) {
if _, ok := r.drag.IsDragging(); !ok { if _, ok := r.drag.IsDragging(); !ok {
@ -54,7 +55,7 @@ func (r *terrainRenderer) Handle(ctx ui.Context, event ui.Event) bool {
} }
} }
if e.Button == ui.MouseButtonLeft && !controlKeyDown { if e.Button == ui.MouseButtonLeft && !controlKeyDown {
pos := r.project.screenToMapInt(int(e.X), int(e.Y)) pos := r.isometric.ViewToTileInt(pos)
r.game.UserClickedTile(pos) r.game.UserClickedTile(pos)
} }
if e.Button == ui.MouseButtonRight { if e.Button == ui.MouseButtonRight {
@ -62,62 +63,60 @@ func (r *terrainRenderer) Handle(ctx ui.Context, event ui.Event) bool {
} }
} }
case *ui.MouseButtonUpEvent: case *ui.MouseButtonUpEvent:
pos := e.Pos().ToInt() pos := e.Pos()
if pos.In(r.project.windowInteractRect) { if pos.In(r.interactBounds) {
if _, ok := r.drag.IsDragging(); ok { if _, ok := r.drag.IsDragging(); ok {
r.game.Terrain.Center = mapToTile(r.project.center) r.game.Terrain.Center = r.isometric.TileInt(r.isometric.Center())
r.drag.Cancel() r.drag.Cancel()
} }
} }
case *ui.MouseMoveEvent: case *ui.MouseMoveEvent:
pos := e.Pos() pos := e.Pos()
if pos.ToInt().In(r.project.windowInteractRect) { if pos.In(r.interactBounds) {
hover := r.project.screenToMapInt(int(e.X), int(e.Y)) hover := r.isometric.ViewToTileInt(pos)
r.hover = &hover r.hover = &hover
} else { } else {
r.hover = nil r.hover = nil
} }
if _, ok := r.drag.IsDragging(); ok { if _, ok := r.drag.IsDragging(); ok {
delta, _ := r.drag.Move(pos) delta, _ := r.drag.Move(pos)
r.project.center = r.project.center.Sub(r.project.screenToMapRel(int(delta.X), int(delta.Y))) r.isometric.Pan(r.isometric.ViewToTileRelative(delta.Invert()))
r.project.update(ctx.Renderer())
} }
if r.hover != nil { if r.hover != nil {
if e.MouseWheel < 0 { if e.MouseWheel < 0 {
r.project.ZoomOut(ctx, r.hover.ToF32()) r.isometric.ZoomOut(r.hover.ToF32())
} else if e.MouseWheel > 0 { } else if e.MouseWheel > 0 {
r.project.ZoomIn(ctx, r.hover.ToF32()) r.isometric.ZoomIn(r.hover.ToF32())
} }
} }
case *ui.MouseLeaveEvent: case *ui.MouseLeaveEvent:
r.hover = nil r.hover = nil
r.project.update(ctx.Renderer())
case *ui.KeyDownEvent: case *ui.KeyDownEvent:
switch e.Key { switch e.Key {
case ui.KeyPadPlus: case ui.KeyPadPlus:
r.project.ZoomIn(ctx, r.project.center) r.isometric.ZoomIn(r.isometric.Center())
case ui.KeyMinus: case ui.KeyMinus:
r.project.ZoomOut(ctx, r.project.center) r.isometric.ZoomOut(r.isometric.Center())
case ui.KeyPadMinus: case ui.KeyPadMinus:
r.project.ZoomOut(ctx, r.project.center) r.isometric.ZoomOut(r.isometric.Center())
case ui.KeyW: case ui.KeyW:
r.project.Pan(ctx, geom.PtF32(-1, -1)) r.isometric.Pan(geom.PtF32(-1, -1))
case ui.KeyA: case ui.KeyA:
r.project.Pan(ctx, geom.PtF32(-1, 1)) r.isometric.Pan(geom.PtF32(-1, 1))
case ui.KeyS: case ui.KeyS:
r.project.Pan(ctx, geom.PtF32(1, 1)) r.isometric.Pan(geom.PtF32(1, 1))
case ui.KeyD: case ui.KeyD:
r.project.Pan(ctx, geom.PtF32(1, -1)) r.isometric.Pan(geom.PtF32(1, -1))
} }
} }
return false return false
} }
func (r *terrainRenderer) Render(ctx ui.Context) { func (r *terrainRenderer) Render(ctx ui.Context) {
zoom := r.project.zoom zoom := r.isometric.Zoom()
terrain := r.game.Terrain terrain := r.game.Terrain
toTileTexture := func(x, y int) ui.Texture { toTileTexture := func(tile geom.Point) ui.Texture {
temp := terrain.Temp.Value(x, y) temp := terrain.Temp.Value(tile.X, tile.Y)
if temp < .35 { if temp < .35 {
return ctx.Textures().ScaledByName("tile-snow", zoom) return ctx.Textures().ScaledByName("tile-snow", zoom)
} }
@ -195,26 +194,35 @@ func (r *terrainRenderer) Render(ctx ui.Context) {
// vertical (tile): [96,160) = 64 // vertical (tile): [96,160) = 64
// vertical (total): [0,160) = 160 // vertical (total): [0,160) = 160
r.project.visibleTiles(func(x, y int, pos geom.Point) { topLeft := geom.PtF32(-64*zoom, -112*zoom)
text := toTileTexture(x, y) bottomRight := geom.PtF32(64*zoom, 48*zoom)
rect := r.project.screenToTileRect(pos) textureRect := func(center geom.PointF32) geom.RectangleF32 {
ctx.Renderer().DrawTexture(text, rect.ToF32()) return geom.RectangleF32{Min: center.Add(topLeft), Max: center.Add(bottomRight)}
}
hoverTexture := ctx.Textures().ScaledByName("tile-hover", zoom)
if r.hover != nil && x == r.hover.X && y == r.hover.Y { r.isometric.EnumerateInt(func(tile geom.Point, view geom.PointF32) {
ctx.Renderer().DrawTexture(ctx.Textures().ScaledByName("tile-hover", zoom), rect.ToF32()) text := toTileTexture(tile)
rect := textureRect(view)
ctx.Renderer().DrawTexture(text, rect)
// if r.game.Debug {
// ctx.Renderer().FillRectangle(view.Add2D(-1, -1).RectRel2D(2, 2), color.White)
// ctx.Fonts().TextAlign("debug", view, color.White, fmt.Sprintf("%d, %d", tile.X, tile.Y), ui.AlignCenter)
// }
if r.hover != nil && tile.X == r.hover.X && tile.Y == r.hover.Y {
ctx.Renderer().DrawTexture(hoverTexture, rect)
} }
}) })
r.project.visibleTiles(func(x, y int, pos geom.Point) { r.isometric.EnumerateInt(func(tile geom.Point, view geom.PointF32) {
text := toItemTexture(x, y) text := toItemTexture(tile.X, tile.Y)
if text == nil { if text == nil {
return return
} }
placeX, placeY := terrain.PlaceX.Value(x, y), terrain.PlaceY.Value(x, y) placeX, placeY := terrain.PlaceX.Value(tile.X, tile.Y), terrain.PlaceY.Value(tile.X, tile.Y)
pos = r.project.mapToScreenF(float32(x)-.2+float32(.9*placeX-.45), float32(y)-.2+float32(.9*placeY-.45)) displaced := r.isometric.TileToView(tile.ToF32().Add2D(-.2+.9*float32(placeX)-.45, -.2+.9*float32(placeY)-.45))
rect := r.project.screenToTileRect(pos) rect := textureRect(displaced)
ctx.Renderer().DrawTexture(text, rect.ToF32()) ctx.Renderer().DrawTexture(text, rect)
// ctx.Fonts().Text("debug", pos.ToF32(), color.White, fmt.Sprintf("%d, %d", x, y))
}) })
} }