Add organic S-curve meandering to tile road texture shader
This commit is contained in:
+71
-8
@@ -24,6 +24,7 @@ uniform float roadRadius;
|
||||
uniform float roadBorderWidth;
|
||||
uniform float roadTextureScale;
|
||||
uniform float roadOpacity;
|
||||
uniform float roadMeander;
|
||||
uniform vec3 roadColor;
|
||||
uniform vec3 roadBorderColor;
|
||||
|
||||
@@ -102,6 +103,49 @@ float bezierCurveDist(vec2 p, vec2 p0, vec2 p2) {
|
||||
return min(min(d0, d1), min(d2, d3));
|
||||
}
|
||||
|
||||
float meanderingBezierDist(vec2 p, vec2 p0, vec2 p2, float amp, float s1, float s2) {
|
||||
vec2 chord = p2 - p0;
|
||||
float cLen = length(chord);
|
||||
vec2 perp = (cLen > 1e-4) ? vec2(-chord.y / cLen, chord.x / cLen) : vec2(0.0, 1.0);
|
||||
|
||||
// 6-segment meandering curve from p0 (t=0) to p2 (t=1).
|
||||
// At t=0 and t=1, sin(pi*t) == 0 and sin(2*pi*t) == 0, so endpoints remain locked at p0 and p2!
|
||||
const float PI_VAL = 3.14159265;
|
||||
vec2 prevPt = p0;
|
||||
float minDist = 999.0;
|
||||
for (int i = 1; i <= 6; ++i) {
|
||||
float t = float(i) / 6.0;
|
||||
float omt = 1.0 - t;
|
||||
// Base quadratic Bezier through (0, 0)
|
||||
vec2 basePt = (omt * omt) * p0 + (t * t) * p2;
|
||||
// Asymmetric S-curve + bend wave perpendicular to the road chord
|
||||
float wave = amp * (0.65 * s1 * sin(PI_VAL * t) + 0.85 * s2 * sin(2.0 * PI_VAL * t));
|
||||
vec2 curPt = basePt + perp * wave;
|
||||
minDist = min(minDist, distToSegment(p, prevPt, curPt));
|
||||
prevPt = curPt;
|
||||
}
|
||||
return minDist;
|
||||
}
|
||||
|
||||
float meanderingBranchDist(vec2 p, vec2 pe, vec2 centerHub, float amp, float s) {
|
||||
vec2 dir = centerHub - pe;
|
||||
float dLen = length(dir);
|
||||
vec2 perp = (dLen > 1e-4) ? vec2(-dir.y / dLen, dir.x / dLen) : vec2(0.0, 1.0);
|
||||
const float PI_VAL = 3.14159265;
|
||||
|
||||
vec2 prevPt = pe;
|
||||
float minDist = 999.0;
|
||||
for (int i = 1; i <= 4; ++i) {
|
||||
float t = float(i) / 4.0;
|
||||
vec2 basePt = mix(pe, centerHub, t);
|
||||
float wave = amp * 0.65 * s * sin(PI_VAL * t);
|
||||
vec2 curPt = basePt + perp * wave;
|
||||
minDist = min(minDist, distToSegment(p, prevPt, curPt));
|
||||
prevPt = curPt;
|
||||
}
|
||||
return minDist;
|
||||
}
|
||||
|
||||
float getRiverDistance(vec2 p, int mask) {
|
||||
if (mask == 0) return 999.0;
|
||||
|
||||
@@ -126,7 +170,13 @@ float getRiverDistance(vec2 p, int mask) {
|
||||
}
|
||||
}
|
||||
|
||||
float getRoadDistance(vec2 p, int mask) {
|
||||
float getRoadDistance(vec2 p, int mask, vec2 tileCenterWorld, float amp) {
|
||||
// Deterministic per-tile random signs so each tile meanders uniquely
|
||||
float h1 = fract(sin(dot(tileCenterWorld, vec2(12.9898, 78.233))) * 43758.5453);
|
||||
float h2 = fract(sin(dot(tileCenterWorld, vec2(39.3468, 11.135))) * 24634.6345);
|
||||
float s1 = (h1 > 0.5) ? 1.0 : -1.0;
|
||||
float s2 = (h2 > 0.5) ? 1.0 : -1.0;
|
||||
|
||||
int edges[6];
|
||||
int count = 0;
|
||||
for (int e = 0; e < 6; ++e) {
|
||||
@@ -137,18 +187,21 @@ float getRoadDistance(vec2 p, int mask) {
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
// Unconnected road on a single tile: short natural country track segment at tile center
|
||||
return distToSegment(p, vec2(-0.14, -0.05), vec2(0.14, 0.05));
|
||||
// Unconnected road on a single tile: short winding country track at tile center
|
||||
return meanderingBezierDist(p, vec2(-0.16, -0.06 * s1), vec2(0.16, 0.06 * s1), amp * 0.6, s1, s2);
|
||||
} else if (count == 1) {
|
||||
return distToSegment(p, EDGE_MIDPOINTS[edges[0]], vec2(0.0));
|
||||
vec2 centerHub = vec2(s1, s2) * (amp * 0.25);
|
||||
return meanderingBranchDist(p, EDGE_MIDPOINTS[edges[0]], centerHub, amp, s1);
|
||||
} else if (count == 2) {
|
||||
return bezierCurveDist(p, EDGE_MIDPOINTS[edges[0]], EDGE_MIDPOINTS[edges[1]]);
|
||||
return meanderingBezierDist(p, EDGE_MIDPOINTS[edges[0]], EDGE_MIDPOINTS[edges[1]], amp, s1, s2);
|
||||
} else {
|
||||
vec2 centerHub = vec2(s1, s2) * (amp * 0.22);
|
||||
float minDist = 999.0;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
minDist = min(minDist, distToSegment(p, EDGE_MIDPOINTS[edges[i]], vec2(0.0)));
|
||||
float branchSign = ((edges[i] % 2) == 0) ? s1 : -s2;
|
||||
minDist = min(minDist, meanderingBranchDist(p, EDGE_MIDPOINTS[edges[i]], centerHub, amp * 0.75, branchSign));
|
||||
for (int j = i + 1; j < count; ++j) {
|
||||
minDist = min(minDist, bezierCurveDist(p, EDGE_MIDPOINTS[edges[i]], EDGE_MIDPOINTS[edges[j]]));
|
||||
minDist = min(minDist, meanderingBezierDist(p, EDGE_MIDPOINTS[edges[i]], EDGE_MIDPOINTS[edges[j]], amp * 0.65, s1, s2));
|
||||
}
|
||||
}
|
||||
return minDist;
|
||||
@@ -319,7 +372,17 @@ void main() {
|
||||
|
||||
// 6b. Road Texture Integration (blended directly into the tile surface texture)
|
||||
if (hasRoad == 1 && oceanWaterFac < 0.95) {
|
||||
float rawRoadDist = getRoadDistance(fragVert.xy, roadMask);
|
||||
vec2 tileCenterWorld = vWorldPos.xy - fragVert.xy;
|
||||
float amp = max(0.0, roadMeander);
|
||||
|
||||
// Additional subtle world-space domain warp inside the tile (fades to 0 at hex border so edges stay locked)
|
||||
float borderFade = 1.0 - smoothstep(0.24, 0.42, length(fragVert.xy));
|
||||
vec2 microWarp = vec2(
|
||||
sin(vWorldPos.y * 13.0 + vWorldPos.x * 5.0),
|
||||
cos(vWorldPos.x * 13.0 - vWorldPos.y * 5.0)
|
||||
) * (amp * 0.22 * borderFade);
|
||||
|
||||
float rawRoadDist = getRoadDistance(fragVert.xy + microWarp, roadMask, tileCenterWorld, amp);
|
||||
float rScale = max(0.5, roadTextureScale);
|
||||
vec2 uvRoad = vWorldPos.xy * rScale;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user