Claude Code Plugins

Community-maintained marketplace

Feedback

Design and implement lightweight Three.js (r150+) ES-module scenes—hero sections, interactive product viewers, particle backdrops, GLTF showcases, or quick prototypes—whenever prompts mention 'three.js/threejs scene', '3D web background', 'orbit controls', or 'WebGL demo'.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name threejs-builder
description Design and implement lightweight Three.js (r150+) ES-module scenes—hero sections, interactive product viewers, particle backdrops, GLTF showcases, or quick prototypes—whenever prompts mention 'three.js/threejs scene', '3D web background', 'orbit controls', or 'WebGL demo'.
metadata [object Object]

Three.js Builder

A Codex-ready framework for shipping elegant, performant Three.js web experiences with modern ES modules.

Reference Files

Read the appropriate reference when working on specific topics:

Topic File Use When
GLTF Models references/gltf-loading-guide.md Loading, caching, cloning 3D models, SkeletonUtils
Reference Frames references/reference-frame-contract.md Calibration, anchoring, axis correctness, debugging
Game Development references/game-patterns.md State machines, animation switching, parallax, pooling
Advanced Topics references/advanced-topics.md Post-processing, shaders, physics, instancing
Calibration Helpers scripts/README.md GLTF calibration helper installation and usage

Philosophy: Start from Scene Intent

Three.js work lives inside a scene graph: parents transform children, and everything in scene is rendered.

Before writing code, ask:

  • What is the core visual element or metaphor (hero object, particle field, data glyph)?
  • How should visitors manipulate or perceive it (static hero, orbit control, camera-relative movement)?
  • What level of hardware/performance do we target (mobile hero snippet vs. desktop interactive)?
  • Which animation best communicates the story (continuous rotation, bobbing, interaction-driven)?

Core principles:

  1. Scene graph first – model hierarchy and transforms before materials.
  2. Intent-driven primitives – boxes, spheres, torus, planes cover 80% of hero cases; reach for GLTF only when fidelity demands it.
  3. Animation as transformation – treat animation as evolving position/rotation/scale with renderer.setAnimationLoop.
  4. Performance through restraint – reuse geometries/materials, clamp device pixel ratio, keep draw calls low.

Foundational Principle: Lock a “Reference Frame Contract” First

Most Three.js bugs are not “rendering bugs” — they’re reference-frame bugs: wrong origin, wrong up-axis assumption, wrong forward direction, wrong “ground = y=0” semantics, wrong color space, or wrong loading environment.

Treat your scene like a contract you must prove once, then never violate:

  • Axes: which way is forward for gameplay, and how do models map to it?
  • Anchors: what does “on the ground” mean for each asset class?
  • Units: what is 1 unit (meter-ish? pixel-ish?) and how do imported assets fit?
  • Color: are textures and output color space correct?

If you don’t lock this contract early, you will chase symptoms: inverted movement, floating/sinking models, “wrongly loaded” textures, and end-state hangs caused by tangled state transitions.

The Calibration Pass (do this before building gameplay)

Do a 60-second calibration scene and lock constants (axes/anchors/scale/color) before shipping gameplay systems.

Deep guide + troubleshooting map: references/reference-frame-contract.md

Mesh Forward Verification (Do Not Guess)

Don’t “eyeball rotate until it looks right” and don’t assume “GLTF faces -Z”.

In the calibration scene, prove each imported model’s forward direction and lock it as a constant (global MODEL_YAW_OFFSET or per-asset yawOffset).

If you want a reusable helper module instead of hand-rolling arrows/box helpers each time:

  • Install (repo-scoped): run python3 .codex/skills/threejs-builder/scripts/install-gltf-calibration-helpers.py --out ./gltf-calibration-helpers.mjs
  • Install (from within the skill folder): run python3 scripts/install-gltf-calibration-helpers.py --out ./gltf-calibration-helpers.mjs
  • Use: import and call attachGltfCalibrationHelpers(...) (see scripts/README.md)

Minimal check:

// In Three.js, an Object3D’s "forward" is its local -Z axis.
const localForward = new THREE.Vector3(0, 0, -1);

// Attach a visible arrow to the model so you can *see* its forward.
const forwardArrow = new THREE.ArrowHelper(localForward, new THREE.Vector3(0, 1.2, 0), 1.2, 0xff00ff);
modelRoot.add(forwardArrow);

// Also log the current world-forward for debugging (post-normalization and post-rotation).
const worldForward = new THREE.Vector3();
modelRoot.getWorldDirection(worldForward);
console.log('model world forward (-Z):', worldForward.toArray());

Then decide:

  • If the mesh runs backward, set yawOffset = Math.PI.
  • If it faces the camera (common symptom), your yaw convention is wrong—flip the offset and re-check.

Coordinate System & Camera Awareness (Critical)

Three.js is right-handed: +X right, +Y up, +Z toward the camera. Many GLTF assets are authored facing -Z, but verify per pack (don’t assume). Camera-relative controls must project the camera forward vector onto the XZ plane, normalize, then derive the right vector via a right-handed cross product to avoid inverted WASD when the camera orbits.

const forward = new THREE.Vector3();
camera.getWorldDirection(forward);
forward.y = 0;
forward.normalize();
// Right-handed basis (DON'T swap arguments): right = forward × up
const right = new THREE.Vector3().crossVectors(forward, new THREE.Vector3(0, 1, 0));

Practical rule:

  • If A/D is inverted, your cross product order is wrong (or you need to flip forward with forward.negate() depending on your convention). Fix the basis, not the key mapping.

Origins & “Ground” Anchors (Stop Guessing)

Anchoring is an asset-contract decision, not a “tweak until it looks right” decision.

Rule of thumb:

  • Characters/props: minY = 0
  • Ground tiles: often maxY = 0 (walkable surface)

Deep guide + example code: references/reference-frame-contract.md


Build Flow

1. Frame the Experience

  • Define the vibe (portfolio, gamey, data viz) and jot a mini mood board (colors, light style, animation verbs).
  • Choose interaction tier: static render, auto-rotation, OrbitControls, or custom camera/gameplay loop.
  • Sketch layout: do you need a ground plane, backdrop gradient, floating particles, or spotlight object?

2. Establish Runtime Foundations

  • Use modern ES modules—no global THREE. Import from three package or CDN versions (>=0.150).
  • Minimal HTML template:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Three.js Scene</title>
  <style>body{margin:0;overflow:hidden;background:#010103;}canvas{display:block;}</style>
</head>
<body>
  <script type="module">
    import * as THREE from 'https://unpkg.com/three@0.160.0/build/three.module.js';

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 2000);
    camera.position.set(0, 1.5, 4.5);

    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(innerWidth, innerHeight);
    renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
    renderer.outputColorSpace = THREE.SRGBColorSpace;
    document.body.appendChild(renderer.domElement);

    window.addEventListener('resize', () => {
      camera.aspect = innerWidth / innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(innerWidth, innerHeight);
    });
  </script>
</body>
</html>
  • Plug OrbitControls from three/addons/controls/OrbitControls.js when interaction is needed, enable damping, and call controls.update() each frame.

2.5 GLTF Loading Guardrails (When You Use Models)

  • Run via a local server (not file://) so relative resources resolve reliably.
  • For animated/skinned GLTF instancing, use SkeletonUtils.clone() (plain .clone() breaks skeleton links).
  • Always log animation clip names after load and select by exact string (avoid “best guess” name matching unless it’s a deliberate fallback).
  • If your asset pack uses an atlas, avoid “multiply tinting” materials (it can turn textured models into weird flat colors). Prefer leaving material.color at white and using emissive/lighting for emphasis.

3. Compose the Scene

  • Geometries: prefer built-ins (Box, Sphere, Torus, Plane, Cone, Icosahedron). For particle clouds use BufferGeometry + Float32Array positions.
  • Materials: pick MeshStandardMaterial as default (tweak roughness/metalness), MeshPhysicalMaterial for glass/liquids, MeshBasicMaterial for unlit accents, PointsMaterial for particles.
  • Lighting: start with Ambient (0.3–0.5) + Directional key + colored fill; enable shadows only when necessary and set renderer.shadowMap.enabled = true + map sizes of 1024–2048.
  • Color palettes: define constants (e.g., const COLORS = { primary: 0xff6600, accent: 0x0ff0ff }) and keep backgrounds dark to let emissive tones pop.

4. Animate & Interact

  • Implement animation loops with renderer.setAnimationLoop((time) => { ... renderer.render(scene, camera); });.
  • Common patterns:
    • Continuous rotation with mesh.rotation.y = time * 0.001.
    • Bobbing via mesh.position.y = Math.sin(time * 0.002) * 0.4.
    • Mouse-reactive tilt: normalize pointer coords and lerp rotations for smoothness.
    • Camera-relative character motion for interactive demos (see snippet above for direction vectors).
  • Store state outside the loop (e.g., const inputState = { up: false, ... }).

4.5 State Machines & “Time Ran Out” Bugs

If your app “hangs” at time-out, it’s almost always because multiple systems are competing to transition state.

Guardrails:

  • Use a single state.mode and a single transition function.
  • Treat timeLeft <= 0 as terminal and clamp it once: timeLeft = Math.max(0, timeLeft - dt).
  • Add a one-way latch like hasEnded so the end transition can only happen once.

5. Polish & Performance

  • Cap renderer.setPixelRatio(Math.min(devicePixelRatio, 2)) to keep 4K laptops smooth.
  • Reuse geometries/materials, avoid instantiating inside the animation loop.
  • Use helpers (GridHelper, AxesHelper) for debugging, but remove them for production scenes.
  • Separate setup helpers (e.g., createLights()/createMeshes()) to keep files navigable.

Anti-Patterns to Avoid

Legacy global builds: Using <script src="three.js"></script> and accessing THREE globals breaks tree-shaking and OrbitControls imports. Use ES modules.

Camera-blind controls: Mapping WASD directly to world axes feels wrong once the camera rotates. Always derive movement vectors from the camera orientation.

Floating/sinking fixes by “random y offsets”: If you’re tweaking position.y in 5 places, your anchoring strategy is missing. Why bad: you’ll never converge across ground tiles vs characters vs props. Better: normalize each asset to a declared anchor (minY or maxY) at load time and only place the wrapper.

Asset guessing: Assuming GLTF forward direction, scale, or animation names without a calibration pass. Better: build a 60-second calibration scene and lock constants (forward offsets, anchors, heights).

Off-center canvases from transform math: Mixing translate() + scale() with the wrong transform-origin makes the render area drift. Better: center via layout (flex/grid) and apply scale() only, with transform-origin: center.

Geometry churn inside loops: Creating geometries/materials each frame tanks GC and FPS. Instantiate once and mutate transforms only.

Overly dense primitives: Jumping to SphereGeometry(1, 128, 128) without purpose wastes perf; start with defaults and scale segments only when silhouetted edges demand it.

Unbounded pixel ratio: Forgetting to clamp devicePixelRatio makes retina users suffer. Always clamp to ≤2 unless photorealism is mandatory.


Variation Guidance

  • Purpose-specific styling:
    • Portfolio hero → slow easing, glossy lighting, analogous palettes.
    • Game prototype → punchy colors, contrasty lights, camera-relative controls, particles.
    • Data viz → clean planes, grid helpers, consistent units, annotations.
    • Background loop → subtle gradients, low-frequency motion, ambient glow.
  • Mix geometry/material choices: cycle through spheres, rounded boxes, low-poly icosahedrons, tubes. Experiment with flatShading, wireframe, emissive blooms.
  • Camera moods: alternate between dolly-in hero (z≈4), aerial tilt (camera.set(8, 11, -6)), or isometric (45° yaw). Pair with matching movement logic.
  • Animation verbs: rotate, pulse, orbit, flock, noise-driven drift. Avoid always rotating cubes at 0.001 speed.

Remember

Three.js scenes succeed when the mental model (scene graph + intent) guides the build. Lead with philosophy, respect the coordinate system, keep loops lean, and vary geometry, lighting, and animation so every experience feels purpose-built. Let these patterns unlock creativity—they are rails, not cages.

For specific topics, see the Reference Files table at the top of this document.