Files
galaxy-game/client/world/torus_line_segments_into.go
T
2026-03-08 23:30:11 +02:00

136 lines
3.7 KiB
Go

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
}