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
|
// terrain-aware), "lowground" follows the lowest ground and wanders like a
|
||||||
// real river — the task-22 gate decides which ships; "lowground" is the
|
// real river — the task-22 gate decides which ships; "lowground" is the
|
||||||
// provisional default pending that verdict. Width/depth scales are taste
|
// 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
|
// absolute floor above sea — the erosion flood-guard discipline: no river
|
||||||
// bed may create inland below-sea cells, so the rendered coastline cannot
|
// bed may create inland below-sea cells, so the rendered coastline cannot
|
||||||
// move even with rivers carved.
|
// move even with rivers carved.
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,54 @@ public static class RiverCarvePass
|
||||||
return stats;
|
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>
|
/// <summary>
|
||||||
/// Deterministic Dijkstra from the start cell to the nearest ocean cell under
|
/// 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),
|
/// 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[,] height, int n, Func<int, int, float> seaAt,
|
||||||
float coreSq, float craterCx, float craterCy, Params p, Stats stats)
|
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);
|
var pts = new List<(float x, float y)>(upland);
|
||||||
if (lowlandRoute != null && lowlandRoute.Count > 1)
|
if (lowlandRoute != null && lowlandRoute.Count > 1)
|
||||||
pts.AddRange(lowlandRoute.GetRange(1, lowlandRoute.Count - 1));
|
pts.AddRange(lowlandRoute.GetRange(1, lowlandRoute.Count - 1));
|
||||||
|
pts = SmoothCourse(pts);
|
||||||
|
|
||||||
// Densify to ~1-px samples (plan courses are decimated ×4).
|
// Densify to ~1-px samples (plan courses are decimated ×4).
|
||||||
var dense = new List<(float x, float y)>();
|
var dense = new List<(float x, float y)>();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue