d63fe44618
The pre-existing `Deltas` helper used the height to wrap the x-axis, which silently produced wrong values on any rectangular galaxy (`w != h`). Square galaxies — the only configuration the engine ships today — masked the bug, so it stayed in tree. `Deltas` is now a thin wrapper around the new `ShortestDelta(a, b, size)`, which returns the signed per-axis shortest delta on a 1-D circle (range `(-size/2, size/2]`). The signed flavour is what the Phase 19 ship-group renderer needs to draw an IncomingGroup trajectory across the torus seam; `Deltas` continues to return the pair of absolute deltas for distance computation. Adds `pkg/calc/map_test.go` with table-driven coverage for both helpers, including a regression that exercises the rectangular case the bug was hiding behind, and the half-circumference tie-break. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
40 lines
1.3 KiB
Go
40 lines
1.3 KiB
Go
package calc
|
||
|
||
import "math"
|
||
|
||
// ShortDistance returns the shortest Euclidean distance between two
|
||
// points on a torus of size w×h.
|
||
func ShortDistance(w, h uint32, x1, y1, x2, y2 float64) float64 {
|
||
return math.Hypot(Deltas(w, h, x1, y1, x2, y2))
|
||
}
|
||
|
||
// Deltas returns the per-axis absolute distance between two points on
|
||
// a torus of size w×h. Each axis wraps independently: the x-axis
|
||
// against width w, the y-axis against height h. The returned values
|
||
// are always non-negative; combine via math.Hypot for the Euclidean
|
||
// torus distance, or use [ShortestDelta] when the signed direction
|
||
// matters (for example when drawing a wrap-aware line).
|
||
func Deltas(w, h uint32, x1, y1, x2, y2 float64) (float64, float64) {
|
||
return math.Abs(ShortestDelta(x1, x2, w)), math.Abs(ShortestDelta(y1, y2, h))
|
||
}
|
||
|
||
// ShortestDelta returns the signed delta (b - a) on a 1-D circle of
|
||
// circumference size, picking whichever direction has the shorter
|
||
// absolute distance. The result lies in (-size/2, size/2]: at exactly
|
||
// half the circumference the function returns +size/2 so the tie-case
|
||
// is deterministic regardless of input order.
|
||
func ShortestDelta(a, b float64, size uint32) float64 {
|
||
if size == 0 {
|
||
return b - a
|
||
}
|
||
s := float64(size)
|
||
d := math.Mod(b-a, s)
|
||
half := s / 2
|
||
if d > half {
|
||
d -= s
|
||
} else if d <= -half {
|
||
d += s
|
||
}
|
||
return d
|
||
}
|