Files
english/.opencode/skills/threejs/references/08-interaction.md
2026-04-12 01:06:31 +07:00

6.6 KiB

Interaction & Picking

Handle user input and object interaction.

Mouse/Touch Raycasting

Detect which object user clicked:

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const clickableObjects = []; // array of meshes

function onPointerMove(event) {
  // Normalize mouse coordinates (-1 to +1)
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

  // Update raycaster
  raycaster.setFromCamera(mouse, camera);

  // Find intersections
  const intersects = raycaster.intersectObjects(clickableObjects);

  if (intersects.length > 0) {
    // Hover effect
    intersects[0].object.material.emissive.setHex(0xff0000);
  }
}

function onClick(event) {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects(clickableObjects);

  if (intersects.length > 0) {
    const object = intersects[0].object;
    console.log('Clicked:', object.name);
    console.log('Point:', intersects[0].point);
  }
}

renderer.domElement.addEventListener('pointermove', onPointerMove);
renderer.domElement.addEventListener('click', onClick);

DragControls (Addon)

Drag objects with mouse:

import { DragControls } from 'three/addons/controls/DragControls.js';

const controls = new DragControls(objectsArray, camera, renderer.domElement);

// Events
controls.addEventListener('dragstart', (event) => {
  orbitControls.enabled = false; // disable camera controls during drag
  event.object.material.emissive.set(0xaaaaaa);
});

controls.addEventListener('drag', (event) => {
  console.log(event.object.position);
});

controls.addEventListener('dragend', (event) => {
  orbitControls.enabled = true;
  event.object.material.emissive.set(0x000000);
});

TransformControls (Addon)

Interactive 3D gizmo for translate/rotate/scale:

import { TransformControls } from 'three/addons/controls/TransformControls.js';

const transformControls = new TransformControls(camera, renderer.domElement);
scene.add(transformControls);

// Attach to object
transformControls.attach(mesh);

// Switch modes
transformControls.setMode('translate'); // or 'rotate', 'scale'

// Switch space
transformControls.setSpace('world'); // or 'local'

// Events
transformControls.addEventListener('change', () => {
  renderer.render(scene, camera);
});

transformControls.addEventListener('dragging-changed', (event) => {
  orbitControls.enabled = !event.value; // disable orbit during transform
});

// Keyboard shortcuts
window.addEventListener('keydown', (event) => {
  switch (event.key) {
    case 'g': transformControls.setMode('translate'); break;
    case 'r': transformControls.setMode('rotate'); break;
    case 's': transformControls.setMode('scale'); break;
    case 'x': transformControls.showX = !transformControls.showX; break;
    case 'Escape': transformControls.detach(); break;
  }
});

Selection Box (Addon)

Box selection for multiple objects:

import { SelectionBox } from 'three/addons/interactive/SelectionBox.js';
import { SelectionHelper } from 'three/addons/interactive/SelectionHelper.js';

const selectionBox = new SelectionBox(camera, scene);
const helper = new SelectionHelper(renderer, 'selectBox');

let isSelecting = false;

renderer.domElement.addEventListener('pointerdown', (event) => {
  isSelecting = true;
  selectionBox.startPoint.set(
    (event.clientX / window.innerWidth) * 2 - 1,
    -(event.clientY / window.innerHeight) * 2 + 1,
    0.5
  );
});

renderer.domElement.addEventListener('pointermove', (event) => {
  if (isSelecting) {
    selectionBox.endPoint.set(
      (event.clientX / window.innerWidth) * 2 - 1,
      -(event.clientY / window.innerHeight) * 2 + 1,
      0.5
    );
    const allSelected = selectionBox.select();
    console.log('Selected:', allSelected.length);
  }
});

renderer.domElement.addEventListener('pointerup', () => {
  isSelecting = false;
});

Keyboard Input

Handle keyboard controls:

const keysPressed = {};

window.addEventListener('keydown', (event) => {
  keysPressed[event.key] = true;
});

window.addEventListener('keyup', (event) => {
  keysPressed[event.key] = false;
});

// In animation loop
function animate() {
  const speed = 0.1;

  if (keysPressed['w']) object.position.z -= speed;
  if (keysPressed['s']) object.position.z += speed;
  if (keysPressed['a']) object.position.x -= speed;
  if (keysPressed['d']) object.position.x += speed;

  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

Pointer Lock (First Person)

Lock pointer for FPS controls:

import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';

const controls = new PointerLockControls(camera, renderer.domElement);

// Lock on click
renderer.domElement.addEventListener('click', () => {
  controls.lock();
});

controls.addEventListener('lock', () => {
  console.log('Pointer locked');
});

controls.addEventListener('unlock', () => {
  console.log('Pointer unlocked');
});

// Movement
const velocity = new THREE.Vector3();
const direction = new THREE.Vector3();

function animate() {
  if (controls.isLocked) {
    // Apply movement
    controls.moveForward(velocity.z);
    controls.moveRight(velocity.x);
  }
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

Object Highlighting

Visual feedback on hover/selection:

let hoveredObject = null;
const originalEmissive = new THREE.Color();

function onPointerMove(event) {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects(scene.children, true);

  // Reset previous
  if (hoveredObject) {
    hoveredObject.material.emissive.copy(originalEmissive);
    hoveredObject = null;
  }

  // Highlight new
  if (intersects.length > 0) {
    hoveredObject = intersects[0].object;
    originalEmissive.copy(hoveredObject.material.emissive);
    hoveredObject.material.emissive.setHex(0x555555);
  }

  renderer.domElement.style.cursor = hoveredObject ? 'pointer' : 'default';
}

Tooltips & UI Overlays

Show HTML tooltip at 3D position:

function updateTooltip(object3D, text) {
  const vector = object3D.position.clone();
  vector.project(camera);

  const x = (vector.x * 0.5 + 0.5) * window.innerWidth;
  const y = (-vector.y * 0.5 + 0.5) * window.innerHeight;

  tooltip.style.left = x + 'px';
  tooltip.style.top = y + 'px';
  tooltip.textContent = text;
}