Skip to content

Commit 3dbc1cb

Browse files
author
b
committed
fix: attach letter blocks to tube endpoints on end-hit
1 parent 25b0db1 commit 3dbc1cb

File tree

1 file changed

+45
-38
lines changed

1 file changed

+45
-38
lines changed

src/components/CircuitBackground.vue

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ interface PulseMarker {
2626
2727
interface 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);
8184
const cameraLookTarget = new THREE.Vector3(0, 0, 0);
8285
const blockGeometry = new RoundedBoxGeometry(1, 1, 1, 5, 0.2);
8386
const 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();
8687
let flowTravel = 0;
87-
let blockAssemblyGroup: THREE.Group | null = null;
88-
let blockCursor = 0;
89-
const letterBlocks: LetterBlock[] = [];
9088
9189
function 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
296292
function 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() {
345336
function 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

Comments
 (0)