eloigil6 commited on
Commit
7e5fd4d
·
1 Parent(s): d14073a

Add bird animation and cassette pile to the scene. Updated main.js to include bird movement and tail flicking behavior, and enhanced world.js with new cassette pile, walkman, and headphones models. Integrated cable geometry for visual detail and adjusted bird positioning on the bench.

Browse files
Files changed (2) hide show
  1. frontend/main.js +26 -0
  2. frontend/world.js +167 -1
frontend/main.js CHANGED
@@ -121,6 +121,8 @@ scene.traverse((obj) => {
121
  obj.matrixAutoUpdate = false;
122
  });
123
  for (const cloud of world.clouds) cloud.matrixAutoUpdate = true;
 
 
124
 
125
  // ---------------------------------------------------------------------------
126
  // Vending machine interaction: hover outline + floating label, click hook
@@ -318,6 +320,21 @@ window.lofinityDebug = {
318
  stats() {
319
  return { ...renderer.info.render, ...renderer.info.memory };
320
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  };
322
 
323
  // ---------------------------------------------------------------------------
@@ -347,6 +364,15 @@ function animate(now = 0) {
347
  if (cloud.position.x > 170) cloud.position.x = -170;
348
  }
349
 
 
 
 
 
 
 
 
 
 
350
  if (intro.phase === "loading" || intro.phase === "held") {
351
  // Barely-there float while gazing at the sky
352
  camera.position.y = CAM_START.y + Math.sin(elapsed * 0.5) * 0.08;
 
121
  obj.matrixAutoUpdate = false;
122
  });
123
  for (const cloud of world.clouds) cloud.matrixAutoUpdate = true;
124
+ world.bird.matrixAutoUpdate = true;
125
+ world.bird.userData.tail.matrixAutoUpdate = true;
126
 
127
  // ---------------------------------------------------------------------------
128
  // Vending machine interaction: hover outline + floating label, click hook
 
320
  stats() {
321
  return { ...renderer.info.render, ...renderer.info.memory };
322
  },
323
+ goto(px, py, pz, lx, ly, lz) {
324
+ this.skipIntro();
325
+ camera.position.set(px, py, pz);
326
+ lookTarget.set(lx, ly, lz);
327
+ view.mode = "machine"; // parks the camera (no idle sway)
328
+ },
329
+ birdInfo() {
330
+ const b = world.bird;
331
+ return {
332
+ rotY: b.rotation.y,
333
+ posY: b.position.y,
334
+ auto: b.matrixAutoUpdate,
335
+ matrixRotY: Math.atan2(b.matrix.elements[8], b.matrix.elements[10]),
336
+ };
337
+ },
338
  };
339
 
340
  // ---------------------------------------------------------------------------
 
364
  if (cloud.position.x > 170) cloud.position.x = -170;
365
  }
366
 
367
+ // the bench bird looks around, hops now and then, flicks its tail
368
+ const bird = world.bird;
369
+ bird.rotation.y =
370
+ -0.5 + Math.sin(elapsed * 0.4) * 0.4 + Math.sin(elapsed * 1.1) * 0.12;
371
+ bird.position.y =
372
+ bird.userData.baseY + Math.pow(Math.max(0, Math.sin(elapsed * 1.9)), 12) * 0.06;
373
+ bird.userData.tail.rotation.x =
374
+ -0.35 + Math.pow(Math.max(0, Math.sin(elapsed * 1.3 + 1)), 8) * 0.5;
375
+
376
  if (intro.phase === "loading" || intro.phase === "held") {
377
  // Barely-there float while gazing at the sky
378
  camera.position.y = CAM_START.y + Math.sin(elapsed * 0.5) * 0.08;
frontend/world.js CHANGED
@@ -413,6 +413,136 @@ function buildBench() {
413
  return shadows(g);
414
  }
415
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  // --- lamp post ---------------------------------------------------------------
417
 
418
  function buildLamp() {
@@ -849,6 +979,42 @@ export function buildWorld(scene) {
849
  bench.position.set(1.7, 0.3, 4.4);
850
  scene.add(bench);
851
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
852
  const sign = buildSign();
853
  sign.position.set(-8.3, 0.3, 4.2);
854
  sign.rotation.y = 0.12;
@@ -931,5 +1097,5 @@ export function buildWorld(scene) {
931
  const clouds = buildClouds();
932
  for (const cloud of clouds) scene.add(cloud);
933
 
934
- return { vending, bench, sign, lamp, bigTree, house, clouds };
935
  }
 
413
  return shadows(g);
414
  }
415
 
416
+ // --- cassette pile -----------------------------------------------------------
417
+
418
+ function buildCassette(color) {
419
+ const g = new THREE.Group();
420
+ const shell = box(0.46, 0.075, 0.3, lambert(color));
421
+ shell.position.y = 0.0375;
422
+ g.add(shell);
423
+ const label = box(0.32, 0.012, 0.2, lambert(0xf4ecd8));
424
+ label.position.y = 0.078;
425
+ g.add(label);
426
+ const tapeWindow = box(0.16, 0.013, 0.08, lambert(0x1d232f));
427
+ tapeWindow.position.y = 0.079;
428
+ g.add(tapeWindow);
429
+ return g;
430
+ }
431
+
432
+ function buildCassettePile() {
433
+ const g = new THREE.Group();
434
+ const colors = [0x2e3440, 0xf2e8d5, 0xd95d4e, 0x7cc4ea, 0xf2c84b, 0x4a3b2c];
435
+ // [x, y, z, yaw] — three flat, two crossed on top, one tilted at the summit
436
+ const placements = [
437
+ [0, 0, 0, 0.15],
438
+ [0.5, 0, 0.05, -0.25],
439
+ [-0.46, 0, -0.04, 0.4],
440
+ [0.22, 0.088, 0.02, 1.35],
441
+ [-0.28, 0.088, -0.03, -1.15],
442
+ [-0.02, 0.176, 0, 0.55],
443
+ ];
444
+ placements.forEach(([x, y, z, yaw], i) => {
445
+ const cassette = buildCassette(colors[i % colors.length]);
446
+ cassette.position.set(x, y, z);
447
+ cassette.rotation.y = yaw;
448
+ if (i === placements.length - 1) cassette.rotation.z = 0.07;
449
+ g.add(cassette);
450
+ });
451
+ return shadows(g);
452
+ }
453
+
454
+ // --- walkman + vintage headphones ---------------------------------------------
455
+
456
+ function buildWalkman() {
457
+ const g = new THREE.Group();
458
+ const body = box(0.5, 0.12, 0.36, lambert(0xc7cdd4));
459
+ body.position.y = 0.06;
460
+ g.add(body);
461
+
462
+ const tapeWindow = box(0.3, 0.015, 0.2, lambert(0x1d232f));
463
+ tapeWindow.position.set(-0.06, 0.125, 0);
464
+ g.add(tapeWindow);
465
+ const tapeLabel = box(0.14, 0.012, 0.1, lambert(0xf4ecd8));
466
+ tapeLabel.position.set(-0.06, 0.133, 0);
467
+ g.add(tapeLabel);
468
+
469
+ // transport buttons along the edge, the red one is record
470
+ [0x3a4254, 0x3a4254, 0xd95d4e].forEach((color, i) => {
471
+ const button = box(0.05, 0.05, 0.06, lambert(color));
472
+ button.position.set(0.19, 0.12, -0.1 + i * 0.1);
473
+ g.add(button);
474
+ });
475
+ return shadows(g);
476
+ }
477
+
478
+ function buildHeadphones() {
479
+ const g = new THREE.Group();
480
+ const band = new THREE.Mesh(
481
+ new THREE.TorusGeometry(0.2, 0.018, 8, 18, Math.PI),
482
+ lambert(0x6b7280)
483
+ );
484
+ band.rotation.x = -Math.PI / 2;
485
+ band.position.y = 0.035;
486
+ g.add(band);
487
+ for (const side of [-1, 1]) {
488
+ const cup = cyl(0.085, 0.085, 0.05, 12, lambert(0x3a4254));
489
+ cup.position.set(side * 0.2, 0.03, 0);
490
+ g.add(cup);
491
+ const foam = cyl(0.08, 0.08, 0.026, 12, lambert(0xe8893c));
492
+ foam.position.set(side * 0.2, 0.068, 0);
493
+ g.add(foam);
494
+ }
495
+ return shadows(g);
496
+ }
497
+
498
+ // --- little bird ---------------------------------------------------------------
499
+
500
+ function buildBird() {
501
+ const g = new THREE.Group();
502
+ const cream = lambert(0xf5efe0, { flatShading: true });
503
+ const featherMaterial = lambert(0xd9cdb4, { flatShading: true });
504
+
505
+ const body = ico(0.13, 1, cream);
506
+ body.scale.set(1, 0.95, 1.2);
507
+ body.position.y = 0.12;
508
+ g.add(body);
509
+
510
+ const breast = ico(0.085, 1, lambert(0xe8893c, { flatShading: true }));
511
+ breast.position.set(0, 0.07, 0.07);
512
+ g.add(breast);
513
+
514
+ const beak = new THREE.Mesh(new THREE.ConeGeometry(0.025, 0.07, 6), lambert(0xd9a13c));
515
+ beak.rotation.x = Math.PI / 2;
516
+ beak.position.set(0, 0.15, 0.17);
517
+ g.add(beak);
518
+
519
+ for (const side of [-1, 1]) {
520
+ const eye = ico(0.018, 0, lambert(0x1d232f));
521
+ eye.position.set(side * 0.055, 0.17, 0.115);
522
+ g.add(eye);
523
+
524
+ const wing = ico(0.07, 1, featherMaterial);
525
+ wing.scale.set(0.5, 0.7, 1.3);
526
+ wing.position.set(side * 0.115, 0.12, -0.01);
527
+ g.add(wing);
528
+
529
+ const leg = cyl(0.012, 0.012, 0.05, 5, lambert(0xb3823c));
530
+ leg.position.set(side * 0.04, 0.025, 0.02);
531
+ g.add(leg);
532
+ }
533
+
534
+ const tail = new THREE.Group();
535
+ const feathers = box(0.07, 0.02, 0.14, featherMaterial);
536
+ feathers.position.z = -0.07;
537
+ tail.add(feathers);
538
+ tail.position.set(0, 0.12, -0.13);
539
+ tail.rotation.x = -0.35;
540
+ g.add(tail);
541
+ g.userData.tail = tail;
542
+
543
+ return shadows(g);
544
+ }
545
+
546
  // --- lamp post ---------------------------------------------------------------
547
 
548
  function buildLamp() {
 
979
  bench.position.set(1.7, 0.3, 4.4);
980
  scene.add(bench);
981
 
982
+ // someone left their tape collection on the bench…
983
+ const cassettes = buildCassettePile();
984
+ cassettes.position.set(-1.0, 1.05, -0.05);
985
+ cassettes.rotation.y = -0.2;
986
+ bench.add(cassettes);
987
+
988
+ // …with their walkman and headphones beside it
989
+ const walkman = buildWalkman();
990
+ walkman.position.set(0.15, 1.05, 0.02);
991
+ walkman.rotation.y = 0.35;
992
+ bench.add(walkman);
993
+
994
+ const headphones = buildHeadphones();
995
+ headphones.position.set(0.85, 1.05, -0.04);
996
+ headphones.rotation.y = -0.6;
997
+ bench.add(headphones);
998
+
999
+ const cableCurve = new THREE.CatmullRomCurve3([
1000
+ new THREE.Vector3(0.38, 1.1, 0.1),
1001
+ new THREE.Vector3(0.52, 1.07, 0.22),
1002
+ new THREE.Vector3(0.685, 1.08, 0.07),
1003
+ ]);
1004
+ const cable = new THREE.Mesh(
1005
+ new THREE.TubeGeometry(cableCurve, 16, 0.01, 5),
1006
+ lambert(0x2b3138)
1007
+ );
1008
+ cable.castShadow = true;
1009
+ bench.add(cable);
1010
+
1011
+ // a little bird perches on the backrest
1012
+ const bird = buildBird();
1013
+ bird.position.set(1.15, 2.11, -0.62);
1014
+ bird.rotation.y = -0.5;
1015
+ bird.userData.baseY = bird.position.y;
1016
+ bench.add(bird);
1017
+
1018
  const sign = buildSign();
1019
  sign.position.set(-8.3, 0.3, 4.2);
1020
  sign.rotation.y = 0.12;
 
1097
  const clouds = buildClouds();
1098
  for (const cloud of clouds) scene.add(cloud);
1099
 
1100
+ return { vending, bench, sign, lamp, bigTree, house, clouds, bird };
1101
  }