Line light animation problem

I have a problem in animating the lights path of the line. Basically what I want to happen here is that… there is one-animation in bottom-face-edges of the box. And two-loop animation in side-face-edges of the box. I’m not sure if this is TSL shader problem or the logic in edges… can anyone help me into this?

I can’t fix it… I want to repeat the loop in left-side-face of edges of the box. Check the image for you to be able to understand it… as you can see I don’t want the lights to fade. i want it to keep continuing but it does not.

Here is the code. Anyhelp would do thanks!

const TSLBoxLoader = () => {

    // 12 edges with manually assigned t values so the light travels in order
    const edgeGeo = useMemo(() => {
        const h = 0.325;

        // Define Bottom vertices (b) and Top vertices (t)
        const b0 = [-h, -h, -h]; // Bottom Back-Left
        const b1 = [h, -h, -h]; // Bottom Back-Right
        const b2 = [h, -h, h]; // Bottom Front-Right
        const b3 = [-h, -h, h]; // Bottom Front-Left

        const t0 = [-h, h, -h]; // Top Back-Left
        const t1 = [h, h, -h]; // Top Back-Right
        const t2 = [h, h, h]; // Top Front-Right
        const t3 = [-h, h, h]; // Top Front-Left

        // The exact sequence: Bottom -> Yo-Yo the Sides -> Top
        const sequence = [
            // ── Bottom × 1 ──────────────────────────
            b0, b1, b2, b3, b0,

            // ── Left side × 2 ───────────────────────
            // Pass 1: b0 → up → across top → down → back
            t0, t3, b3, b0,
            // Pass 2: repeat the same left-side loop
            t0, t3, b3, b0,
        ];

        const edges = [];
        // Connect the dots into segment pairs
        for (let i = 0; i < sequence.length - 1; i++) {
            edges.push([...sequence[i], ...sequence[i + 1]]);
        }

        const positions = [];
        const lineTVals = [];
        const totalSegments = edges.length;
        edges.forEach((e, i) => {
            positions.push(e[0], e[1], e[2], e[3], e[4], e[5]);
            // lineT smoothly travels from 0 to 1 across this exact path
            lineTVals.push(i / totalSegments, (i + 1) / totalSegments);
        });

        const geo = new THREE.BufferGeometry();
        geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
        geo.setAttribute('lineT', new THREE.Float32BufferAttribute(lineTVals, 1));
        return geo;
    }, []);

    const items = useMemo(() => {
        return [0, 1, 2].map(() => {
            const prog = uniform(0.0)
            const lineTAttr = attribute('lineT', 'float')

            // How far behind the travelling head is this vertex?
            // delta = 0 at the head, grows going backward
            const delta = fract(prog.sub(lineTAttr).add(1.0))
            const tailLen = float(0.28)

            // Bright tail: 1 at head → 0 at tail end
            const brightness = smoothstep(tailLen, float(0.0), delta)
            // Extra white-hot flare right at the head
            const headFlare = smoothstep(float(0.04), float(0.0), delta)

            const cyan = color(0x00e5ff)
            const white = color(0xffffff)
            const dimBase = color(0x001a26)   // faint outline so box reads when idle

            const mat = new THREE.LineBasicNodeMaterial()
            mat.colorNode = mix(dimBase, mix(cyan, white, headFlare), brightness)
            mat.transparent = true
            mat.side = THREE.DoubleSide
            return { mat, prog }
        })
    }, [])


    useFrame(({ clock }) => {
        const t = clock.elapsedTime
        items.forEach(({ prog }, i) => {
            // Each box offset by 1/3 of the cycle so they stagger
            prog.value = (t * 0.09 + i * (1 / 3)) % 1
        })
    })

    return (
        <>
            <group>
                {[-1.1, 0, 1.1].map((x, i) => (
                    // Tilt each box to a nice isometric-ish angle so depth is visible
                    <lineSegments
                        key={i}
                        position={[x, 0, 0]}
                        rotation={[0.5, 0.7, 0]}
                        material={items[i].mat}
                    >
                        <primitive object={edgeGeo} attach="geometry" />
                    </lineSegments>
                ))}
            </group>
            <OrbitControls />
        </>
    )
}

Interesting effect! I think the issue is less about the shader and more about how the path parameter (lineT) is mapped across the segments.

Right now lineT is normalized across the entire sequence (i / totalSegments). When the animation wraps using fract(...), the shader assumes the path is one continuous loop from 0 → 1. Because of that, when you repeat the left-side path in your sequence, the light still treats it as a single continuous pass, so the second loop doesn’t visually persist the way you expect.

A couple of things you might try:

1. Separate the loops logically
Instead of treating the entire sequence as one normalized path, you could reset lineT for the repeated side-loop segments so they run in their own 0→1 range. That way the shader will treat them as independent loops rather than part of the global progression.

2. Use modulo for repeating segments
If the left-side edges should loop continuously, you can remap their lineT values locally:

const localT = fract(prog.sub(lineTAttr * repeatFactor).add(1.0))

This makes the shader repeat within that subset of edges.

3. Double-check the sequence continuity
Your path currently goes:

Bottom loop → left side loop → same left side loop again

But the shader tail (delta = fract(...)) expects a continuous cyclic path, so when the head reaches the end of the first side loop it immediately continues forward, which can visually cancel the second loop.

Another approach is to explicitly duplicate the geometry with a separate progress uniform for the second loop rather than relying on the same prog.

Overall the concept is solid though, using lineT as a path parameter combined with fract() and smoothstep() is a really nice technique for animated edge highlights in Three.js shaders.

If you want, I can also show a cleaner pattern for path-based line animations that avoids these sequencing problems entirely (it uses cumulative segment length instead of manual lineT indexing).

Hi, sorry I’ve been busy, but yes I would appreciate if you can make a demo on it. Overall, Thank you.. I will tried to fix this while waiting for your example /demo.