Files
galaxy-game/client/world/renderer_draw.go
T
2026-03-07 17:01:22 +02:00

166 lines
3.5 KiB
Go

package world
import (
"sort"
"github.com/google/uuid"
)
// drawKind is used only for stable tie-breaking when priorities are equal.
type drawKind int
const (
drawKindLine drawKind = iota
drawKindCircle
drawKindPoint
)
type drawItem struct {
kind drawKind
priority int
id uuid.UUID
styleID StyleID
// Exactly one of these is set.
p *Point
c *Circle
l *Line
}
// drawPlanSinglePass renders a plan using a single ordered pass per tile.
// Items in each tile are sorted by (Priority asc, Kind asc, ID asc) for determinism.
//
// allowWrap controls torus behavior:
// - true: circles/points produce wrap copies, lines use torus-shortest segments
// - false: no copies, lines drawn directly as stored
func (w *World) drawPlanSinglePass(drawer PrimitiveDrawer, plan RenderPlan, allowWrap bool) {
var lastStyleID StyleID = StyleIDInvalid
var lastStyle Style
applyStyle := func(styleID StyleID) {
if styleID == lastStyleID {
return
}
s, ok := w.styles.Get(styleID)
if !ok {
// Unknown style ID is a programming/config error.
panic("render: unknown style ID")
}
// Apply style state. Some fields may be nil intentionally.
if s.FillColor != nil {
drawer.SetFillColor(s.FillColor)
}
if s.StrokeColor != nil {
drawer.SetStrokeColor(s.StrokeColor)
}
drawer.SetLineWidth(s.StrokeWidthPx)
if len(s.StrokeDashes) > 0 {
drawer.SetDash(s.StrokeDashes...)
} else {
// Ensure solid line when switching from dashed style.
drawer.SetDash()
}
drawer.SetDashOffset(s.StrokeDashOffset)
lastStyleID = styleID
lastStyle = s
}
for _, td := range plan.Tiles {
if td.ClipW <= 0 || td.ClipH <= 0 {
continue
}
// Collect items for this tile.
items := make([]drawItem, 0, len(td.Candidates))
for _, it := range td.Candidates {
switch v := it.(type) {
case Point:
vv := v
items = append(items, drawItem{
kind: drawKindPoint,
priority: vv.Priority,
id: vv.Id,
styleID: vv.StyleID,
p: &vv,
})
case Circle:
vv := v
items = append(items, drawItem{
kind: drawKindCircle,
priority: vv.Priority,
id: vv.Id,
styleID: vv.StyleID,
c: &vv,
})
case Line:
vv := v
items = append(items, drawItem{
kind: drawKindLine,
priority: vv.Priority,
id: vv.Id,
styleID: vv.StyleID,
l: &vv,
})
default:
// Unknown map items should not exist.
panic("render: unknown map item type")
}
}
if len(items) == 0 {
continue
}
sort.Slice(items, func(i, j int) bool {
a, b := items[i], items[j]
if a.priority != b.priority {
return a.priority < b.priority
}
if a.kind != b.kind {
return a.kind < b.kind
}
return uuidLess(a.id, b.id)
})
drawer.Save()
drawer.ClipRect(float64(td.ClipX), float64(td.ClipY), float64(td.ClipW), float64(td.ClipH))
for _, di := range items {
applyStyle(di.styleID)
switch di.kind {
case drawKindPoint:
w.drawPointInTile(drawer, plan, td, *di.p, allowWrap, lastStyle)
case drawKindCircle:
w.drawCircleInTile(drawer, plan, td, *di.c, allowWrap, lastStyle)
case drawKindLine:
w.drawLineInTile(drawer, plan, td, *di.l, allowWrap)
default:
panic("render: unknown draw kind")
}
}
drawer.Restore()
}
}
func uuidLess(a, b uuid.UUID) bool {
aa := a[:]
bb := b[:]
for i := 0; i < len(aa); i++ {
if aa[i] < bb[i] {
return true
}
if aa[i] > bb[i] {
return false
}
}
return false
}