3d Orbit Inspect Demo
Meng ToCommunityCreated by Meng To
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured approach to creating interactive 3D objects in Three.js. It implements a two-mode interaction system: a subtle, damped camera lean triggered by hover, and a full-screen inspection mode triggered by a click. The inspection mode lifts the object into a dedicated render layer, allowing for free-form rotation, zooming, and panning. The system is designed to handle state transitions between these modes without visual artifacts, ensuring the object returns to its original bench position upon closing.
What it does
- Enables hover-based camera leaning with damped, bounded movement.
- Provides a click-to-inspect interaction that lifts objects into a dedicated render layer.
- Supports object manipulation including rotation, zooming, and panning while in inspection mode.
- Includes accessibility features like keyboard navigation, focus management, and live region announcements.
- Handles state transitions to ensure smooth object return to its initial bench pose.
- Supports touch-based interaction with sway effects and pinch-to-zoom functionality.
When to use it
- When you need to display a single 3D object that requires both a passive hover state and an active inspection state.
- When building product showcases, portfolio pieces, or joinery demonstrations in Three.js.
- When you need to prevent interaction conflicts between hover-based orbits and click-based inspection.
- When you require a consistent way to return objects to their original scene coordinates after inspection.
Advantages
- Prevents visual jitter by freezing camera lean during click and inspection states.
- Maintains scene integrity by using layer-based rendering for the lifted object.
- Ensures smooth transitions between bench and hand-held states using perspective-true interpolation.
- Respects user preferences for reduced motion by disabling animations when requested.
- Provides robust focus management for keyboard users during the inspection lifecycle.
Usage examples
- “How do I implement a hover-to-lean effect for my 3D product model?”
- The skill provides a module that calculates camera lean based on raycast hits, using a damped, bounded approach to ensure the object does not turn edge-on.
- “How can I allow users to pick up and inspect a 3D object in my scene?”
- You can use the provided inspection logic to lift the object to a fixed camera-space position, enabling drag-to-turn and wheel-to-zoom controls.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/mengto/3d-orbit-inspect-demo/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip 3d-orbit-inspect-demo.zip -d .agents/skills
Documentation by Meng To
3D Orbit and Inspect
Three.js · WebGL. No OrbitControls, no custom shaders.
The mechanism: hover leans the camera a few degrees around the object with a gain that rises only while a raycast hits it, and click hands the object (not the camera) to a second render layer that lifts it to a fixed spot in front of the lens. The two modes share no state except one rule: the lean freezes whenever a click is possible or the piece is in the hand. Remove that rule and the target slides out from under the click.
Nearest skills: webgl-3d-object (a floating mesh, no inspect), 3d-virtual-tour (its "orbit inspection" is one mode of a walkthrough; the camera itself flies). Reach for this when there is one object and two gestures on it.
Reuse demo.html: the joint, the room, the lean, the lift and the return are all in one module. Copy updateLean, updateHand, openInspect, closeInspect, the pointer block and draw. Swap the meshes for your own object; nothing else names the joint except FROM, RR and Q_BENCH.
What is and is not in the source
Kibori (kibori.html, section "8b. Inspect — pick an item up") has click-to-lift with drag-to-turn, an idle wheel, a "Look closer" cue and a zoom-in cursor on hover, and close by click-away, Esc, scroll or its Close button. It has no hover orbit: the camera only leans a few degrees as scroll dollies it between stations. It also has no free zoom, because wheel is the page scroll and closes the piece. So:
Ported unchanged: the item is lifted, the camera is not flown; the layer-1 second pass; the
1-(1-k)^4.4lift ease; 1.0 s in, 0.60 s home, 0.28 s fast close; the way home straightens before it descends (sstep(0,.36)thensstep(.24,1)); drag 0.0085 / 0.0065 rad per px; spin dampingexp(-2.4 dt); idle turn 0.26 rad/s after 2.2 s; click = under 6 px and 600 ms; the band between the top bar and the caption; the cue pill. Not ported: Kibori's hand-written tone-mapping pass and blur of the room behind the piece; the demo usesACESFilmicand a flat dim.Built here in the Kibori manner: the hover lean, the wheel/pinch dolly, arrow-key and +/- control, focus return, the live region, the touch sway.
Rules, each with the failure it prevents
Freeze the lean on pointerdown and while inspecting. Otherwise the camera keeps easing during a 100 ms click and the raycast at pointerup misses a piece that moved 6 px. Raycast again at pointerup, on the same layer the piece is on.
Gain from a raycast, target from the pointer relative to the piece.
grises to 1 only while the ray hits (tau 0.30 s); the target isclamp((pointer - pieceCentre)/(0.18 W), -1, 1). Screen-centre pointer maps make the piece drift whenever the mouse is anywhere on the page.Bound and damp. Azimuth ±0.30 rad, elevation ±0.09 rad,
1 - exp(-4.5 dt). Unbounded lean turns the object edge-on; an undamped one jitters with a 1 px mouse.Hover is the last pointer, not a media query.
(hover:hover)lies on hybrid laptops and headless Chrome. ReadpointerTypeonpointermove; touch gets a slow sway (±0.6 of the bound at 0.55 rad/s) so the depth is still shown without hover.Click and drag are one pointerdown. Under 6 px and under 600 ms opens; anything else is a drag and never opens. In the hand the same 6 px test separates "turn" from "click away". Set
touch-action:noneon the canvas only while inspecting, so the page can still scroll when it is not.Lift by layer, not by clone or
.visible. The piece moves to layer 1, the room renders on layer 0, a fullscreen quad at opacity0.80 egoes between,clearDepth(), then layer 1. Lights needlayers.enable(1)or the lifted piece renders black. The scene needsbackground = nulland manualclear(); a scene background re-clears pass two.Perspective-true hand-off, no pop. Interpolate in camera space:
x/zandy/zlinearly, then multiply by the lerped depth. A world-space lerp slides in depth and the piece hardly moves sideways for the first 100 ms. The world "from" pose is stored, never re-read from the moving object, so a cancelled lift lands on the exact bench pose (verified equal to four decimals after Esc at 150 ms).Hold the camera in the hand; never chase it. The hand pose is defined in camera space; if the lean keeps easing while inspecting, the piece swims. Freeze it, resume on close.
Sit at the band's middle, sized by it.
fit = min(0.92 bandH, 0.88 W) / (2 R ppu)withppu = H / (2 D tan(fov/2)), D = 1.0 m. Measure the caption's settled top (top - 14 - 40) before it fades in; a piece sized for the whole viewport runs under the caption.Close is its own clock. On close,
e0 = r0 = insOut(k)andkrestarts from 1; a piece turned further takes up to 1.5x longer (span). Closing from the middle of the lift must not restart from zero.Zoom is eased and bounded. 0.70 to 1.45 of fit, wheel
exp(-dy 0.0012), ease1 - exp(-8 dt), pinch by finger distance ratio. Past 1.5 the piece overlaps the caption; below 0.7 it is smaller than it sat on the bench. Reset to 1 on close.Pitch is bounded and relaxes. 0.25 to 1.30 rad, home 0.72, pull home at
exp(-0.35 dt). A flat timber turned past ~1.3 is a hairline.Keyboard and focus. A real button opens (
Inspect the joint); Enter and Space work. On open, focus the Close button and set the captioninert = false; Tab is trapped to it; arrows turn 0.14 rad,+-zoom 15%, Esc closes. Return focus to the opener after the page copy is visible again: the copy isvisibility:hiddenwhile inspecting andfocus()on a hidden element silently does nothing.A live region, not aria-live on the dialog. Announce "Inspecting ..." on open and "Back on the bench." on close through a separate
role=statusnode, cleared then set 30 ms later so a repeat is announced.
Numbers
| thing | value |
|---|---|
| lens, rest camera | FOV 34, elevation 0.40 rad, 1.50 m (portrait: 0.92 / aspect) |
| hover lean | az ±0.30, el ±0.09, damping 4.5/s, gain tau 0.30 s |
| lift | 1.0 s, 1 - (1-k)^4.4, arc sin(pi e) * 0.06 m, hand at D = 1.0 m |
| home | 0.60 s (0.28 s fast), rotate sstep(0,.36), descend sstep(.24,1) |
| dim behind | opacity 0.80 e, same colour as the room |
| lamp on the piece | warm point, 3.0 e, up 0.5 / left 0.6 / toward eye 0.35 m, layer 1 only |
| turn | 0.0085 / 0.0065 rad/px, damp 2.4/s, idle 0.26 rad/s after 2.2 s |
| budget | dt clamp 1/30, DPR cap 2, shadow map static (2048), render only when something moved |
Room
Dark room #0a0806, FogExp2 0.42 (materials that must not go murky set fog:false), key 0xfff2df 2.0 from upper left with the only shadow, fill 0xaec7df 0.76, rim 0xffdeb4 0.72, hemisphere 0.19, a low ember point 0xffb46a. Environment: a PMREM of three emissive cards in a black box at environmentIntensity 0.55. Ember #dbad64 is the only accent (cue ring, kicker, focus).
Reduced motion, hidden, cost
prefers-reduced-motion: no lean (the piece rests at the three-quarter bench pose), the lift is instant (rate = 1e3), no idle spin, no cue slide. Drag, keys and zoom still work: they are direct manipulation.visibilitychangecancels the loop; resume resetslastso the first frame does not integrate the pause.Cost, measured on an Apple GPU in headless Chrome (Metal): about 0.3 ms of CPU per rendered frame, 3 draw calls, about 250 triangles; 8.6 ms per synchronous frame in the two-pass lifted state at 1440x900 and 14.2 ms at 2880x1800 (DPR 2). Idle at rest renders zero frames. The lever is DPR; the shadow map is static and free, and triangle count does not matter.
Expensive gotchas
The lifted piece is black. Lights are layer-filtered by the camera;
light.layers.enable(1).The room disappears when the piece appears.
scene.backgroundre-clears in the secondrender; usesetClearColorandautoClear = false.A shader compile hitch on the first click. The lit-piece pass has a different light set (the lamp). Draw both states once at load.
Shadow of a piece that has left.
shadowMap.autoUpdate = false, thenneedsUpdatewhen the piece changes layer, or a ghost shadow stays on the bench.Cue and cursor stay after a click. Clear both on open;
zoom-incursor only on hit,grab/grabbingin the hand.Focus lost on close. See rule 13.
Object3D.cloneserialisesuserData. If you clone instead of switching layers, emptyuserDatafirst (render targets and polygons in there stall the clone).
Verify
Load at 1440x900 and 390x844; hover at three pointer positions (lean left, right, none); press and release on the piece (opens), press-drag-release across it (does not open); drag, wheel, pinch in the hand; click-away, Esc and Close each return the piece to the exact bench pose, including when cancelled 150 ms into the lift; Tab reaches the button, Enter opens, focus lands on Close and returns; reduced motion shows the rest pose and an instant lift; console is clean.
Extracted from KIBORI 木彫, a dark WebGL workshop page where every bench piece can be picked up and turned in the hand (section 8b). The hover lean and dolly are new, built to the same easing and HUD.
Files
- ATTRIBUTION.mdAdded by AutoGineer856 B
- LICENSELicence file added by AutoGineer1.0 KiB
- PROMPT.md4.3 KiB
- SKILL.md9.7 KiB
- agents/openai.yaml320 B
- demo.html36.6 KiB
- preview-full.jpg144.1 KiB
- preview.jpg189.3 KiB
PROMPT.md
3D Orbit & Inspect — Demo Prompts
Minimal prompt
Use $3d-orbit-inspect-demo to make this 3D object lean around a few degrees when I hover it, and lift into a full inspect view when I click: drag to turn, Esc to put it back.
Recreate the demo
Use $3d-orbit-inspect-demo to recreate 3D Orbit & Inspect as a single
standalone HTML file. Treat demo.html as the visual, motion, accessibility and
performance reference. Load three.js 0.169.0 through an import map from unpkg.
Experience
One full-viewport dark workshop (
#0a0806), a hinoki-and-sugi wedged through-tenon joint lying on a dark plank, warm key light from the upper left, one low ember accent (#dbad64). Instrument Serif and Inter.Left column: a small ember kicker naming the stack (Three.js, WebGL, MeshPhysicalMaterial, no OrbitControls, no shaders), the title "Orbit & Inspect", one paragraph, two hint labels and an "Inspect the joint" button. The joint sits right of the words on desktop, below them on a phone.
The joint is real geometry, in millimetres: a beam with a rectangular mortise cut through its depth, a post stepped down to a tenon that passes through it, a slot in the tenon and a tapered wedge in the slot. 0.4 mm clearances, 1.2 mm bevels, the post's shoulder exactly on the beam face, the wedge flat on its slot. Everything rests on the plank (no floating, no intersecting).
Hover
Raycast against the joint's meshes. While hit, a gain eases to 1 (tau 0.30 s); the camera orbits the joint by an amount taken from where the pointer is relative to the joint, capped at 0.30 rad round and 0.09 rad up and down, damped at 4.5/s. Off the joint the gain returns to 0 and the camera returns.
A "Look closer" pill with an ember ring sits beside the joint, never over it; the cursor is
zoom-in.Touch devices have no hover: a slow sway at 0.6 of the bounds shows the depth.
Reduced motion: no lean, the joint rests at its three-quarter pose.
Click and the inspect view
Press and release under 6 px and 600 ms on the joint opens it. A press that drags does not. The lean freezes for the length of a press.
The joint moves to layer 1. Pass one draws the room, then a fullscreen dim at 0.80 opacity, then pass two draws the joint alone, lit by the room's lights plus a warm lamp from upper left. It travels to the middle of the free band between the top bar and the caption, 1.0 m from the eye, sized to the band, on a perspective-true path (interpolate x/z and y/z, then the depth), 1.0 s with
1 - (1-k)^4.4, lifting on asinarc of 0.06 m and tilting toward you to 0.72 rad.The page copy fades out. A caption arrives: "Wedged through-tenon 込栓", "Two timbers and a wedge — no glue, no steel", and a hint row with Close.
In the hand: drag turns it (0.0085 rad/px yaw, 0.0065 pitch, pitch 0.25 to 1.30), it coasts with
exp(-2.4 dt), after 2.2 s idle it turns on at 0.26 rad/s. Wheel or pinch moves it closer, 0.70 to 1.45 of fit, eased. Arrow keys turn,+/-zoom.Click away, Esc or Close puts it back. It straightens first, then descends to the exact pose it left (0.60 s; cancelling mid-lift lands on the same pose). Focus returns to the opener after the page copy is visible.
A real button opens it from the keyboard; a
role=statusregion announces "Inspecting..." and "Back on the bench."
Performance and rules
DPR cap 2,
dtclamp 1/30, pause when hidden, render only when something moved, static shadow map (update it only when the joint changes layer), warm both light states at load so the first click does not compile. Screenshots at 1440x900 and 390x844, console clean.
Remix prompt
Use $3d-orbit-inspect-demo to build a different object with the same two gestures. Change the subject: a lacquered tea bowl on a slate slab, a chef's knife on a board, a rotary dial, a pocket watch. Change the palette and the composition (centred object, text above, a cool room and a copper accent). Keep the mechanism and the budgets: bounded damped hover lean with a raycast-driven gain, the lean frozen across a click, 6 px / 600 ms click test, layer-1 lift with a dim between passes, 1.0 s in, 0.60 s home, focus return, live region, reduced-motion still, DPR cap 2. If the subject is rotationally symmetric, give the lean something to show: a handle, a spout, a logo, a chip.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Meng To
- Publisher
- Meng To (Community)
- Source
- MengTo/Skills · agent-skills/3d/3d-orbit-inspect-demo · commit 5ea1661
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Meng To or Meng To.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download 3d-orbit-inspect-demo.zip