Skip to content

Commit 2fed936

Browse files
committed
grid cables - make the thorn pulse move in continuous wave
This wave is rooted in the strongest power source - for example your most powerful solar. So it can go against the "actual flow". Revert at will
1 parent 66e358e commit 2fed936

4 files changed

Lines changed: 69 additions & 15 deletions

File tree

LuaRules/Gadgets/Shaders/gfx_overdrive_cables.frag.glsl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -591,9 +591,12 @@ void main() {
591591
// at twig-local 0 and propagates through the twig at
592592
// TWIG_SWEEP_SPEED. Inter-twig stagger feels snappy while motion
593593
// *within* a twig stays comfortable.
594-
// `spawnAlongMain` is what lets us decouple these — without it both
595-
// speeds would be tied to the same propagation rate.
596-
float wavePassedElmos = mod(gameTime * CABLE_PROP_SPEED * (1.0 + 1.2 * maxxed) - spawnAlongMain, CABLE_PROP_PERIOD / (1.0 + 0.5 * maxxed));
594+
// Stagger by the twig root's distance-from-root (packed into gridData.y by
595+
// the GS), not the per-edge along — so the wave is one wavefront radiating
596+
// from the grid root, chaining across joints instead of restarting per
597+
// edge. The within-twig sweep below still uses edge-local spawnAlongMain.
598+
float globalRoot = gridData.y;
599+
float wavePassedElmos = mod(gameTime * CABLE_PROP_SPEED * (1.0 + 1.2 * maxxed) - globalRoot, CABLE_PROP_PERIOD / (1.0 + 0.5 * maxxed));
597600
float subwavePos = TWIG_SWEEP_SPEED * (wavePassedElmos / CABLE_PROP_SPEED);
598601
float localAlong = along - spawnAlongMain;
599602
float d = localAlong - subwavePos;

LuaRules/Gadgets/Shaders/gfx_overdrive_cables.geom.glsl

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ out DataGS {
6464
float width;
6565
vec2 cableUV;
6666
vec2 timeData;
67+
// (gridEfficiency, flow, isOwnAlly). For twigs (isBranch>0.5) .y is overloaded
68+
// to the twig root's distance-from-root (flow is unused there) → pulse stagger.
6769
vec3 gridData;
6870
// Smooth per-vertex cable along-direction (3D). Linearly interpolated by
6971
// the rasteriser so the FS frame (perp3D/trueUp → cylinder normal) rotates
@@ -181,7 +183,7 @@ void emitVtx(vec3 wp, vec3 tangent3D, vec2 cuv,
181183
width = w;
182184
cableUV = cuv;
183185
timeData = td;
184-
gridData = grid.xyw; // .z dropped (unused by FS); see DataGS BUDGET NOTE
186+
gridData = grid.xyw; // .z (per-end rootDist) used in main() for twig globalRoot, not forwarded; see DataGS BUDGET NOTE
185187
cableTangent = tangent3D; // smooth along-dir → FS interpolates the lit frame
186188
spawnAlongMain = gOutSpawnAlong;
187189
gsSlot = gOutSlot;
@@ -525,7 +527,12 @@ void emitTentHalf(float side, vec2 a, vec2 d, vec2 perpAB,
525527
void emitTwig(vec2 a, vec2 d, vec2 perpAB,
526528
float halfMainW, float widthVal, float effAmp, float seed,
527529
vec4 gridD, vec2 timeD, float cap, float tCenter,
528-
float spawnAlongMain, int twigIdx, float arcDh, int numSeg) {
530+
float spawnAlongMain, int twigIdx, float arcDh, int numSeg,
531+
float globalRoot) {
532+
// Pack this twig's distance-from-root into gridData.y (flow, unused by twigs)
533+
// for the FS pulse stagger — see the DataGS gridData note.
534+
vec4 gridTwig = gridD;
535+
gridTwig.y = globalRoot;
529536
// Resolve spawn point on the wiggly main path at tCenter so twigs root on
530537
// the visible cable.
531538
float lenAB = length(d);
@@ -612,9 +619,9 @@ void emitTwig(vec2 a, vec2 d, vec2 perpAB,
612619
// follows the twig's pointing direction.
613620
gOutBranch = 1.0;
614621
gOutSpawnAlong = spawnAlongMain; // shared by all 4 twig vertices; lets FS compute twig-local along
615-
emitVtx(rootL, twigDir3D, vec2(spawnAlongMain, -1.0), twigW, gridD, timeD, cap);
616-
emitVtx(rootR, twigDir3D, vec2(spawnAlongMain, 1.0), twigW, gridD, timeD, cap);
617-
emitVtx(tipL, twigDir3D, vec2(spawnAlongMain + bLen, -1.0), twigW, gridD, timeD, cap);
622+
emitVtx(rootL, twigDir3D, vec2(spawnAlongMain, -1.0), twigW, gridTwig, timeD, cap);
623+
emitVtx(rootR, twigDir3D, vec2(spawnAlongMain, 1.0), twigW, gridTwig, timeD, cap);
624+
emitVtx(tipL, twigDir3D, vec2(spawnAlongMain + bLen, -1.0), twigW, gridTwig, timeD, cap);
618625
EndPrimitive();
619626
gOutSpawnAlong = 0.0;
620627
}
@@ -795,7 +802,16 @@ void main() {
795802
}
796803
float tCenter = float(idxArr[bestK]) / float(G);
797804
float spawnAlongMain = alongArr[bestK];
805+
// This twig root's distance-from-root: lerp the two ends' values (gridData.z,
806+
// rootA on v0 / rootB on v1) by its fractional arc position, so it stays
807+
// continuous with both pylons regardless of which end is nearer the root.
808+
// totalArc > 0 always here (a twig only emits when len3D >= ~42).
809+
float rootFrac = spawnAlongMain / alongArr[numSeg];
810+
float baseRootA = gridD.z;
811+
float baseRootB = dataIn[1].vsGridData.z;
812+
float globalRoot = baseRootA + (baseRootB - baseRootA) * rootFrac;
798813
emitTwig(a, d, perpAB, halfW, widthVal, effAmp, seed,
799-
gridD, timeD, cap, tCenter, spawnAlongMain, twigIdx, arcDh, numSeg);
814+
gridD, timeD, cap, tCenter, spawnAlongMain, twigIdx, arcDh, numSeg,
815+
globalRoot);
800816
}
801817
}

LuaRules/Gadgets/Shaders/gfx_overdrive_cables.vert.glsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
layout (location = 0) in vec2 vertPos; // (x, z) world coords
1212
layout (location = 1) in vec3 vertData; // (capacity, appearTime, witherTime)
13-
layout (location = 2) in vec4 vertGrid; // (gridEfficiency, flow, bubblePhase, isOwnAlly)
13+
layout (location = 2) in vec4 vertGrid; // (gridEfficiency, flow, rootDist, isOwnAlly); .z = per-end distance-from-root (ex-bubblePhase)
1414
layout (location = 3) in float vertSlot; // coverage SSBO slot (-1 = disabled)
1515

1616
out gl_PerVertex {

LuaRules/Gadgets/gfx_overdrive_cables.lua

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,6 +1390,23 @@ local function BuildMpCache()
13901390
end
13911391
end
13921392

1393+
-- Per-node tree-path distance from the component root. Forward pass: parent
1394+
-- precedes child in `order`, so the parent's distance is already final.
1395+
-- Topology-stable, so free to accumulate here. Drives the twig-pulse stagger
1396+
-- so thorn pulses chain across edges instead of restarting per cable.
1397+
local distFromRoot = {}
1398+
for i = 1, #order do
1399+
local u = order[i]
1400+
local pi = parentInTree[u]
1401+
if not pi then
1402+
distFromRoot[u] = 0
1403+
else
1404+
local e = edges[pi.key]
1405+
local dx, dz = e.px - e.cx, e.pz - e.cz
1406+
distFromRoot[u] = distFromRoot[pi.parent] + sqrt(dx * dx + dz * dz)
1407+
end
1408+
end
1409+
13931410
-- Static per-subtree aggregates (post-order over `order`).
13941411
local subPmax = {}
13951412
local subDmax = {}
@@ -1442,6 +1459,7 @@ local function BuildMpCache()
14421459
mpCache.subPmaxNonWind = subPmaxNonWind
14431460
mpCache.subWindCount = subWindCount
14441461
mpCache.subWindBase = subWindBase
1462+
mpCache.distFromRoot = distFromRoot
14451463
mpCache.valid = true
14461464
end
14471465

@@ -1679,6 +1697,7 @@ local function SendAll()
16791697
end
16801698

16811699
-- Bin edges by ally, in one pass.
1700+
local distFromRoot = mpCache.distFromRoot
16821701
local perAlly = {}
16831702
for key, edge in pairs(edges) do
16841703
local ally = allyOfUnit[edge.parentID] or allyOfUnit[edge.childID]
@@ -1687,7 +1706,8 @@ local function SendAll()
16871706
if not pa then
16881707
pa = {
16891708
keys = {}, pxs = {}, pzs = {}, cxs = {}, czs = {},
1690-
caps = {}, maxxed = {}, flows = {}, effs = {}, n = 0,
1709+
caps = {}, maxxed = {}, flows = {}, effs = {},
1710+
rootAs = {}, rootBs = {}, n = 0,
16911711
}
16921712
perAlly[ally] = pa
16931713
end
@@ -1699,6 +1719,10 @@ local function SendAll()
16991719
pa.caps[i] = capacities[key] or 0
17001720
pa.maxxed[i] = (spGetUnitRulesParam(edge.parentID, "OD_gridMaxxed") or spGetUnitRulesParam(edge.childID, "OD_gridMaxxed")) == 1
17011721
pa.flows[i] = flows[key] or 0
1722+
-- Per-end distance-from-root for the twig pulse. v0 = parent end,
1723+
-- v1 = child end; keyed by node so flow reorientation just swaps them.
1724+
pa.rootAs[i] = distFromRoot[edge.parentID] or 0
1725+
pa.rootBs[i] = distFromRoot[edge.childID] or 0
17021726
-- Cached per-grid lookup; fall back to child end if parent's grid
17031727
-- is unknown. 0 → magenta in the shader (unit_mex_overdrive's
17041728
-- "no grid" sentinel).
@@ -1718,6 +1742,7 @@ local function SendAll()
17181742
keys = pa.keys, pxs = pa.pxs, pzs = pa.pzs,
17191743
cxs = pa.cxs, czs = pa.czs,
17201744
caps = pa.caps, maxxed = pa.maxxed, flows = pa.flows, effs = pa.effs,
1745+
rootAs = pa.rootAs, rootBs = pa.rootBs,
17211746
})
17221747
alliesWithEdges[ally] = true
17231748
end
@@ -2322,7 +2347,11 @@ local function GenerateOrganicTree()
23222347
local witherTime = e.witherFrame and (e.witherFrame / GAME_SPEED) or 0
23232348
local eff = (e.eff or 0) * (e.maxxed and -1 or 1)
23242349
local flow = e.flow or 0
2325-
local phase = e.bubblePhase or 0
2350+
-- vertGrid.z (ex-bubblePhase, now dead — GS drops gridData.z) carries each
2351+
-- end's distance-from-root: v0 = rootA, v1 = rootB. GS lerps it to twig
2352+
-- roots to chain pulses across edges.
2353+
local rootA = e.rootA or 0
2354+
local rootB = e.rootB or 0
23262355
local isOwn = (fullview and 2) or (e.isOwnAlly and 1) or 0
23272356
-- Coverage SSBO slot index, or -1 to disable bit updates / lookups
23282357
-- on the GS side. Stored as float here for VBO layout simplicity;
@@ -2333,12 +2362,12 @@ local function GenerateOrganicTree()
23332362
-- Vertex 0: parent end (10 floats: pos2 + data3 + grid4 + slot)
23342363
verts[k+1] = e.px; verts[k+2] = e.pz
23352364
verts[k+3] = cap; verts[k+4] = appearTime; verts[k+5] = witherTime
2336-
verts[k+6] = eff; verts[k+7] = flow; verts[k+8] = phase; verts[k+9] = isOwn
2365+
verts[k+6] = eff; verts[k+7] = flow; verts[k+8] = rootA; verts[k+9] = isOwn
23372366
verts[k+10] = slot
2338-
-- Vertex 1: child end (same per-edge payload)
2367+
-- Vertex 1: child end (same per-edge payload, except rootB for its end)
23392368
verts[k+11] = e.cx; verts[k+12] = e.cz
23402369
verts[k+13] = cap; verts[k+14] = appearTime; verts[k+15] = witherTime
2341-
verts[k+16] = eff; verts[k+17] = flow; verts[k+18] = phase; verts[k+19] = isOwn
2370+
verts[k+16] = eff; verts[k+17] = flow; verts[k+18] = rootB; verts[k+19] = isOwn
23422371
verts[k+20] = slot
23432372
k = k + 20
23442373
end
@@ -2583,6 +2612,10 @@ function OnCableTreeFull(data)
25832612
e.eff = data.effs and data.effs[i] or 0
25842613
e.px, e.pz = data.pxs[i], data.pzs[i]
25852614
e.cx, e.cz = data.cxs[i], data.czs[i]
2615+
-- Refresh with px/cx so the per-end root distances track the
2616+
-- current parent/child orientation.
2617+
e.rootA = data.rootAs and data.rootAs[i] or 0
2618+
e.rootB = data.rootBs and data.rootBs[i] or 0
25862619
end
25872620
e.isOwnAlly = ownAlly
25882621
-- Late-bind a coverage slot if missing (e.g., gadget was reloaded
@@ -2616,6 +2649,8 @@ function OnCableTreeFull(data)
26162649
maxxed = data.maxxed[i],
26172650
flow = newFlow,
26182651
eff = data.effs and data.effs[i] or 0,
2652+
rootA = data.rootAs and data.rootAs[i] or 0,
2653+
rootB = data.rootBs and data.rootBs[i] or 0,
26192654
isOwnAlly = ownAlly,
26202655
appearFrame = af,
26212656
witherFrame = nil,

0 commit comments

Comments
 (0)