Skill

isometric-rolling-cube

This skill should be used when the user is working on rolling cube animation, edge-pivoting, isometric cube movement, transform-origin animation, Object3D reparenting for rotation, grid-based cube wandering, window boundary enforcement, direction selection logic, cursor following, or weighted random direction bias — even if they don't explicitly mention "rolling cube." Covers both CSS transform-origin and Three.js Object3D pivot approaches, GSAP animation chaining, cumulative rotation tracking, grid snapping, boundary checking, and cursor-biased direction selection.

From lll-animation
Install
1
Run in your terminal
$
npx claudepluginhub simon-tanna/lll-animation-plugin --plugin lll-animation
Tool Access

This skill uses the workspace's default tool permissions.

Supporting Assets
View in Repository
references/boundary-algorithm.md
references/direction-data.md
Skill Content

Isometric Rolling Cube

A cube "rolls" by tipping 90 degrees over one of its bottom edges, translating one grid cell in that direction. This is fundamentally different from rotating around the centre — the pivot point must be at the contact edge, not the object's origin.

This skill covers the technique for both CSS (Phase 1) and Three.js (Phase 2) implementations, with GSAP driving the animation in both cases.

Invocation Behaviour

When invoked without a specific topic, prompt for context:

Which aspect of rolling animation are you working on?

TopicCovers
Rolling / pivotCSS transform-origin or Three.js Object3D pivot technique
DirectionsThe 4 isometric roll directions and data tables
BoundariesWindow boundary enforcement and direction filtering
CursorCursor-following bias and weighted random selection
SnappingGrid snapping and floating-point drift prevention
ReparentingThree.js attach() vs add() and the pivot cycle

Or describe what you need help with.

When a specific topic is identified, jump directly to the relevant section. For CSS cube construction (faces, preserve-3d, isometric projection), delegate to the phase1-css-cube skill. For GSAP API specifics, delegate to the gsap-expert skill.

The 4 Isometric Roll Directions

In isometric view, the cube moves diagonally on screen. Each screen direction maps to a world-axis movement:

Screen DirectionWorld Direction (XZ)Grid Delta
Top-Right+X(1, 0)
Bottom-Right+Z(0, 1)
Bottom-Left-X(-1, 0)
Top-Left-Z(0, -1)

For a unit cube (side length S), each direction has a corresponding rotation axis, pivot offset, and translation vector.

Direction Data (Three.js — Y-up, XZ ground plane)

For a cube centred at (gx, S/2, gz):

DirectionTranslationPivot Offset from CentreRotation AxisAngle
+X(S, 0, 0)(+S/2, -S/2, 0)(0, 0, -1)PI/2
-X(-S, 0, 0)(-S/2, -S/2, 0)(0, 0, +1)PI/2
+Z(0, 0, S)(0, -S/2, +S/2)(+1, 0, 0)PI/2
-Z(0, 0, -S)(0, -S/2, -S/2)(-1, 0, 0)PI/2

The rotation axis can be derived: cross(direction, downVector) where downVector = (0, -1, 0).

The pivot offset is always: half a cube-width toward the roll direction on the ground axis, plus half a cube-width downward (to the bottom edge).

Direction Data (CSS — pre-isometric flat plane)

Full transform-origin values and translation vectors for all 4 directions: see references/direction-data.md.

Key principle: transform-origin must be a 3D value (three components) targeting the specific bottom edge for each direction. A 2D value places the pivot at Z=0, which is wrong for the Bottom-Right and Top-Left directions (those need a non-zero Z component).


Phase 1: CSS Rolling Technique

How It Works

Each roll is a simple 90-degree rotation animated with GSAP. The key is the reset step after each roll — this keeps each animation starting from a clean state.

For each roll:

  1. Animate a 90-degree rotation in the roll direction (using GSAP)
  2. On complete (the reset): move the element to its new grid position, zero out the rotation, and the cube is ready for the next roll

The Reset Step (Core Technique)

After a roll completes, the cube has rotated 90 degrees. Absorb this into the base state:

  • Move the element to its new grid position (CSS left/top or translate)
  • Zero out the roll rotation
  • This prevents cumulative transform stacking

Without this reset, each successive roll compounds on the previous rotation and the animation breaks after 2-3 rolls. The reset is what makes the entire approach work — each roll starts from a clean, predictable state.

Cumulative State

Because each roll resets to a clean state, no complex rotation tracking is needed. The logical grid position (gridX, gridY) is the only state to maintain between rolls — update it in the onComplete callback after each reset.


Phase 2: Three.js Pivot Technique

The Object3D Reparenting Cycle

Three.js has no transform-origin equivalent. Instead, create a temporary pivot Object3D at the rolling edge and make the cube a child of it. Rotating the pivot then rotates the cube around that edge.

The full cycle for one roll:

Step 1 — Create pivot at the rolling edge: Position a new Object3D at the cube's bottom edge in the roll direction.

Step 2 — Attach cube to pivot: Use pivot.attach(cube) — this preserves the cube's world-space position while making it a child of the pivot. The cube does not visually move.

Step 3 — Animate pivot rotation: Use GSAP to rotate the pivot 90 degrees around the appropriate axis. The cube, as a child, orbits around the pivot point.

Step 4 — Detach cube from pivot: Use scene.attach(cube) — this moves the cube back to the scene's children while preserving its current world position and rotation.

Step 5 — Clean up: Remove the pivot from the scene. Snap the cube's position to exact grid coordinates.

Why attach() Not add()

This is the single most important API distinction in the entire implementation.

  • Object3D.add(child) — reparents the child but keeps its local transform unchanged. Since the local transform is now interpreted relative to the new parent, the child's world position changes. The cube would visually jump.
  • Object3D.attach(child) — reparents the child and recalculates its local transform so that its world position stays the same. Internally it computes: newLocal = inverse(newParent.worldMatrix) * oldParent.worldMatrix * oldLocal.

Use attach() for both directions: cube-to-pivot and pivot-back-to-scene.

Rotation Reset After Each Roll

After each roll's reparenting cleanup, reset the cube's rotation to clean values. Since each roll is exactly 90 degrees, all rotation components should be exact multiples of PI/2.

Recommended approach: Reset Euler angles to clean values after each roll in the onComplete callback. Round each rotation component to the nearest multiple of PI/2. This keeps every roll starting from a predictable state — no cumulative drift, no gimbal lock issues.

Optional advanced approach: Use quaternions (Quaternion.premultiply()) to track cumulative rotation if you need precise face-orientation tracking (e.g., knowing which face is on top). This is not needed for the workshop's rolling behaviour.


GSAP Animation Patterns

Per-Roll Timeline with onComplete Chaining

Each roll is an independent animation. Since directions are chosen randomly, you cannot pre-build a long timeline. Instead, chain rolls dynamically:

  1. Pick a random valid direction
  2. Create a short timeline (or single tween) for one roll
  3. In the onComplete callback, clean up the pivot/transform state, then call the roll function again (possibly after a delay)

Use gsap.delayedCall(pauseDuration, rollNext) to insert pauses between rolls — this is cleaner than adding empty delays to timelines.

Targeting Three.js Objects with GSAP

Target the sub-object, not the mesh itself:

  • Position: gsap.to(mesh.position, { x: ..., z: ... })
  • Rotation: gsap.to(pivot.rotation, { z: angle })

Do NOT use dot-path syntax on the mesh: gsap.to(mesh, { "rotation.x": angle }) — this does not work with Three.js objects.

Easing

  • "power2.inOut" gives a natural roll-and-settle feel
  • "power1.out" (GSAP default) also works well for a lighter landing
  • Experiment encouraged — the "right" ease is subjective

The Landing Jump (yoyo)

Add a natural bounce at each landing with yoyo: true:

// Runs alongside the roll animation — plays up then automatically reverses
gsap.to(positionContainer, {
  y: -jumpHeight,
  duration: rollDuration / 2,
  yoyo: true,
  repeat: 1,
  ease: "power2.out"
})

yoyo: true with repeat: 1 plays the tween forward then automatically reverses — the position container rises then falls back, producing the characteristic up-then-down bounce at each landing.

Render Loop

GSAP tweens values but does not trigger Three.js renders. Three options:

  1. Continuous rAF looprequestAnimationFrame that calls renderer.render() every frame. Simple, always works. (spec preferred — the boilerplate provides this)
  2. GSAP onUpdate — call renderer.render() inside each tween's onUpdate. Renders only during animation but misses static scene updates.
  3. gsap.tickergsap.ticker.add(() => renderer.render(scene, camera)). Synchronises rendering with GSAP's update cycle. Good middle ground.

Grid Snapping

Floating-point arithmetic causes gradual drift after many rolls. After each roll completes (in the onComplete callback), snap the cube's position to exact grid coordinates:

  • Three.js: gsap.set(mesh.position, { x: Math.round(mesh.position.x), z: Math.round(mesh.position.z) }) — using gsap.set() rather than direct assignment keeps GSAP's internal state consistent
  • CSS: Set the element's position to gridX * cellSize, gridY * cellSize and clear any transform residue

Track position as integer grid coordinates (gridX, gridY) alongside the rendered position. Update the grid coordinates after each roll, and snap the rendered position to match.


Boundary Enforcement

Before each roll, filter valid directions against grid bounds. See references/boundary-algorithm.md for the full direction filtering algorithm, CSS viewport-pixel bounds approach, and Three.js grid-coordinate bounds approach.


Cursor Following

Weighted direction bias toward cursor position. See references/boundary-algorithm.md for the full bias algorithm, CSS screen-space approach, and Three.js Vector3.unproject() conversion for world-space cursor coordinates.


Common Pitfalls

PitfallSymptomFix
Pivot at centre, not edgeCube spins in placePosition pivot at bottom edge using offset formula
Using add() instead of attach()Cube jumps when reparentedAlways use attach() for both directions
Euler angle accumulationUnexpected rotation after 3-4 rollsReset rotation to clean multiples of PI/2 after each roll
No grid snapCube drifts off grid over timegsap.set() to round position in onComplete
Pre-built timeline for random directionsCan't randomise; timeline is fixedUse onComplete chaining, one roll per timeline
GSAP directional shortcuts (_cw, _short)Ignored or breaks on Three.js objectsThese only work on DOM targets — use raw angle values
No render loopScene doesn't update visuallyAdd gsap.ticker.add(() => renderer.render(...))
Modifying boilerplate camera/sceneBreaks grid alignmentThe starter repo's camera and grid are pre-configured — don't touch them

See Also

  • phase1-css-cube — CSS cube construction (Desandro 6-div approach, face transforms, preserve-3d, isometric projection)
  • workshop-guide — Task progression, acceptance criteria, phase transitions
  • gsap-expert — Full GSAP API reference, timeline patterns, ScrollTrigger
  • threejs-fundamentals — Scene setup, Object3D hierarchy, coordinate system, math utilities
Similar Skills
ui-ux-pro-max

UI/UX design intelligence for web and mobile. Includes 50+ styles, 161 color palettes, 57 font pairings, 161 product types, 99 UX guidelines, and 25 chart types across 10 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui, and HTML/CSS). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, and check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, and mobile app. Elements: button, modal, navbar, sidebar, card, table, form, and chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, and flat design. Topics: color systems, accessibility, animation, layout, typography, font pairing, spacing, interaction states, shadow, and gradient. Integrations: shadcn/ui MCP for component search and examples.

49.4k
Stats
Stars0
Forks0
Last CommitFeb 27, 2026