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 }