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 } allowWrap := params.Options == nil || !params.Options.DisableWrapScroll var tiles []WorldTile if allowWrap { tiles = tileWorldRect(worldRect, w.W, w.H) } else { tiles = tileWorldRectNoWrap(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 }