diff --git a/projection.go b/projection.go deleted file mode 100644 index c90d7d1..0000000 --- a/projection.go +++ /dev/null @@ -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) -} diff --git a/terrainrenderer.go b/terrainrenderer.go index 52bde42..82e7039 100644 --- a/terrainrenderer.go +++ b/terrainrenderer.go @@ -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) }) }