package world // torusShortestLineSegmentsInto converts a Line primitive into 1..4 canonical segments // inside [0..worldW) x [0..worldH) that represent the torus-shortest polyline. // // It appends results into dst using tmp as an intermediate buffer. // No allocations occur if dst/tmp have sufficient capacity (>=4). func torusShortestLineSegmentsInto(dst, tmp []lineSeg, l Line, worldW, worldH int) ([]lineSeg, []lineSeg) { dst = dst[:0] tmp = tmp[:0] // Step 1: choose the torus-shortest representation in unwrapped space. ax, bx := shortestWrappedDelta(l.X1, l.X2, worldW) ay, by := shortestWrappedDelta(l.Y1, l.Y2, worldH) // Step 2: shift so that A is inside canonical [0..W) x [0..H). shiftX := floorDiv(ax, worldW) * worldW shiftY := floorDiv(ay, worldH) * worldH ax -= shiftX bx -= shiftX ay -= shiftY by -= shiftY dst = append(dst, lineSeg{x1: ax, y1: ay, x2: bx, y2: by}) // Step 3: split by X boundary if needed (jump-aware). tmp = splitSegmentsByXInto(tmp, dst, worldW) // Step 4: split by Y boundary if needed (jump-aware). dst = splitSegmentsByYInto(dst, tmp, worldH) return dst, tmp } // torusShortestLineSegments is a compatibility wrapper that allocates. // Prefer torusShortestLineSegmentsInto in hot paths. func torusShortestLineSegments(l Line, worldW, worldH int) []lineSeg { dst := make([]lineSeg, 0, 4) tmp := make([]lineSeg, 0, 4) dst, _ = torusShortestLineSegmentsInto(dst, tmp, l, worldW, worldH) return dst } // splitSegmentsByXInto appends 1..2 segments for each input segment into out, without allocating. // out is reset to length 0 by this function. func splitSegmentsByXInto(out []lineSeg, segs []lineSeg, worldW int) []lineSeg { out = out[:0] for _, s := range segs { x1, y1, x2, y2 := s.x1, s.y1, s.x2, s.y2 // After normalization, x1 is expected inside [0..worldW). Only x2 may be outside. if x2 >= 0 && x2 < worldW { out = append(out, s) continue } dx := x2 - x1 dy := y2 - y1 if dx == 0 { out = append(out, s) continue } if x2 >= worldW { // Crosses the right boundary at x=worldW, then reappears at x=0. bx := worldW num := bx - x1 iy := y1 + (dy*num)/dx s1 := lineSeg{x1: x1, y1: y1, x2: worldW, y2: iy} s2 := lineSeg{x1: 0, y1: iy, x2: x2 - worldW, y2: y2} out = append(out, s1, s2) continue } // x2 < 0: crosses the left boundary at x=0, then reappears at x=worldW. bx := 0 num := bx - x1 iy := y1 + (dy*num)/dx s1 := lineSeg{x1: x1, y1: y1, x2: 0, y2: iy} s2 := lineSeg{x1: worldW, y1: iy, x2: x2 + worldW, y2: y2} out = append(out, s1, s2) } return out } // splitSegmentsByYInto appends 1..2 segments for each input segment into out, without allocating. // out is reset to length 0 by this function. func splitSegmentsByYInto(out []lineSeg, segs []lineSeg, worldH int) []lineSeg { out = out[:0] for _, s := range segs { x1, y1, x2, y2 := s.x1, s.y1, s.x2, s.y2 // After normalization, y1 is expected inside [0..worldH). Only y2 may be outside. if y2 >= 0 && y2 < worldH { out = append(out, s) continue } dx := x2 - x1 dy := y2 - y1 if dy == 0 { out = append(out, s) continue } if y2 >= worldH { // Crosses the top boundary at y=worldH, then reappears at y=0. by := worldH num := by - y1 ix := x1 + (dx*num)/dy s1 := lineSeg{x1: x1, y1: y1, x2: ix, y2: worldH} s2 := lineSeg{x1: ix, y1: 0, x2: x2, y2: y2 - worldH} out = append(out, s1, s2) continue } // y2 < 0: crosses the bottom boundary at y=0, then reappears at y=worldH. by := 0 num := by - y1 ix := x1 + (dx*num)/dy s1 := lineSeg{x1: x1, y1: y1, x2: ix, y2: 0} s2 := lineSeg{x1: ix, y1: worldH, x2: x2, y2: y2 + worldH} out = append(out, s1, s2) } return out }