package world // lineSeg is one canonical segment (endpoints in [0..W) x [0..H)) to be drawn. // It represents part of the torus-shortest polyline for a Line primitive after wrap splitting. type lineSeg struct { x1, y1 int x2, y2 int } // drawPointInTile draws point marker copies that intersect the tile. // lastStyle is already applied; it provides PointRadiusPx. func (w *World) drawPointInTile(drawer PrimitiveDrawer, plan RenderPlan, td TileDrawPlan, p Point, allowWrap bool, lastStyle Style) { rPx := lastStyle.PointRadiusPx if rPx <= 0 { // Nothing visible. return } // Convert screen radius to world-fixed conservatively. rWorldFp := PixelSpanToWorldFixed(int(rPx+0.999999), plan.ZoomFp) var shifts []wrapShift if allowWrap { shifts = pointWrapShifts(p, rWorldFp, w.W, w.H) } else { shifts = []wrapShift{{dx: 0, dy: 0}} } for _, s := range shifts { if allowWrap && !pointCopyIntersectsTile(p, rWorldFp, s.dx, s.dy, td.Tile) { continue } px := worldSpanFixedToCanvasPx((p.X+td.Tile.OffsetX+s.dx)-plan.WorldRect.minX, plan.ZoomFp) py := worldSpanFixedToCanvasPx((p.Y+td.Tile.OffsetY+s.dy)-plan.WorldRect.minY, plan.ZoomFp) drawer.AddPoint(float64(px), float64(py), rPx) fill := alphaNonZero(lastStyle.FillColor) stroke := alphaNonZero(lastStyle.StrokeColor) if fill { drawer.Fill() } if stroke { // Stroke must be last when both are present. drawer.Stroke() } } } func (w *World) drawCircleInTile(drawer PrimitiveDrawer, plan RenderPlan, td TileDrawPlan, c Circle, allowWrap bool, lastStyle Style) { var shifts []wrapShift effRadius := circleRadiusEffFp(c.Radius, w.circleRadiusScaleFp) if allowWrap { shifts = circleWrapShiftsInto(w.scratchWrapShifts, c.X, c.Y, effRadius, w.W, w.H) } else { var one [1]wrapShift one[0] = wrapShift{dx: 0, dy: 0} shifts = one[:] } rPx := worldSpanFixedToCanvasPx(effRadius, plan.ZoomFp) for _, s := range shifts { if allowWrap && !circleCopyIntersectsTile(c.X, c.Y, effRadius, s.dx, s.dy, td.Tile, w.W, w.H) { continue } cxPx := worldSpanFixedToCanvasPx((c.X+td.Tile.OffsetX+s.dx)-plan.WorldRect.minX, plan.ZoomFp) cyPx := worldSpanFixedToCanvasPx((c.Y+td.Tile.OffsetY+s.dy)-plan.WorldRect.minY, plan.ZoomFp) fill := alphaNonZero(lastStyle.FillColor) stroke := alphaNonZero(lastStyle.StrokeColor) switch { case fill && stroke: // gg consumes the current path on Fill/Stroke, so we must draw twice: // once for fill, then again for stroke. drawer.AddCircle(float64(cxPx), float64(cyPx), float64(rPx)) drawer.Fill() drawer.AddCircle(float64(cxPx), float64(cyPx), float64(rPx)) drawer.Stroke() case fill: drawer.AddCircle(float64(cxPx), float64(cyPx), float64(rPx)) drawer.Fill() case stroke: drawer.AddCircle(float64(cxPx), float64(cyPx), float64(rPx)) drawer.Stroke() default: // neither visible => nothing } } w.scratchWrapShifts = shifts[:0] } func (w *World) drawLineInTilePath(drawer PrimitiveDrawer, plan RenderPlan, td TileDrawPlan, l Line, allowWrap bool) int { segs := w.scratchLineSegs[:0] tmp := w.scratchLineSegsTmp[:0] if cap(segs) < 4 { segs = make([]lineSeg, 0, 4) } if cap(tmp) < 4 { tmp = make([]lineSeg, 0, 4) } if allowWrap { segs, tmp = torusShortestLineSegmentsInto(segs, tmp, l, w.W, w.H) } else { var one [1]lineSeg one[0] = lineSeg{x1: l.X1, y1: l.Y1, x2: l.X2, y2: l.Y2} segs = one[:] } for _, s := range segs { x1 := worldSpanFixedToCanvasPx((s.x1+td.Tile.OffsetX)-plan.WorldRect.minX, plan.ZoomFp) y1 := worldSpanFixedToCanvasPx((s.y1+td.Tile.OffsetY)-plan.WorldRect.minY, plan.ZoomFp) x2 := worldSpanFixedToCanvasPx((s.x2+td.Tile.OffsetX)-plan.WorldRect.minX, plan.ZoomFp) y2 := worldSpanFixedToCanvasPx((s.y2+td.Tile.OffsetY)-plan.WorldRect.minY, plan.ZoomFp) drawer.AddLine(float64(x1), float64(y1), float64(x2), float64(y2)) } w.scratchLineSegs = segs[:0] w.scratchLineSegsTmp = tmp[:0] return len(segs) } func (w *World) drawLineInTile(drawer PrimitiveDrawer, plan RenderPlan, td TileDrawPlan, l Line, allowWrap bool) { w.drawLineInTilePath(drawer, plan, td, l, allowWrap) drawer.Stroke() }