main.ts

examples/spark/raycasting/main.ts · raw · open the example · all examples · site home

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// Spark's raycasting: five swinging robot heads (one upload, five objects);
// a click tints the one hit for a moment. THREE.Raycaster calls each
// SplatModel's raycast(), a CPU ellipsoid test over the loaded splats
// (js/raycast.js), and the tint is the model's recolor (js/objects.js).
import * as THREE from "three";
import { asset } from "../js/assets.js";
import { SplatSet } from "../js/objects.js";
import { examplePage, windowRenderer } from "../js/page.js";
import { loadSplatFile } from "../js/files.js";

const page = examplePage("raycasting");
page.legend("Click to select");

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 0.1, 10);
camera.position.set(0, -0.25, -1.5);
camera.lookAt(0, -0.15, 0);

// createRenderer, sized to the window and added to the page; it follows
// window resizes, the camera's aspect too (js/page.ts)
const renderer = await windowRenderer(camera);

const splats = new SplatSet({ renderer, scene });
const head = await loadSplatFile(renderer, asset("robot-head.spz"), { onProgress: page.progress("robot-head") });
const robots = Array.from({ length: 5 }, (_, i) => {
  const model = splats.of(head);
  model.rotation.x = Math.PI;
  model.scale.setScalar(0.2);
  model.position.z = i;
  return { model, speed: 600 + i * 10, clickedTimer: 0 };
});
page.ready(renderer);

const WHITE = new THREE.Color(1, 1, 1);
const PINK = new THREE.Color(1, 0.5, 0.5);
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();

renderer.domElement.addEventListener("click", (event) => {
  pointer.set((event.clientX / innerWidth) * 2 - 1, -(event.clientY / innerHeight) * 2 + 1);
  raycaster.setFromCamera(pointer, camera);
  const hit = raycaster.intersectObjects(robots.map((robot) => robot.model))[0];
  const robot = robots.find((robot) => robot.model === hit?.object);
  if (robot && !robot.clickedTimer) {
    robot.model.recolor = PINK;
    robot.clickedTimer = 40;
  }
});

renderer.setAnimationLoop((time) => {
  for (const robot of robots) {
    const angle = Math.PI * 1.5 + Math.sin(time / robot.speed);
    robot.model.position.x = Math.cos(angle) * 0.6;
    robot.model.position.y = Math.sin(angle) * 0.6;
    if (robot.clickedTimer > 0 && --robot.clickedTimer === 0) {
      robot.model.recolor = WHITE;
    }
  }
  renderer.render(scene, camera);
});