Skills/Three.js Skills

Three.js Skills

A curated collection of Three.js skill files providing agents with foundational 3D knowledge, accurate API references, constructor signatures, working code examples, performance optimization tips, and animation mixer/clip workflows.

CLAUDE CODECURSORWINDSURF
Loading stats...

Installation

Install directly into your agent CLI.

$git clone https://github.com/pinkforest/threejs-playground.git
Usage examplePrompt your agent

“Build an interactive 3D experience with Three.js: set up camera, lighting, materials, animation mixer, and render loop.”

Three.js Skills Subgroups

Three.js Fundamentalsjump ↓

Set up WebGL scenes, perspective and orthographic cameras, standard renderers, Object3D hierarchies, geometries, materials, and lighting pipelines.

Three.js Animation Systemsjump ↓

Implement character and skeletal animation with AnimationMixer, AnimationClip, AnimationAction playback, morph targets, and cross-fading.

SKILL.md

--- name: threejs-skills description: Complete Three.js toolkit for AI agents covering 3D scene setup, cameras, geometries, materials, lighting, and animation mixer/clip workflows. license: MIT metadata: author: pinkforest / cloudai-x repo: cloudai-x/threejs-skills version: "1.0" ---

Three.js Skills

A curated, comprehensive collection of Three.js skill files providing foundational 3D knowledge, accurate API references, constructor signatures, working code examples, performance optimization tips, and animation systems.

Subgroups Included

1. Three.js Fundamentals — Scene setup, cameras, renderer, Object3D hierarchy, coordinate systems, geometries, materials, and lighting 2. Three.js Animation Systems — AnimationMixer, AnimationClip, AnimationAction, keyframes, transitions, morph targets, and performance


Subgroup 1: Three.js Fundamentals

--- name: threejs-fundamentals description: Three.js scene setup, cameras, renderer, Object3D hierarchy, coordinate systems. Use when setting up 3D scenes, creating cameras, configuring renderers, managing object hierarchies, or working with transforms. ---

Three.js Fundamentals

Quick Start

javascript
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); const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); const cube = new THREE.Mesh(geometry, material); scene.add(cube);

// Add light scene.add(new THREE.AmbientLight(0xffffff, 0.5)); const dirLight = new THREE.DirectionalLight(0xffffff, 1); dirLight.position.set(5, 5, 5); scene.add(dirLight);

camera.position.z = 5;

// Animation loop function animate() { requestAnimationFrame(animate); cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render(scene, camera); } animate();

// Handle resize window.addEventListener("resize", () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); ```

Core Classes

Scene

Container for all 3D objects, lights, and cameras.

javascript
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000); // Solid color
scene.background = texture; // Skybox texture
scene.background = cubeTexture; // Cubemap
scene.environment = envMap; // Environment map for PBR
scene.fog = new THREE.Fog(0xffffff, 1, 100); // Linear fog
scene.fog = new THREE.FogExp2(0xffffff, 0.02); // Exponential fog

Cameras

PerspectiveCamera - Most common, simulates human eye.

javascript
// 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.

javascript
// OrthographicCamera(left, right, top, bottom, near, far)
const aspect = window.innerWidth / window.innerHeight;
const frustumSize = 10;
const camera = new THREE.OrthographicCamera(
  (frustumSize * aspect) / -2,
  (frustumSize * aspect) / 2,
  frustumSize / 2,
  frustumSize / -2,
  0.1,
  1000,
);

ArrayCamera - Multiple viewports with sub-cameras.

javascript
const cameras = [];
for (let i = 0; i < 4; i++) {
  const subcamera = new THREE.PerspectiveCamera(40, 1, 0.1, 100);
  subcamera.viewport = new THREE.Vector4(
    Math.floor(i % 2) * 0.5,
    Math.floor(i / 2) * 0.5,
    0.5,
    0.5,
  );
  cameras.push(subcamera);
}
const arrayCamera = new THREE.ArrayCamera(cameras);

CubeCamera - Renders environment maps for reflections.

javascript
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256);
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);
scene.add(cubeCamera);

// Use for reflections material.envMap = cubeRenderTarget.texture;

// Update each frame (expensive!) cubeCamera.position.copy(reflectiveMesh.position); cubeCamera.update(renderer, scene); ```

WebGLRenderer

javascript
const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector("#canvas"), // Optional existing canvas
  antialias: true, // Smooth edges
  alpha: true, // Transparent background
  powerPreference: "high-performance", // GPU hint
  preserveDrawingBuffer: true, // For screenshots
});

renderer.setSize(width, height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

// Tone mapping renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.0;

// Color space (Three.js r152+) renderer.outputColorSpace = THREE.SRGBColorSpace;

// Shadows renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap;

// Clear color renderer.setClearColor(0x000000, 1);

// Render renderer.render(scene, camera); ```

Object3D

Base class for all 3D objects. Mesh, Group, Light, Camera all extend Object3D.

javascript
const obj = new THREE.Object3D();

// Transform 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);

// Local vs World transforms obj.getWorldPosition(targetVector); obj.getWorldQuaternion(targetQuaternion); obj.getWorldDirection(targetVector);

// Hierarchy obj.add(child); obj.remove(child); obj.parent; obj.children;

// Visibility obj.visible = false;

// Layers (for selective rendering/raycasting) obj.layers.set(1); obj.layers.enable(2); obj.layers.disable(0);

// Traverse hierarchy obj.traverse((child) => { if (child.isMesh) child.material.color.set(0xff0000); });

// Matrix updates obj.matrixAutoUpdate = true; // Default: auto-update matrices obj.updateMatrix(); // Manual matrix update obj.updateMatrixWorld(true); // Update world matrix recursively ```

Group

Empty container for organizing objects.

javascript
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; ```

Mesh

Combines geometry and material.

javascript
const mesh = new THREE.Mesh(geometry, material);

// Multiple materials (one per geometry group) const mesh = new THREE.Mesh(geometry, [material1, material2]);

// Useful properties mesh.geometry; mesh.material; mesh.castShadow = true; mesh.receiveShadow = true;

// Frustum culling mesh.frustumCulled = true; // Default: skip if outside camera view

// Render order mesh.renderOrder = 10; // Higher = rendered later ```

Coordinate System

Three.js uses a right-handed coordinate system:

  • +X points right
  • +Y points up
  • +Z points toward viewer (out of screen)
javascript
// Axes helper
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper); // Red=X, Green=Y, Blue=Z

Math Utilities

Vector3

javascript
const v = new THREE.Vector3(x, y, z);
v.set(x, y, z);
v.copy(otherVector);
v.clone();

// Operations (modify in place) v.add(v2); v.sub(v2); v.multiply(v2); v.multiplyScalar(2); v.divideScalar(2); v.normalize(); v.negate(); v.clamp(min, max); v.lerp(target, alpha);

// Calculations (return new value) v.length(); v.lengthSq(); // Faster than length() v.distanceTo(v2); v.dot(v2); v.cross(v2); // Modifies v v.angleTo(v2);

// Transform v.applyMatrix4(matrix); v.applyQuaternion(q); v.project(camera); // World to NDC v.unproject(camera); // NDC to world ```

Matrix4

javascript
const m = new THREE.Matrix4();
m.identity();
m.copy(other);
m.clone();

// Build transforms m.makeTranslation(x, y, z); m.makeRotationX(theta); m.makeRotationY(theta); m.makeRotationZ(theta); m.makeRotationFromQuaternion(q); m.makeScale(x, y, z);

// Compose/decompose m.compose(position, quaternion, scale); m.decompose(position, quaternion, scale);

// Operations m.multiply(m2); // m = m * m2 m.premultiply(m2); // m = m2 * m m.invert(); m.transpose();

// Camera matrices m.makePerspective(left, right, top, bottom, near, far); m.makeOrthographic(left, right, top, bottom, near, far); m.lookAt(eye, target, up); ```

Quaternion

javascript
const q = new THREE.Quaternion();
q.setFromEuler(euler);
q.setFromAxisAngle(axis, angle);
q.setFromRotationMatrix(matrix);

q.multiply(q2); q.slerp(target, t); // Spherical interpolation q.normalize(); q.invert(); ```

Euler

javascript
const euler = new THREE.Euler(x, y, z, "XYZ"); // Order matters!
euler.setFromQuaternion(q);
euler.setFromRotationMatrix(m);

// Rotation orders: 'XYZ', 'YXZ', 'ZXY', 'XZY', 'YZX', 'ZYX' ```

Color

javascript
const color = new THREE.Color(0xff0000);
const color = new THREE.Color("red");
const color = new THREE.Color("rgb(255, 0, 0)");
const color = new THREE.Color("#ff0000");

color.setHex(0x00ff00); color.setRGB(r, g, b); // 0-1 range color.setHSL(h, s, l); // 0-1 range

color.lerp(otherColor, alpha); color.multiply(otherColor); color.multiplyScalar(2); ```

MathUtils

javascript
THREE.MathUtils.clamp(value, min, max);
THREE.MathUtils.lerp(start, end, alpha);
THREE.MathUtils.mapLinear(value, inMin, inMax, outMin, outMax);
THREE.MathUtils.degToRad(degrees);
THREE.MathUtils.radToDeg(radians);
THREE.MathUtils.randFloat(min, max);
THREE.MathUtils.randInt(min, max);
THREE.MathUtils.smoothstep(x, min, max);
THREE.MathUtils.smootherstep(x, min, max);

Common Patterns

Proper Cleanup

javascript
function dispose() {
  // Dispose geometries
  mesh.geometry.dispose();

// Dispose materials if (Array.isArray(mesh.material)) { mesh.material.forEach((m) => m.dispose()); } else { mesh.material.dispose(); }

// Dispose textures texture.dispose();

// Remove from scene scene.remove(mesh);

// Dispose renderer renderer.dispose(); } ```

Clock for Animation

javascript
const clock = new THREE.Clock();

function animate() { const delta = clock.getDelta(); // Time since last frame (seconds) const elapsed = clock.getElapsedTime(); // Total time (seconds)

mesh.rotation.y += delta * 0.5; // Consistent speed regardless of framerate

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

Responsive Canvas

javascript
function onWindowResize() {
  const width = window.innerWidth;
  const height = window.innerHeight;

camera.aspect = width / height; camera.updateProjectionMatrix();

renderer.setSize(width, height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); } window.addEventListener("resize", onWindowResize); ```

Loading Manager

javascript
const manager = new THREE.LoadingManager();

manager.onStart = (url, loaded, total) => console.log("Started loading"); manager.onLoad = () => console.log("All loaded"); manager.onProgress = (url, loaded, total) => console.log(${loaded}/${total}); manager.onError = (url) => console.error(Error loading ${url});

const textureLoader = new THREE.TextureLoader(manager); const gltfLoader = new GLTFLoader(manager); ```

Performance Tips

  1. Limit draw calls: Merge geometries, use instancing, atlas textures
  2. Frustum culling: Enabled by default, ensure bounding boxes are correct
  3. LOD (Level of Detail): Use THREE.LOD for distance-based mesh switching
  4. Object pooling: Reuse objects instead of creating/destroying
  5. Avoid `getWorldPosition` in loops: Cache results
javascript
// Merge static geometries
import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
const merged = mergeGeometries([geo1, geo2, geo3]);

// LOD const lod = new THREE.LOD(); lod.addLevel(highDetailMesh, 0); lod.addLevel(medDetailMesh, 50); lod.addLevel(lowDetailMesh, 100); scene.add(lod); ```

See Also

  • threejs-geometry - Geometry creation and manipulation
  • threejs-materials - Material types and properties
  • threejs-lighting - Light types and shadows

Subgroup 2: Three.js Animation Systems

--- name: threejs-animation description: Three.js animation - keyframe animation, skeletal animation, morph targets, animation mixing. Use when animating objects, playing GLTF animations, creating procedural motion, or blending animations. ---

Three.js Animation

Quick Start

javascript
import * as THREE from "three";

// Simple procedural animation const clock = new THREE.Clock();

function animate() { const delta = clock.getDelta(); const elapsed = clock.getElapsedTime();

mesh.rotation.y += delta; mesh.position.y = Math.sin(elapsed) * 0.5;

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

Animation System Overview

Three.js animation system has three main components:

  1. AnimationClip - Container for keyframe data
  2. AnimationMixer - Plays animations on a root object
  3. AnimationAction - Controls playback of a clip

AnimationClip

Stores keyframe animation data.

javascript
// Create animation clip
const times = [0, 1, 2]; // Keyframe times (seconds)
const values = [0, 1, 0]; // Values at each keyframe

const track = new THREE.NumberKeyframeTrack( ".position[y]", // Property path times, values, );

const clip = new THREE.AnimationClip("bounce", 2, [track]); ```

KeyframeTrack Types

javascript
// Number track (single value)
new THREE.NumberKeyframeTrack(".opacity", times, [1, 0]);
new THREE.NumberKeyframeTrack(".material.opacity", times, [1, 0]);

// Vector track (position, scale) new THREE.VectorKeyframeTrack(".position", times, [ 0, 0, 0, // t=0 1, 2, 0, // t=1 0, 0, 0, // t=2 ]);

// Quaternion track (rotation) const q1 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0)); const q2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0)); new THREE.QuaternionKeyframeTrack( ".quaternion", [0, 1], [q1.x, q1.y, q1.z, q1.w, q2.x, q2.y, q2.z, q2.w], );

// Color track new THREE.ColorKeyframeTrack(".material.color", times, [ 1, 0, 0, // red 0, 1, 0, // green 0, 0, 1, // blue ]);

// Boolean track new THREE.BooleanKeyframeTrack(".visible", [0, 0.5, 1], [true, false, true]);

// String track (for morph targets) new THREE.StringKeyframeTrack( ".morphTargetInfluences[smile]", [0, 1], ["0", "1"], ); ```

Interpolation Modes

javascript
const track = new THREE.VectorKeyframeTrack(".position", times, values);

// Interpolation track.setInterpolation(THREE.InterpolateLinear); // Default track.setInterpolation(THREE.InterpolateSmooth); // Cubic spline track.setInterpolation(THREE.InterpolateDiscrete); // Step function ```

AnimationMixer

Plays animations on an object and its descendants.

javascript
const mixer = new THREE.AnimationMixer(model);

// Create action from clip const action = mixer.clipAction(clip); action.play();

// Update in animation loop function animate() { const delta = clock.getDelta(); mixer.update(delta); // Required!

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

Mixer Events

javascript
mixer.addEventListener("finished", (e) => {
  console.log("Animation finished:", e.action.getClip().name);
});

mixer.addEventListener("loop", (e) => { console.log("Animation looped:", e.action.getClip().name); }); ```

AnimationAction

Controls playback of an animation clip.

javascript
const action = mixer.clipAction(clip);

// Playback control action.play(); action.stop(); action.reset(); action.halt(fadeOutDuration);

// Playback state action.isRunning(); action.isScheduled();

// Time control action.time = 0.5; // Current time action.timeScale = 1; // Playback speed (negative = reverse) action.paused = false;

// Weight (for blending) action.weight = 1; // 0-1, contribution to final pose action.setEffectiveWeight(1);

// Loop modes action.loop = THREE.LoopRepeat; // Default: loop forever action.loop = THREE.LoopOnce; // Play once and stop action.loop = THREE.LoopPingPong; // Alternate forward/backward action.repetitions = 3; // Number of loops (Infinity default)

// Clamping action.clampWhenFinished = true; // Hold last frame when done

// Blending action.blendMode = THREE.NormalAnimationBlendMode; action.blendMode = THREE.AdditiveAnimationBlendMode; ```

Fade In/Out

javascript
// Fade in
action.reset().fadeIn(0.5).play();

// Fade out action.fadeOut(0.5);

// Crossfade between animations const action1 = mixer.clipAction(clip1); const action2 = mixer.clipAction(clip2);

action1.play();

// Later, crossfade to action2 action1.crossFadeTo(action2, 0.5, true); action2.play(); ```

Loading GLTF Animations

Most common source of skeletal animations.

javascript
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

const loader = new GLTFLoader(); loader.load("model.glb", (gltf) => { const model = gltf.scene; scene.add(model);

// Create mixer const mixer = new THREE.AnimationMixer(model);

// Get all clips const clips = gltf.animations; console.log( "Available animations:", clips.map((c) => c.name), );

// Play first animation if (clips.length > 0) { const action = mixer.clipAction(clips[0]); action.play(); }

// Play specific animation by name const walkClip = THREE.AnimationClip.findByName(clips, "Walk"); if (walkClip) { mixer.clipAction(walkClip).play(); }

// Store mixer for update loop window.mixer = mixer; });

// Animation loop function animate() { const delta = clock.getDelta(); if (window.mixer) window.mixer.update(delta);

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

Skeletal Animation

Skeleton and Bones

javascript
// Access skeleton from skinned mesh
const skinnedMesh = model.getObjectByProperty("type", "SkinnedMesh");
const skeleton = skinnedMesh.skeleton;

// Access bones skeleton.bones.forEach((bone) => { console.log(bone.name, bone.position, bone.rotation); });

// Find specific bone by name const headBone = skeleton.bones.find((b) => b.name === "Head"); if (headBone) headBone.rotation.y = Math.PI / 4; // Turn head

// Skeleton helper const helper = new THREE.SkeletonHelper(model); scene.add(helper); ```

Programmatic Bone Animation

javascript
function animate() {
  const time = clock.getElapsedTime();

// Animate bone const headBone = skeleton.bones.find((b) => b.name === "Head"); if (headBone) { headBone.rotation.y = Math.sin(time) * 0.3; }

// Update mixer if also playing clips mixer.update(clock.getDelta()); } ```

Bone Attachments

javascript
// Attach object to bone
const weapon = new THREE.Mesh(weaponGeometry, weaponMaterial);
const handBone = skeleton.bones.find((b) => b.name === "RightHand");
if (handBone) handBone.add(weapon);

// Offset attachment weapon.position.set(0, 0, 0.5); weapon.rotation.set(0, Math.PI / 2, 0); ```

Morph Targets

Blend between different mesh shapes.

javascript
// Morph targets are stored in geometry
const geometry = mesh.geometry;
console.log("Morph attributes:", Object.keys(geometry.morphAttributes));

// Access morph target influences mesh.morphTargetInfluences; // Array of weights mesh.morphTargetDictionary; // Name -> index mapping

// Set morph target by index mesh.morphTargetInfluences[0] = 0.5;

// Set by name const smileIndex = mesh.morphTargetDictionary["smile"]; mesh.morphTargetInfluences[smileIndex] = 1; ```

Animating Morph Targets

javascript
// Procedural
function animate() {
  const t = clock.getElapsedTime();
  mesh.morphTargetInfluences[0] = (Math.sin(t) + 1) / 2;
}

// With keyframe animation const track = new THREE.NumberKeyframeTrack( ".morphTargetInfluences[smile]", [0, 0.5, 1], [0, 1, 0], ); const clip = new THREE.AnimationClip("smile", 1, [track]); mixer.clipAction(clip).play(); ```

Animation Blending

Mix multiple animations together.

javascript
// Setup actions
const idleAction = mixer.clipAction(idleClip);
const walkAction = mixer.clipAction(walkClip);
const runAction = mixer.clipAction(runClip);

// Play all with different weights idleAction.play(); walkAction.play(); runAction.play();

// Set initial weights idleAction.setEffectiveWeight(1); walkAction.setEffectiveWeight(0); runAction.setEffectiveWeight(0);

// Blend based on speed function updateAnimations(speed) { if (speed < 0.1) { idleAction.setEffectiveWeight(1); walkAction.setEffectiveWeight(0); runAction.setEffectiveWeight(0); } else if (speed < 5) { const t = speed / 5; idleAction.setEffectiveWeight(1 - t); walkAction.setEffectiveWeight(t); runAction.setEffectiveWeight(0); } else { const t = Math.min((speed - 5) / 5, 1); idleAction.setEffectiveWeight(0); walkAction.setEffectiveWeight(1 - t); runAction.setEffectiveWeight(t); } } ```

Additive Blending

javascript
// Base pose
const baseAction = mixer.clipAction(baseClip);
baseAction.play();

// Additive layer (e.g., breathing) const additiveAction = mixer.clipAction(additiveClip); additiveAction.blendMode = THREE.AdditiveAnimationBlendMode; additiveAction.play();

// Convert clip to additive THREE.AnimationUtils.makeClipAdditive(additiveClip); ```

Animation Utilities

javascript
import * as THREE from "three";

// Find clip by name const clip = THREE.AnimationClip.findByName(clips, "Walk");

// Create subclip const subclip = THREE.AnimationUtils.subclip(clip, "subclip", 0, 30, 30);

// Convert to additive THREE.AnimationUtils.makeClipAdditive(clip); THREE.AnimationUtils.makeClipAdditive(clip, 0, referenceClip);

// Clone clip const clone = clip.clone();

// Get clip duration clip.duration;

// Optimize clip (remove redundant keyframes) clip.optimize();

// Reset clip to first frame clip.resetDuration(); ```

Procedural Animation Patterns

Smooth Damping

javascript
// Smooth follow/lerp
const target = new THREE.Vector3();
const current = new THREE.Vector3();
const velocity = new THREE.Vector3();

function smoothDamp(current, target, velocity, smoothTime, deltaTime) { const omega = 2 / smoothTime; const x = omega * deltaTime; const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x); const change = current.clone().sub(target); const temp = velocity .clone() .add(change.clone().multiplyScalar(omega)) .multiplyScalar(deltaTime); velocity.sub(temp.clone().multiplyScalar(omega)).multiplyScalar(exp); return target.clone().add(change.add(temp).multiplyScalar(exp)); }

function animate() { current.copy(smoothDamp(current, target, velocity, 0.3, delta)); mesh.position.copy(current); } ```

Spring Physics

javascript
class Spring {
  constructor(stiffness = 100, damping = 10) {
    this.stiffness = stiffness;
    this.damping = damping;
    this.position = 0;
    this.velocity = 0;
    this.target = 0;
  }

update(dt) { const force = -this.stiffness * (this.position - this.target); const dampingForce = -this.damping * this.velocity; this.velocity += (force + dampingForce) * dt; this.position += this.velocity * dt; return this.position; } }

const spring = new Spring(100, 10); spring.target = 1;

function animate() { mesh.position.y = spring.update(delta); } ```

Oscillation

javascript
function animate() {
  const t = clock.getElapsedTime();

// Sine wave mesh.position.y = Math.sin(t * 2) * 0.5;

// Bouncing mesh.position.y = Math.abs(Math.sin(t * 3)) * 2;

// Circular motion mesh.position.x = Math.cos(t) * 2; mesh.position.z = Math.sin(t) * 2;

// Figure 8 mesh.position.x = Math.sin(t) * 2; mesh.position.z = Math.sin(t * 2) * 1; } ```

Performance Tips

  1. Share clips: Same AnimationClip can be used on multiple mixers
  2. Optimize clips: Call clip.optimize() to remove redundant keyframes
  3. Disable when off-screen: Stop mixer updates for invisible objects
  4. Use LOD for animations: Simpler rigs for distant characters
  5. Limit active mixers: Each mixer.update() has a cost
javascript
// Pause animation when not visible
mesh.onBeforeRender = () => {
  action.paused = false;
};

mesh.onAfterRender = () => { // Check if will be visible next frame if (!isInFrustum(mesh)) { action.paused = true; } };

// Cache clips const clipCache = new Map(); function getClip(name) { if (!clipCache.has(name)) { clipCache.set(name, loadClip(name)); } return clipCache.get(name); } ```

See Also

  • threejs-loaders - Loading animated GLTF models
  • threejs-fundamentals - Clock and animation loop
  • threejs-shaders - Vertex animation in shaders