ui: plan 01-27 done #1
+29
-10
@@ -2,19 +2,38 @@ package calc
|
|||||||
|
|
||||||
import "math"
|
import "math"
|
||||||
|
|
||||||
// shortest distance between points on torus map
|
// 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 {
|
func ShortDistance(w, h uint32, x1, y1, x2, y2 float64) float64 {
|
||||||
return math.Hypot(Deltas(w, h, x1, y1, x2, y2))
|
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) {
|
func Deltas(w, h uint32, x1, y1, x2, y2 float64) (float64, float64) {
|
||||||
dx := math.Abs(x2 - x1)
|
return math.Abs(ShortestDelta(x1, x2, w)), math.Abs(ShortestDelta(y1, y2, h))
|
||||||
dy := math.Abs(y2 - y1)
|
}
|
||||||
if dx > float64(w/2) {
|
|
||||||
dx = float64(h) - dx
|
// ShortestDelta returns the signed delta (b - a) on a 1-D circle of
|
||||||
}
|
// circumference size, picking whichever direction has the shorter
|
||||||
if dy > float64(h/2) {
|
// absolute distance. The result lies in (-size/2, size/2]: at exactly
|
||||||
dy = float64(h) - dy
|
// half the circumference the function returns +size/2 so the tie-case
|
||||||
}
|
// is deterministic regardless of input order.
|
||||||
return dx, dy
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package calc_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"galaxy/calc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShortestDelta(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
a, b float64
|
||||||
|
size uint32
|
||||||
|
want float64
|
||||||
|
}{
|
||||||
|
{"identity", 5, 5, 100, 0},
|
||||||
|
{"forward small", 10, 30, 100, 20},
|
||||||
|
{"backward small", 30, 10, 100, -20},
|
||||||
|
{"wraps right edge", 95, 5, 100, 10},
|
||||||
|
{"wraps left edge", 5, 95, 100, -10},
|
||||||
|
{"exact half wraps positive", 0, 50, 100, 50},
|
||||||
|
{"slight beyond half goes negative", 0, 51, 100, -49},
|
||||||
|
{"size zero is degenerate identity", 5, 30, 0, 25},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got := calc.ShortestDelta(tc.a, tc.b, tc.size)
|
||||||
|
if got != tc.want {
|
||||||
|
t.Fatalf("ShortestDelta(%v, %v, %d) = %v, want %v",
|
||||||
|
tc.a, tc.b, tc.size, got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDeltasRectangularGalaxy guards against the pre-Phase-19 bug
|
||||||
|
// where the x-axis wrap used the height instead of the width on
|
||||||
|
// rectangular maps.
|
||||||
|
func TestDeltasRectangularGalaxy(t *testing.T) {
|
||||||
|
w, h := uint32(100), uint32(50)
|
||||||
|
dx, dy := calc.Deltas(w, h, 95, 10, 5, 15)
|
||||||
|
if dx != 10 {
|
||||||
|
t.Errorf("dx = %v, want 10 (wrap distance on width 100)", dx)
|
||||||
|
}
|
||||||
|
if dy != 5 {
|
||||||
|
t.Errorf("dy = %v, want 5 (no wrap on height 50)", dy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortDistanceTorus(t *testing.T) {
|
||||||
|
got := calc.ShortDistance(100, 100, 95, 95, 5, 5)
|
||||||
|
want := math.Hypot(10, 10)
|
||||||
|
if math.Abs(got-want) > 1e-9 {
|
||||||
|
t.Fatalf("ShortDistance through wrap = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user