Skip to content

Commit 7b0c36f

Browse files
committed
Overdrive cables: weld twig roots to the trunk's emitted rail vertex
Twigs are emitted by separate GS invocations, so they only stay attached to the trunk if they rebuild the exact geometry the tent invocations emit. The old root anchor offset 0.2*halfW along the twig's LOCAL-normal B from a max-of-window spawn at the belly: on slopes that point both diverged from the trunk's frame and sat ~tentHeight below the visible tube, detaching the twig — and tightening the wiggle noise (previous commit) shifted the trunk enough to expose the mismatch everywhere. Root on the rail instead: the twig dispatch already replays placeRibbonVertices and picks an emitted vertex (bestK); now it also rebuilds that vertex and its predecessor (same wigglyCablePoint xz, same base height) and hands them to emitTwig, which reconstructs the local rail frame — the back-diff vtxTangent and its horizontal perpendicular B_v, exactly as emitTentHalf builds them — and places the root at center + B_v*halfW*side with the same SIDE_CLEAR clamp: the very outer-rail vertex the trunk renders, plus a small Navg*TWIG_CLEAR lift against z-fighting. The chord-averaged normal moves into a shared cableNavg() helper so trunk pad, apex and twig lift all ride one axis. The twig's in-slope growth basis B is sign-aligned to B_v so a wild local terrain normal can't flip the twig back across the trunk it roots on. Reworked from a pre-rail-refactor stash that shared a global cross frame (cableCrossFrame/B3); master's per-vertex transverse axis (30b1907) made that approach obsolete, so the sync is re-expressed in rail terms.
1 parent 9abc568 commit 7b0c36f

1 file changed

Lines changed: 60 additions & 26 deletions

File tree

LuaRules/Gadgets/Shaders/gfx_overdrive_cables.geom.glsl

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,20 @@ float maxHeightInWindow(vec2 p, vec2 dirH, float fullStep) {
315315
return yMax;
316316
}
317317

318+
// Chord-averaged surface normal — the cable's shared "up", sampled at 5 anchor
319+
// points along the chord (same anchors as cableArcDh). Factored out so the twig
320+
// emitter lifts its root along the SAME axis the tent apex and clearance pad
321+
// ride, instead of a local per-point normal that diverges from the trunk frame
322+
// on uneven ground (which let the root dangle off the tube on slopes).
323+
vec3 cableNavg(vec2 a, vec2 d) {
324+
vec3 nAcc = vec3(0.0);
325+
for (int j = 0; j < 5; j++) {
326+
float tj = (float(j) + 0.5) * (1.0 / 5.0);
327+
nAcc += terrainNormal(a + d * tj);
328+
}
329+
return normalize(nAcc);
330+
}
331+
318332
// Adaptive vertex placement, factored out so the twig emitter can replay the
319333
// EXACT vertex set the ribbon renders and root onto it. Scans the max-filtered
320334
// centerline profile on a grid PLACEMENT_OVERSAMPLE× denser than the emit
@@ -427,16 +441,9 @@ void emitTentHalf(float side, vec2 a, vec2 d, vec2 perpAB,
427441
// vertical ROLL from the terrain normal; cross(worldUp, tangent) has no roll
428442
// (worldUp is fixed), it only yaws to track the bend. It also matches the FS
429443
// lighting frame, which already derives perp3D = cross(worldUp, cableTangent),
430-
// so geometry and shading agree.
431-
vec3 Navg;
432-
{
433-
vec3 nAcc = vec3(0.0);
434-
for (int j = 0; j < 5; j++) {
435-
float tj = (float(j) + 0.5) * (1.0 / 5.0);
436-
nAcc += terrainNormal(a + d * tj);
437-
}
438-
Navg = normalize(nAcc);
439-
}
444+
// so geometry and shading agree. cableNavg is shared with the twig emitter so
445+
// twig roots ride the same axis.
446+
vec3 Navg = cableNavg(a, d);
440447
vec3 cableDirH_g = normalize(vec3(d.x, 0.0, d.y));
441448
// Fallback width axis for the first vertex (chord tangent) and any vertex
442449
// whose local tangent degenerates: horizontal, perpendicular to the chord.
@@ -547,7 +554,8 @@ void emitTentHalf(float side, vec2 a, vec2 d, vec2 perpAB,
547554
void emitTwig(vec2 a, vec2 d, vec2 perpAB,
548555
float halfMainW, float widthVal, float effAmp, float seed,
549556
vec4 gridD, vec2 timeD, float cap, float tCenter,
550-
float spawnAlongMain, int twigIdx, float arcDh, int numSeg) {
557+
float spawnAlongMain, int twigIdx, float arcDh, int numSeg,
558+
vec3 railPrev, vec3 railCenter) {
551559
// Resolve spawn point on the wiggly main path at tCenter so twigs root on
552560
// the visible cable.
553561
float lenAB = length(d);
@@ -601,25 +609,41 @@ void emitTwig(vec2 a, vec2 d, vec2 perpAB,
601609
vec3 T = normalize(cableDirH - dot(cableDirH, N) * N);
602610
vec3 B = normalize(cross(N, T));
603611

612+
// Rail frame at the emitted vertex the twig roots on: tangent = back-diff
613+
// of adjacent emitted centerline points (exactly emitTentHalf's vtxTangent
614+
// at this vertex), width axis B_v = its horizontal perpendicular (exactly
615+
// the axis the tent rails are laid on).
616+
vec3 railTangent = railCenter - railPrev;
617+
float rtL = length(railTangent);
618+
railTangent = (rtL > 1e-4) ? railTangent / rtL : cableDirH;
619+
vec3 B_v = cross(vec3(0.0, 1.0, 0.0), railTangent);
620+
float bvL = length(B_v);
621+
B_v = (bvL > 1e-3) ? B_v / bvL : normalize(cross(vec3(0.0, 1.0, 0.0), cableDirH));
622+
// Local N gives B an arbitrary sign that can disagree with the rail axis on
623+
// broken ground. Align them so the twig grows AWAY from the tube on the
624+
// SAME side its root is placed (+B_v*halfMainW*side below) instead of
625+
// doubling back across the trunk.
626+
if (dot(B, B_v) < 0.0) B = -B;
627+
604628
float ca = cos(angleOff);
605629
float sa = sin(angleOff) * side;
606630
vec3 twigDir3D = ca * T + sa * B;
607631
vec3 twigPerp3D = normalize(cross(N, twigDir3D));
608632

609-
// Anchor spawn to the same max-of-window lift the main ribbon uses, offset
610-
// along the local normal N — at this 0.3 magnitude N tracks the trunk's
611-
// chord-averaged Navg pad closely enough to keep the junction seated, and N
612-
// is already the twig's own basis. TWIG_CLEAR is slightly less than
613-
// CENTERLINE_CLEAR so the junction sits just under the trunk's centerline
614-
// (z-fight avoidance, see TWIG_CLEAR comment).
615-
vec2 dirH = (lenAB > 0.0) ? d / lenAB : vec2(1.0, 0.0);
616-
float fullStep = lenAB / float(numSeg);
617-
float spawnYbase = maxHeightInWindow(spawn, dirH, fullStep);
618-
vec3 spawn3D = vec3(spawn.x, spawnYbase, spawn.y) + N * TWIG_CLEAR;
619-
620-
// Anchor the root to the spawn-side edge of the cable's in-slope cross
621-
// section so the twig pokes out of the side, not the midline.
622-
vec3 root3D = spawn3D + B * (halfMainW * 0.2 * side);
633+
// Root anchor: reproduce the trunk's spawn-side OUTER rail vertex — the
634+
// same center ± B_v*halfW the tent slopes emit, with the same SIDE_CLEAR
635+
// anti-underground clamp — so the junction is welded to a vertex the
636+
// ribbon actually renders. The old code offset by 0.2*halfW along the
637+
// LOCAL-normal B from a max-of-window spawn at the belly; on slopes that
638+
// both diverged from the trunk frame and dangled below the visible tube,
639+
// detaching the twig. A small lift along the trunk's shared Navg
640+
// (TWIG_CLEAR) tucks the junction just proud of the rail to avoid
641+
// z-fighting.
642+
vec3 Navg = cableNavg(a, d);
643+
vec3 center3D = railCenter + Navg * CENTERLINE_CLEAR;
644+
vec3 root3D = center3D + B_v * (halfMainW * side);
645+
root3D.y = max(root3D.y, heightAtWorldPos(root3D.xz) + SIDE_CLEAR);
646+
root3D += Navg * TWIG_CLEAR;
623647
vec3 tip3D = root3D + twigDir3D * bLen;
624648

625649
vec3 rootL = root3D - twigPerp3D * twigHWr;
@@ -817,7 +841,17 @@ void main() {
817841
}
818842
float tCenter = float(idxArr[bestK]) / float(G);
819843
float spawnAlongMain = alongArr[bestK];
844+
// Rebuild the rooted vertex and its predecessor exactly as emitTentHalf
845+
// does (same wigglyCablePoint xz, same base height) so emitTwig can
846+
// reconstruct the local rail frame at the junction. bestK >= 1 always,
847+
// so bestK-1 is a valid emitted vertex.
848+
float tPrev = float(idxArr[bestK - 1]) / float(G);
849+
vec2 pC = wigglyCablePoint(a, d, perpAB, tCenter, lenAB, arcDh, effAmp, seed);
850+
vec2 pP = wigglyCablePoint(a, d, perpAB, tPrev, lenAB, arcDh, effAmp, seed);
851+
vec3 railCenter = vec3(pC.x, yBaseArr[bestK], pC.y);
852+
vec3 railPrev = vec3(pP.x, yBaseArr[bestK - 1], pP.y);
820853
emitTwig(a, d, perpAB, halfW, widthVal, effAmp, seed,
821-
gridD, timeD, cap, tCenter, spawnAlongMain, twigIdx, arcDh, numSeg);
854+
gridD, timeD, cap, tCenter, spawnAlongMain, twigIdx, arcDh, numSeg,
855+
railPrev, railCenter);
822856
}
823857
}

0 commit comments

Comments
 (0)