ui: basic map scroller
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
package world
|
||||
|
||||
// renderCirclesStageA performs a full expanded-canvas redraw but renders ONLY Circle primitives.
|
||||
func (w *World) renderCirclesStageA(drawer PrimitiveDrawer, params RenderParams) error {
|
||||
plan, err := w.buildRenderPlanStageA(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
drawCirclesFromPlan(drawer, plan, w.W, w.H)
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawCirclesFromPlan executes a circles-only draw from an already built render plan.
|
||||
func drawCirclesFromPlan(drawer PrimitiveDrawer, plan RenderPlan, worldW, worldH int) {
|
||||
for _, td := range plan.Tiles {
|
||||
if td.ClipW <= 0 || td.ClipH <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter only circles; skip tiles that have no circles.
|
||||
circles := make([]Circle, 0, len(td.Candidates))
|
||||
for _, it := range td.Candidates {
|
||||
c, ok := it.(Circle)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
circles = append(circles, c)
|
||||
}
|
||||
if len(circles) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine which circle copies actually intersect this tile segment.
|
||||
type circleCopy struct {
|
||||
c Circle
|
||||
dx int
|
||||
dy int
|
||||
}
|
||||
copiesToDraw := make([]circleCopy, 0, len(circles))
|
||||
|
||||
for _, c := range circles {
|
||||
shifts := circleWrapShifts(c, worldW, worldH)
|
||||
for _, s := range shifts {
|
||||
if circleCopyIntersectsTile(c, s.dx, s.dy, td.Tile, worldW, worldH) {
|
||||
copiesToDraw = append(copiesToDraw, circleCopy{c: c, dx: s.dx, dy: s.dy})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(copiesToDraw) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
drawer.Save()
|
||||
drawer.ClipRect(float64(td.ClipX), float64(td.ClipY), float64(td.ClipW), float64(td.ClipH))
|
||||
|
||||
for _, cc := range copiesToDraw {
|
||||
c := cc.c
|
||||
|
||||
// Project the circle center for this tile copy (tile offset + wrap shift).
|
||||
cxPx := worldSpanFixedToCanvasPx((c.X+td.Tile.OffsetX+cc.dx)-plan.WorldRect.minX, plan.ZoomFp)
|
||||
cyPx := worldSpanFixedToCanvasPx((c.Y+td.Tile.OffsetY+cc.dy)-plan.WorldRect.minY, plan.ZoomFp)
|
||||
|
||||
// Radius is a world span.
|
||||
rPx := worldSpanFixedToCanvasPx(c.Radius, plan.ZoomFp)
|
||||
|
||||
drawer.AddCircle(float64(cxPx), float64(cyPx), float64(rPx))
|
||||
}
|
||||
|
||||
drawer.Fill()
|
||||
drawer.Restore()
|
||||
}
|
||||
}
|
||||
|
||||
type wrapShift struct {
|
||||
dx int
|
||||
dy int
|
||||
}
|
||||
|
||||
// circleWrapShifts returns 1..4 wrap shifts (multiples of worldW/worldH) required to render
|
||||
// all torus copies of the circle inside the canonical world domain.
|
||||
// The (0,0) shift is always present.
|
||||
func circleWrapShifts(c Circle, worldW, worldH int) []wrapShift {
|
||||
// If radius covers the whole axis, additional copies are not useful.
|
||||
// (One copy already covers everything under any reasonable clip.)
|
||||
if c.Radius >= worldW || c.Radius >= worldH {
|
||||
return []wrapShift{{dx: 0, dy: 0}}
|
||||
}
|
||||
|
||||
xShifts := []int{0}
|
||||
yShifts := []int{0}
|
||||
|
||||
if c.X+c.Radius >= worldW {
|
||||
xShifts = append(xShifts, -worldW)
|
||||
}
|
||||
if c.X-c.Radius < 0 {
|
||||
xShifts = append(xShifts, worldW)
|
||||
}
|
||||
|
||||
if c.Y+c.Radius >= worldH {
|
||||
yShifts = append(yShifts, -worldH)
|
||||
}
|
||||
if c.Y-c.Radius < 0 {
|
||||
yShifts = append(yShifts, worldH)
|
||||
}
|
||||
|
||||
out := make([]wrapShift, 0, len(xShifts)*len(yShifts))
|
||||
for _, dx := range xShifts {
|
||||
for _, dy := range yShifts {
|
||||
out = append(out, wrapShift{dx: dx, dy: dy})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// circleCopyIntersectsTile checks whether the circle copy (shifted by dx/dy) intersects the tile segment.
|
||||
// We use the tile's unwrapped segment bounds: [offset+rect.min, offset+rect.max) per axis.
|
||||
func circleCopyIntersectsTile(c Circle, dx, dy int, tile WorldTile, worldW, worldH int) bool {
|
||||
// Unwrapped tile segment bounds.
|
||||
segMinX := tile.OffsetX + tile.Rect.minX
|
||||
segMaxX := tile.OffsetX + tile.Rect.maxX
|
||||
segMinY := tile.OffsetY + tile.Rect.minY
|
||||
segMaxY := tile.OffsetY + tile.Rect.maxY
|
||||
|
||||
// Circle bbox in the same unwrapped space (apply shift + tile offset).
|
||||
cx := c.X + tile.OffsetX + dx
|
||||
cy := c.Y + tile.OffsetY + dy
|
||||
|
||||
minX := cx - c.Radius
|
||||
maxX := cx + c.Radius
|
||||
minY := cy - c.Radius
|
||||
maxY := cy + c.Radius
|
||||
|
||||
// Treat bbox as half-open for intersection checks.
|
||||
if maxX <= segMinX || minX >= segMaxX || maxY <= segMinY || minY >= segMaxY {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user