From lll-animation
This skill should be used when setting up 3D scenes, creating cameras, configuring renderers, managing object hierarchies, or working with transforms. Covers scene setup, cameras, renderer, Object3D hierarchy, and coordinate systems.
npx claudepluginhub simon-tanna/lll-animation-plugin --plugin lll-animationThis skill uses the workspace's default tool permissions.
```javascript
Provides Three.js fundamentals: scene, camera, renderer setup, Object3D hierarchy, coordinate systems, and transforms. Use for initializing 3D web scenes and managing objects.
Sets up Three.js scenes, cameras, renderers, object hierarchies, and transforms. Covers resize handling, lighting, Perspective/Orthographic cameras, fog, and backgrounds for basic 3D web rendering.
Provides Three.js API references, best practices, and code examples for scene setup, geometry, materials, lighting, textures, animation, loaders, shaders, postprocessing, and interaction in 3D web apps.
Share bugs, ideas, or general feedback.
import * as THREE from "three";
// Create scene, camera, renderer
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000,
);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
// Add a mesh
const geometry = new THREE.BoxGeometry(1, 1, 1);
// MeshBasicMaterial needs no lighting — matches workshop Task 2.1
const material = new THREE.MeshBasicMaterial({ color: 0x4fd1c5 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// Note: for an OrthographicCamera (used in the workshop), position the camera
// at an isometric angle rather than straight-on. The boilerplate provides this.
camera.position.z = 5; // example for PerspectiveCamera only
// Animation loop — Three.js does NOT render automatically
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
// Handle resize (PerspectiveCamera — OrthographicCamera uses left/right/top/bottom instead)
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight; // PerspectiveCamera only
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
Container for all 3D objects, lights, and cameras.
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
PerspectiveCamera — Most common, simulates human eye.
// PerspectiveCamera(fov, aspect, near, far)
const camera = new THREE.PerspectiveCamera(
75, // Field of view (degrees)
window.innerWidth / window.innerHeight, // Aspect ratio
0.1, // Near clipping plane
1000, // Far clipping plane
);
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);
camera.updateProjectionMatrix(); // Call after changing fov, aspect, near, far
OrthographicCamera — No perspective distortion, good for 2D/isometric. Objects stay the same size regardless of distance from camera.
// OrthographicCamera(left, right, top, bottom, near, far)
// These define a rectangular viewing box (frustum) in world units
const aspect = window.innerWidth / window.innerHeight;
const frustumSize = 10;
const camera = new THREE.OrthographicCamera(
(frustumSize * aspect) / -2, // left
(frustumSize * aspect) / 2, // right
frustumSize / 2, // top
frustumSize / -2, // bottom
0.1, // near
1000, // far
);
Isometric setup: Position the camera along a diagonal and use lookAt to point at the origin. The zoom property adjusts how many world units are visible. In an isometric view, world +X appears as a diagonal going up-right on screen, and +Z goes down-right.
camera.position.set(10, 10, 10);
camera.lookAt(0, 0, 0);
camera.zoom = 50;
camera.updateProjectionMatrix();
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector("#canvas"), // Optional existing canvas
antialias: true, // Smooth edges
alpha: true, // Transparent background
});
renderer.setSize(width, height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setClearColor(0x000000, 1);
// Shadows
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.render(scene, camera);
Base class for all 3D objects. Mesh, Group, Light, Camera all extend Object3D.
Every Object3D has a local transform (position, rotation, scale) relative to its parent. Three.js composes these down the hierarchy when rendering:
worldMatrix = parent.worldMatrix × child.localMatrix
This means:
.position is in parent-local coordinates, not world coordinatesconst obj = new THREE.Object3D();
// Transform (all in parent-local space)
obj.position.set(x, y, z);
obj.rotation.set(x, y, z); // Euler angles (radians)
obj.quaternion.set(x, y, z, w); // Quaternion rotation
obj.scale.set(x, y, z);
// World-space queries
obj.getWorldPosition(targetVector);
obj.getWorldQuaternion(targetQuaternion);
obj.getWorldDirection(targetVector);
// add() — keeps child's LOCAL transform unchanged
// If the new parent is at a different position, the child visually jumps
parent.add(child);
// attach() — recomputes child's local transform to preserve WORLD position
// The child appears to stay in place
parent.attach(child);
parent.remove(child);
parent.children; // Array of children
child.parent; // Reference to parent
When to use which:
add() — placing a new object into a parent (it has no meaningful world position yet)attach() — reparenting an existing object that should stay visually in place (e.g., the pivot pattern below)attach() depends on up-to-date world matrices. If you just created or repositioned the parent in the same frame, call parent.updateMatrixWorld() before attach().
// Example: child at world (5,0,0), new parent at world (3,0,0)
parent.add(child); // child.position stays (5,0,0) local → world (8,0,0) — JUMPS
parent.attach(child); // child.position becomes (2,0,0) local → world (5,0,0) — STAYS
obj.visible = false;
// Layers (for selective rendering/raycasting)
obj.layers.set(1);
obj.layers.enable(2);
// Traverse hierarchy
obj.traverse((child) => {
if (child.isMesh) child.material.color.set(0xff0000);
});
// Force-update world matrix (needed before attach() if parent just moved)
obj.updateMatrixWorld(true);
Empty container for organizing objects.
const group = new THREE.Group();
group.add(mesh1);
group.add(mesh2);
scene.add(group);
// Transform entire group
group.position.x = 5;
group.rotation.y = Math.PI / 4;
Combines geometry and material.
const mesh = new THREE.Mesh(geometry, material);
// Per-face materials (array of 6 for BoxGeometry — one per face)
const mesh = new THREE.Mesh(geometry, [mat0, mat1, mat2, mat3, mat4, mat5]);
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.frustumCulled = true; // Default: skip rendering if outside camera view
mesh.renderOrder = 10; // Higher = rendered later
Three.js uses a right-handed, Y-up coordinate system:
The ground plane is XZ (not XY).
CSS contrast: In CSS, +Y points down the screen and there is no Z depth by default. In Three.js, +Y points up and the ground plane is XZ.
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper); // Red=X, Green=Y, Blue=Z
Most commonly needed operations. For the full API (all Vector3, Matrix4, Quaternion, Euler, Color methods), see references/math-utilities.md.
// Vector3
const v = new THREE.Vector3(x, y, z);
v.set(x, y, z);
v.copy(other);
v.clone();
v.add(v2);
v.sub(v2);
v.multiplyScalar(2);
v.normalize();
v.length();
v.distanceTo(v2);
v.lerp(target, alpha);
// Angle conversions
THREE.MathUtils.degToRad(degrees);
THREE.MathUtils.radToDeg(radians);
// Useful utilities
THREE.MathUtils.clamp(value, min, max);
THREE.MathUtils.lerp(start, end, alpha);
Three.js does not render automatically — you must call renderer.render(scene, camera) whenever the scene should update. The standard pattern uses requestAnimationFrame:
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
When using GSAP with Three.js, GSAP tweens property values but does not trigger renders. Options:
requestAnimationFrame loop (simplest)renderer.render(scene, camera) in GSAP's onUpdate callbackgsap.ticker.add(() => renderer.render(scene, camera))Three.js has no equivalent to CSS transform-origin. To rotate an object around an arbitrary point, use an empty Object3D as a temporary pivot:
attach() the object to the pivot (preserves world position)attach() the object back to the sceneconst pivot = new THREE.Object3D();
pivot.position.set(edgeX, edgeY, edgeZ);
scene.add(pivot);
pivot.attach(cube); // Cube stays in place visually
// Animate the pivot rotation (e.g., with GSAP)
// gsap.to(pivot.rotation, { z: -Math.PI / 2, onUpdate: render })
// After animation completes:
scene.attach(cube); // Reparent cube back to scene
scene.remove(pivot);
For workshop-specific direction mappings and the full rolling cycle, see the isometric-rolling-cube skill.
Three.js stores rotation as Euler angles (obj.rotation.x/y/z in radians) with a default XYZ order. When combining 90° rotations across multiple axes (common in rolling animations), floating-point imprecision accumulates — after 3-4 rolls the cube visibly drifts.
Fix: After each animation completes, snap to clean multiples of π/2:
function snapRotation(obj) {
const HALF_PI = Math.PI / 2;
obj.rotation.x = Math.round(obj.rotation.x / HALF_PI) * HALF_PI;
obj.rotation.y = Math.round(obj.rotation.y / HALF_PI) * HALF_PI;
obj.rotation.z = Math.round(obj.rotation.z / HALF_PI) * HALF_PI;
}
function dispose() {
mesh.geometry.dispose();
if (Array.isArray(mesh.material)) {
mesh.material.forEach((m) => m.dispose());
} else {
mesh.material.dispose();
}
texture.dispose();
scene.remove(mesh);
renderer.dispose();
}
const clock = new THREE.Clock();
function animate() {
const delta = clock.getDelta(); // Seconds since last frame
const elapsed = clock.getElapsedTime(); // Total seconds
mesh.rotation.y += delta * 0.5; // Framerate-independent speed
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
// Perspective camera
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Orthographic camera
function onResize() {
const aspect = window.innerWidth / window.innerHeight;
camera.left = (frustumSize * aspect) / -2;
camera.right = (frustumSize * aspect) / 2;
camera.top = frustumSize / 2;
camera.bottom = frustumSize / -2;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
window.addEventListener("resize", onResize);
references/math-utilities.md — Full API for Vector3, Matrix4, Quaternion, Euler, Color, and MathUtils. Load when participants need specific math operations beyond the essentials above.threejs-materials — Material types and propertiesthreejs-animation — Keyframe animation, skeletal animation, morph targetsisometric-rolling-cube — Rolling animation technique using the pivot pattern