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