Refactored terrainRenderer to use play.IsometricProjection.
This commit is contained in:
parent
f065317c76
commit
4519417b4e
127
projection.go
127
projection.go
@ -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)
|
|
||||||
}
|
|
@ -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))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user