ui: basic map scroller

This commit is contained in:
Ilia Denisov
2026-03-06 23:29:06 +02:00
committed by GitHub
parent 29d188969b
commit 1de621c743
68 changed files with 9861 additions and 118 deletions
+107
View File
@@ -0,0 +1,107 @@
package world
// RenderPlan describes the full expanded-canvas redraw plan for one RenderParams.
// It is a pure description: it does not execute any drawing.
type RenderPlan struct {
CanvasWidthPx int
CanvasHeightPx int
ZoomFp int
// WorldRect is the unwrapped world-space rect covered by the expanded canvas.
WorldRect Rect
// Tiles are ordered in the same order as produced by tileWorldRect:
// increasing tile X index, then increasing tile Y index.
Tiles []TileDrawPlan
}
// TileDrawPlan describes how to draw one torus tile contribution.
type TileDrawPlan struct {
Tile WorldTile
// Clip rect on the expanded canvas in pixel coordinates.
// It is half-open in spirit: [ClipX, ClipX+ClipW) x [ClipY, ClipY+ClipH).
ClipX int
ClipY int
ClipW int
ClipH int
// Candidates are unique per tile (deduped by ID).
Candidates []MapItem
}
// worldSpanFixedToCanvasPx converts a world fixed-point span into a canvas pixel span
// for the given fixed-point zoom. The conversion is truncating (floor).
func worldSpanFixedToCanvasPx(spanWorldFp, zoomFp int) int {
// spanWorldFp can be negative in some internal cases, but for clip computations
// we always pass non-negative spans.
return (spanWorldFp * zoomFp) / (SCALE * SCALE)
}
// buildRenderPlanStageA builds a full expanded-canvas redraw plan (Stage A).
//
// It assumes the world grid is already built (IndexOnViewportChange called).
// The plan contains per-tile clip rectangles and per-tile candidate lists
// from the spatial index.
func (w *World) buildRenderPlanStageA(params RenderParams) (RenderPlan, error) {
if err := params.Validate(); err != nil {
return RenderPlan{}, err
}
zoomFp, err := params.CameraZoomFp()
if err != nil {
return RenderPlan{}, err
}
worldRect, err := params.ExpandedCanvasWorldRect()
if err != nil {
return RenderPlan{}, err
}
tiles := tileWorldRect(worldRect, w.W, w.H)
// Query candidates per tile.
batches, err := w.collectCandidatesForTiles(tiles)
if err != nil {
return RenderPlan{}, err
}
planTiles := make([]TileDrawPlan, 0, len(batches))
for _, batch := range batches {
tile := batch.Tile
// Convert the tile's canonical rect + offsets into the unwrapped segment.
segMinX := tile.Rect.minX + tile.OffsetX
segMaxX := tile.Rect.maxX + tile.OffsetX
segMinY := tile.Rect.minY + tile.OffsetY
segMaxY := tile.Rect.maxY + tile.OffsetY
// Map that segment into expanded canvas pixel coordinates relative to worldRect.minX/minY.
clipX := worldSpanFixedToCanvasPx(segMinX-worldRect.minX, zoomFp)
clipY := worldSpanFixedToCanvasPx(segMinY-worldRect.minY, zoomFp)
clipX2 := worldSpanFixedToCanvasPx(segMaxX-worldRect.minX, zoomFp)
clipY2 := worldSpanFixedToCanvasPx(segMaxY-worldRect.minY, zoomFp)
clipW := clipX2 - clipX
clipH := clipY2 - clipY
planTiles = append(planTiles, TileDrawPlan{
Tile: tile,
ClipX: clipX,
ClipY: clipY,
ClipW: clipW,
ClipH: clipH,
Candidates: batch.Items,
})
}
return RenderPlan{
CanvasWidthPx: params.CanvasWidthPx(),
CanvasHeightPx: params.CanvasHeightPx(),
ZoomFp: zoomFp,
WorldRect: worldRect,
Tiles: planTiles,
}, nil
}