feat: lock lowground routing; smooth river courses with the road pass's RDP+Chaikin (terrain-water task 23)
LOWGROUND is the gate verdict and the locked default ('short' stays available
behind the dial for the record). Every river polyline (upland stem + lowland
reach) is now decimated with RDP(4.0) and rounded with 4 Chaikin passes —
numerically mirroring MapGenerator.SmoothPath, since this pass is Godot-free —
before the bed profile is built, so the carved centreline carries none of the
8-connected Dijkstra 45° kinks (task-22 §4 caveat a). The monotone-descent and
sea-clamp constraints are applied to the smoothed course, unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1eacd22972
commit
148602b4c5
2 changed files with 54 additions and 2 deletions
|
|
@ -113,7 +113,9 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
// terrain-aware), "lowground" follows the lowest ground and wanders like a
|
||||
// real river — the task-22 gate decides which ships; "lowground" is the
|
||||
// provisional default pending that verdict. Width/depth scales are taste
|
||||
// dials on the flow-proportional bed profile. RiverSeaMargin is the bed's
|
||||
// dials on the flow-proportional bed profile. LOWGROUND is the LOCKED
|
||||
// default — the task-22/23 gate verdict ("short" stays available for the
|
||||
// record). RiverSeaMargin is the bed's
|
||||
// absolute floor above sea — the erosion flood-guard discipline: no river
|
||||
// bed may create inland below-sea cells, so the rendered coastline cannot
|
||||
// move even with rivers carved.
|
||||
|
|
|
|||
|
|
@ -151,6 +151,54 @@ public static class RiverCarvePass
|
|||
return stats;
|
||||
}
|
||||
|
||||
// ---- Route smoothing (task 23): the road pass's AAA pipeline, numerically ----
|
||||
// RDP(4.0) decimation + 4 Chaikin corner-cutting passes, endpoints pinned —
|
||||
// the same constants and structure as MapGenerator.SmoothPath, mirrored here
|
||||
// because this pass is Godot-free. Kills the 8-connected Dijkstra 45° kinks;
|
||||
// the bed then carves along the smoothed centreline.
|
||||
private static List<(float x, float y)> SmoothCourse(List<(float x, float y)> raw)
|
||||
{
|
||||
if (raw.Count < 3) return raw;
|
||||
var dec = Rdp(raw, 0, raw.Count - 1, 4.0f);
|
||||
if (dec.Count < 3) return raw;
|
||||
var sm = dec;
|
||||
for (int pass = 0; pass < 4; pass++)
|
||||
{
|
||||
var nxt = new List<(float x, float y)>(sm.Count * 2) { sm[0] };
|
||||
for (int i = 0; i + 1 < sm.Count; i++)
|
||||
{
|
||||
var a = sm[i]; var b = sm[i + 1];
|
||||
nxt.Add((a.x * 0.75f + b.x * 0.25f, a.y * 0.75f + b.y * 0.25f));
|
||||
nxt.Add((a.x * 0.25f + b.x * 0.75f, a.y * 0.25f + b.y * 0.75f));
|
||||
}
|
||||
nxt.Add(sm[^1]);
|
||||
sm = nxt;
|
||||
}
|
||||
return sm;
|
||||
}
|
||||
|
||||
private static List<(float x, float y)> Rdp(List<(float x, float y)> pts, int i0, int i1, float tol)
|
||||
{
|
||||
if (i1 - i0 <= 1) return new List<(float x, float y)> { pts[i0], pts[i1] };
|
||||
var a = pts[i0]; var b = pts[i1];
|
||||
float abx = b.x - a.x, aby = b.y - a.y;
|
||||
float abLen = MathF.Sqrt(abx * abx + aby * aby);
|
||||
float maxD = 0f; int maxI = i0;
|
||||
for (int i = i0 + 1; i < i1; i++)
|
||||
{
|
||||
float d = abLen < 1e-6f
|
||||
? MathF.Sqrt((pts[i].x - a.x) * (pts[i].x - a.x) + (pts[i].y - a.y) * (pts[i].y - a.y))
|
||||
: MathF.Abs(abx * (a.y - pts[i].y) - (a.x - pts[i].x) * aby) / abLen;
|
||||
if (d > maxD) { maxD = d; maxI = i; }
|
||||
}
|
||||
if (maxD <= tol) return new List<(float x, float y)> { pts[i0], pts[i1] };
|
||||
var left = Rdp(pts, i0, maxI, tol);
|
||||
var right = Rdp(pts, maxI, i1, tol);
|
||||
left.RemoveAt(left.Count - 1);
|
||||
left.AddRange(right);
|
||||
return left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deterministic Dijkstra from the start cell to the nearest ocean cell under
|
||||
/// the selected style's cost model. Returns the path start → ocean (1-px steps),
|
||||
|
|
@ -233,10 +281,12 @@ public static class RiverCarvePass
|
|||
float[,] height, int n, Func<int, int, float> seaAt,
|
||||
float coreSq, float craterCx, float craterCy, Params p, Stats stats)
|
||||
{
|
||||
// Full head→mouth polyline: upland stem, then the lowland reach if any.
|
||||
// Full head→mouth polyline: upland stem, then the lowland reach if any —
|
||||
// then SMOOTHED (task 23) so the carved centreline carries no routing kinks.
|
||||
var pts = new List<(float x, float y)>(upland);
|
||||
if (lowlandRoute != null && lowlandRoute.Count > 1)
|
||||
pts.AddRange(lowlandRoute.GetRange(1, lowlandRoute.Count - 1));
|
||||
pts = SmoothCourse(pts);
|
||||
|
||||
// Densify to ~1-px samples (plan courses are decimated ×4).
|
||||
var dense = new List<(float x, float y)>();
|
||||
|
|
|
|||
Loading…
Reference in a new issue