@@ -26,6 +26,9 @@ interface PulseMarker {
2626
2727interface CircuitLine {
2828 group: THREE .Group ;
29+ blockGroup: THREE .Group ;
30+ blocks: LetterBlock [];
31+ blockCursor: number ;
2932 segments: THREE .Mesh [];
3033 joints: THREE .Mesh [];
3134 material: THREE .MeshBasicMaterial ;
@@ -81,12 +84,7 @@ const focalTarget = new THREE.Vector3(0, 0, 0);
8184const cameraLookTarget = new THREE .Vector3 (0 , 0 , 0 );
8285const blockGeometry = new RoundedBoxGeometry (1 , 1 , 1 , 5 , 0.2 );
8386const blockLabelGeometry = new THREE .PlaneGeometry (0.62 , 0.62 );
84- const blockAssemblyOffset = new THREE .Vector3 (0 , - 19 , CENTER_DEPTH + 26 );
85- const blockLookAt = new THREE .Vector3 ();
8687let flowTravel = 0 ;
87- let blockAssemblyGroup: THREE .Group | null = null ;
88- let blockCursor = 0 ;
89- const letterBlocks: LetterBlock [] = [];
9088
9189function rand(min : number , max : number ) {
9290 return min + Math .random () * (max - min );
@@ -249,21 +247,20 @@ function createLetterBlock(letter: string, bornAt: number): LetterBlock {
249247 return { group , labelTexture , labelMaterial , bornAt };
250248}
251249
252- function layoutLetterBlocks() {
253- for (let i = 0 ; i < letterBlocks .length ; i += 1 ) {
250+ function layoutLetterBlocks(blocks : LetterBlock [] ) {
251+ for (let i = 0 ; i < blocks .length ; i += 1 ) {
254252 const col = i % BLOCK_COLS ;
255253 const row = Math .floor (i / BLOCK_COLS ) % BLOCK_ROWS ;
256254 const layer = Math .floor (i / (BLOCK_COLS * BLOCK_ROWS ));
257255 const x = (col - (BLOCK_COLS - 1 ) * 0.5 ) * (BLOCK_SIZE + BLOCK_GAP );
258256 const y = row * (BLOCK_SIZE + BLOCK_GAP );
259257 const z = layer * (BLOCK_SIZE * 0.78 + BLOCK_GAP * 0.6 );
260- letterBlocks [i ].group .position .set (x , y , z );
258+ blocks [i ].group .position .set (x , y , z );
261259 }
262260}
263261
264- function removeLetterBlock(block : LetterBlock ) {
265- if (! blockAssemblyGroup ) return ;
266- blockAssemblyGroup .remove (block .group );
262+ function removeLetterBlock(block : LetterBlock , fromGroup : THREE .Group ) {
263+ fromGroup .remove (block .group );
267264 for (const child of block .group .children ) {
268265 const mesh = child as THREE .Mesh ;
269266 if (mesh .material && ! (Array .isArray (mesh .material ))) {
@@ -276,36 +273,30 @@ function removeLetterBlock(block: LetterBlock) {
276273 block .labelTexture .dispose ();
277274}
278275
279- function spawnLetterBlock(elapsed : number ) {
280- if (! scene || ! blockAssemblyGroup ) return ;
281- const letter = BLOCK_SEQUENCE [blockCursor % BLOCK_SEQUENCE .length ];
282- blockCursor += 1 ;
276+ function spawnLetterBlock(circuit : CircuitLine , elapsed : number ) {
277+ const letter = BLOCK_SEQUENCE [circuit .blockCursor % BLOCK_SEQUENCE .length ];
278+ circuit .blockCursor += 1 ;
283279 const block = createLetterBlock (letter , elapsed );
284280 block .group .scale .setScalar (0.14 );
285- blockAssemblyGroup .add (block .group );
286- letterBlocks .push (block );
287- if (letterBlocks .length > MAX_BLOCKS ) {
288- const oldest = letterBlocks .shift ();
281+ circuit . blockGroup .add (block .group );
282+ circuit . blocks .push (block );
283+ if (circuit . blocks .length > MAX_BLOCKS ) {
284+ const oldest = circuit . blocks .shift ();
289285 if (oldest ) {
290- removeLetterBlock (oldest );
286+ removeLetterBlock (oldest , circuit . blockGroup );
291287 }
292288 }
293- layoutLetterBlocks ();
289+ layoutLetterBlocks (circuit . blocks );
294290}
295291
296292function updateLetterBlocks(elapsed : number ) {
297- if (! blockAssemblyGroup || ! camera ) return ;
298-
299- blockAssemblyGroup .position .copy (focalPoint ).add (blockAssemblyOffset );
300- blockLookAt .copy (camera .position );
301- blockAssemblyGroup .lookAt (blockLookAt );
302- blockAssemblyGroup .rotateY (Math .PI );
303-
304- for (const block of letterBlocks ) {
305- const age = elapsed - block .bornAt ;
306- const grow = THREE .MathUtils .clamp (age * 5.4 , 0 , 1 );
307- const bounce = grow < 1 ? 1 + Math .sin (grow * Math .PI * 2.3 ) * 0.11 * (1 - grow ) : 1 ;
308- block .group .scale .setScalar (grow * bounce );
293+ for (const circuit of circuits ) {
294+ for (const block of circuit .blocks ) {
295+ const age = elapsed - block .bornAt ;
296+ const grow = THREE .MathUtils .clamp (age * 5.4 , 0 , 1 );
297+ const bounce = grow < 1 ? 1 + Math .sin (grow * Math .PI * 2.3 ) * 0.11 * (1 - grow ) : 1 ;
298+ block .group .scale .setScalar (grow * bounce );
299+ }
309300 }
310301}
311302
@@ -345,13 +336,22 @@ function addCenterHole() {
345336function createCircuitFlow() {
346337 if (! scene ) return ;
347338
348- blockAssemblyGroup = new THREE .Group ();
349- scene .add (blockAssemblyGroup );
350-
351339 for (let i = 0 ; i < CIRCUIT_COUNT ; i += 1 ) {
352340 const pathPoints = createOrthogonalPath ();
353341 const sampler = createSampler (pathPoints );
354342 const group = new THREE .Group ();
343+ const endpoint = pathPoints [pathPoints .length - 1 ];
344+ const beforeEndpoint = pathPoints [Math .max (0 , pathPoints .length - 2 )];
345+ const endpointDirection = endpoint .clone ().sub (beforeEndpoint );
346+ if (endpointDirection .lengthSq () < 1e-5 ) {
347+ endpointDirection .set (0 , 0 , 1 );
348+ } else {
349+ endpointDirection .normalize ();
350+ }
351+ const blockGroup = new THREE .Group ();
352+ blockGroup .position .copy (endpoint ).addScaledVector (endpointDirection , BLOCK_SIZE * 0.65 );
353+ blockGroup .quaternion .setFromUnitVectors (new THREE .Vector3 (0 , 0 , 1 ), endpointDirection );
354+ group .add (blockGroup );
355355 const hue = rand (0.51 , 0.57 );
356356 const baseColor = new THREE .Color ().setHSL (hue , rand (0.62 , 0.8 ), rand (0.42 , 0.54 ));
357357 const material = new THREE .MeshBasicMaterial ({
@@ -433,6 +433,9 @@ function createCircuitFlow() {
433433
434434 circuits .push ({
435435 group ,
436+ blockGroup ,
437+ blocks: [],
438+ blockCursor: 0 ,
436439 segments ,
437440 joints ,
438441 material ,
@@ -518,10 +521,10 @@ function animate() {
518521 for (const pulse of circuit .pulses ) {
519522 let progress = (elapsed * linePulseSpeed + pulse .phase ) % 1 ;
520523 if (progress < 0 ) progress += 1 ;
521- const wrappedAtEnd = progress < pulse . lastProgress ;
524+ const wrappedAtEnd = pulse . lastProgress > 0.74 && progress < 0.26 ;
522525 pulse .lastProgress = progress ;
523526 if (wrappedAtEnd ) {
524- spawnLetterBlock (elapsed );
527+ spawnLetterBlock (circuit , elapsed );
525528 }
526529 if (proximity > 0.58 ) {
527530 const jumpStep = THREE .MathUtils .lerp (0.028 , 0.11 , (proximity - 0.58 ) / 0.42 );
@@ -634,6 +637,10 @@ function disposeScene() {
634637 for (const joint of circuit .joints ) {
635638 joint .geometry .dispose ();
636639 }
640+ for (const block of circuit .blocks ) {
641+ removeLetterBlock (block , circuit .blockGroup );
642+ }
643+ circuit .blocks .length = 0 ;
637644 circuit .material .dispose ();
638645 for (const pulse of circuit .pulses ) {
639646 pulse .core .geometry .dispose ();
0 commit comments