package world import ( "testing" "github.com/stretchr/testify/require" ) func TestDrawLinesFromPlan_WrapX_SplitsAndDrawsInThreeXTiles(t *testing.T) { t.Parallel() w := NewWorld(10, 10) w.resetGrid(2 * SCALE) // Horizontal line that wraps across X: 9 -> 1 at y=5. id, err := w.AddLine(9, 5, 1, 5) require.NoError(t, err) w.indexObject(w.objects[id]) params := RenderParams{ ViewportWidthPx: 10, ViewportHeightPx: 10, MarginXPx: 2, MarginYPx: 2, CameraXWorldFp: 5 * SCALE, CameraYWorldFp: 5 * SCALE, CameraZoom: 1.0, } plan, err := w.buildRenderPlanStageA(params) require.NoError(t, err) d := &fakePrimitiveDrawer{} drawLinesFromPlan(d, plan, w.W, w.H, true) // Expect drawing in 3 X tiles (left partial, middle full, right partial) for the central Y tile: // Each tile group: Save, ClipRect, AddLine(s), Stroke, Restore // // Left tile (offsetX=-10000): 1 line segment. // Middle tile (offsetX=0): 2 segments (wrapped split). // Right tile (offsetX=10000): 1 segment. wantNames := []string{ "Save", "ClipRect", "AddLine", "Stroke", "Restore", "Save", "ClipRect", "AddLine", "AddLine", "Stroke", "Restore", "Save", "ClipRect", "AddLine", "Stroke", "Restore", } require.Equal(t, wantNames, d.CommandNames()) // Group 1: left strip clip (0,2,2,10), line at y=7 from x=1..2 { requireCommandArgs(t, requireDrawerCommandAt(t, d, 1), 0, 2, 2, 10) requireCommandArgs(t, requireDrawerCommandAt(t, d, 2), 1, 7, 2, 7) } // Group 2: middle strip clip (2,2,10,10), two segments: // segment [9000..10000] => x 11..12, y 7 // segment [0..1000] => x 2..3, y 7 { requireCommandArgs(t, requireDrawerCommandAt(t, d, 6), 2, 2, 10, 10) // The order of segments is stable with our splitting: first the one ending at boundary, then the remainder. requireCommandArgs(t, requireDrawerCommandAt(t, d, 7), 11, 7, 12, 7) requireCommandArgs(t, requireDrawerCommandAt(t, d, 8), 2, 7, 3, 7) } // Group 3: right strip clip (12,2,2,10), line at y=7 from x=12..13 { requireCommandArgs(t, requireDrawerCommandAt(t, d, 12), 12, 2, 2, 10) // ClipRect requireCommandArgs(t, requireDrawerCommandAt(t, d, 13), 12, 7, 13, 7) // AddLine } } func TestDrawLinesFromPlan_WrapY_SplitsAndDrawsInThreeYTiles(t *testing.T) { t.Parallel() w := NewWorld(10, 10) w.resetGrid(2 * SCALE) // Vertical line that wraps across Y: 9 -> 1 at x=5. id, err := w.AddLine(5, 9, 5, 1) require.NoError(t, err) w.indexObject(w.objects[id]) params := RenderParams{ ViewportWidthPx: 10, ViewportHeightPx: 10, MarginXPx: 2, MarginYPx: 2, CameraXWorldFp: 5 * SCALE, CameraYWorldFp: 5 * SCALE, CameraZoom: 1.0, } plan, err := w.buildRenderPlanStageA(params) require.NoError(t, err) d := &fakePrimitiveDrawer{} drawLinesFromPlan(d, plan, w.W, w.H, true) // Here we expect 3 Y tiles for the central X tile: // Top partial, middle full (two segments), bottom partial. // // The exact ordering of tiles is by tx then ty, so X=middle strips first. // For this geometry the line only intersects the middle X tiles (offsetX=0), // but spans Y across -1,0,1. wantNames := []string{ "Save", "ClipRect", "AddLine", "Stroke", "Restore", "Save", "ClipRect", "AddLine", "AddLine", "Stroke", "Restore", "Save", "ClipRect", "AddLine", "Stroke", "Restore", } require.Equal(t, wantNames, d.CommandNames()) // Group 1: top strip clip (2,0,10,2), line at x=7 from y=1..2 { requireCommandArgs(t, requireDrawerCommandAt(t, d, 1), 2, 0, 10, 2) requireCommandArgs(t, requireDrawerCommandAt(t, d, 2), 7, 1, 7, 2) } // Group 2: middle strip clip (2,2,10,10), two segments: // segment [9000..10000] => y 11..12 at x=7 // segment [0..1000] => y 2..3 at x=7 { requireCommandArgs(t, requireDrawerCommandAt(t, d, 6), 2, 2, 10, 10) requireCommandArgs(t, requireDrawerCommandAt(t, d, 7), 7, 11, 7, 12) requireCommandArgs(t, requireDrawerCommandAt(t, d, 8), 7, 2, 7, 3) } // Group 3: bottom strip clip (2,12,10,2), line at x=7 from y=12..13 { requireCommandArgs(t, requireDrawerCommandAt(t, d, 12), 2, 12, 10, 2) // ClipRect requireCommandArgs(t, requireDrawerCommandAt(t, d, 13), 7, 12, 7, 13) // AddLine } } func TestTorusShortestLineSegments_TieCaseIsDeterministicAndSplits(t *testing.T) { t.Parallel() // World 10 units => 10000 fixed. worldW := 10 * SCALE worldH := 10 * SCALE // Tie-case along X: 1 -> 6 is exactly half world apart (dx = +5000). // Deterministic rule chooses negative delta representation (wrap is applied). l := Line{ X1: 1 * SCALE, Y1: 5 * SCALE, X2: 6 * SCALE, Y2: 5 * SCALE, } segs := torusShortestLineSegments(l, worldW, worldH) // Expect two horizontal segments: // [6000..10000] and [0..1000] at y=5000. require.Len(t, segs, 2) // Direction is deterministic and follows the chosen negative-delta representation. require.Equal(t, lineSeg{x1: 1000, y1: 5000, x2: 0, y2: 5000}, segs[0]) require.Equal(t, lineSeg{x1: 10000, y1: 5000, x2: 6000, y2: 5000}, segs[1]) } func TestLines_NoWrap_TieCaseDoesNotWrap(t *testing.T) { t.Parallel() w := NewWorld(10, 10) w.resetGrid(2 * SCALE) // Tie-case along X: 1 -> 6 in world of 10. _, err := w.AddLine(1, 5, 6, 5) require.NoError(t, err) for _, obj := range w.objects { w.indexObject(obj) } params := RenderParams{ ViewportWidthPx: 10, ViewportHeightPx: 10, MarginXPx: 0, MarginYPx: 0, CameraXWorldFp: 5 * SCALE, CameraYWorldFp: 5 * SCALE, CameraZoom: 1.0, Options: &RenderOptions{ DisableWrapScroll: true, Layers: []RenderLayer{RenderLayerLines}, }, } d := &fakePrimitiveDrawer{} require.NoError(t, w.Render(d, params)) lines := d.CommandsByName("AddLine") require.Len(t, lines, 1) // At zoom=1 and margin=0, world==canvas, so pixels equal world units. require.Equal(t, []float64{1, 5, 6, 5}, lines[0].Args) }