Ruview/examples/three.js
rUv 27a6edba8b
feat(examples/three.js): cinematic skinned realtime pose demo + folder reorg (#584)
* feat(examples/three.js): cinematic skinned realtime pose demo + ESP32 CSI bridge

Five-stage example progression exploring three.js helpers (ADR-097 surface) as
a viewer for live RuView sensor data:

1. helpers-demo.html              — clean ADR-097 helper reference (GridHelper,
                                    PolarGridHelper, BoxHelper, AxesHelper),
                                    file://-safe, no backend
2. helpers-cinematic.html         — same scene + UnrealBloomPass + pseudo-CSI
                                    sonar pings + tomography sweep + procedural
                                    cyber floor + ambient drift particles
3. helpers-skinned.html           — replaces sphere skeleton with Mixamo X Bot
                                    via GLTFLoader from threejs.org CDN, plays
                                    bundled animations with additive blending
4. helpers-skinned-fbx.html       — same but loads a local Mixamo FBX (needs
                                    serve-demo.py — file:// can't fetch local
                                    siblings). Drop X Bot.fbx alongside.
5. helpers-skinned-realtime.html  — webcam → MediaPipe Pose Heavy →
                                    poseWorldLandmarks → direct quaternion
                                    retargeting onto the Mixamo skeleton.
                                    Real ESP32-S3 CSI streamed over WebSocket
                                    from ruvultra (Tailscale, port 8766).

Supporting:
  - serve-demo.py             threaded HTTP server with no-cache headers
                               (fixes net::ERR_EMPTY_RESPONSE on the FBX path)
  - ruvultra-csi-bridge.py    ESP32 RuView firmware tick → WebSocket bridge,
                               runs as systemd-run unit on ruvultra

Bugs found + fixed along the way (all documented in code comments):
  - FBX exports yield TWO parallel Bone trees with identical names; only the
    SkinnedMesh.skeleton.bones one drives visible deformation. model.traverse
    finds orphans.
  - Mixamo FBX nests a zero-length wrapper bone above the real bone, same name.
    bone.children[0].getWorldPosition == bone.getWorldPosition → restDir is
    (0,0,0) → setFromUnitVectors collapses to identity. Walk past same-named
    same-position wrappers when computing tail.
  - AnimationMixer.update() with a "stopped" action still mutates bones unless
    enabled=false is set.

Retargeting layer in helpers-skinned-realtime.html:
  - 12 bones direct quaternion retarget (arms × 2, legs × 2, spine × 3, neck)
  - Hips root rotation from shoulder/hip line basis (torso twist + lean)
  - Neck aims at ear-midpoint (kp 7+8), not nose (kp 0), to remove the
    forward bias of the protruding-nose anchor
  - One Euro Filter per landmark per axis (Casiez 2012) — adaptive low-pass
  - Visibility-weighted per-bone slerp gain — occluded limbs relax to rest
  - URL toggles: ?mirror= ?yflip= ?zflip= ?cnn=0/1/2 ?csi=ws://...

Live CSI integration:
  - Bridge parses adaptive_ctrl tick lines (motion/presence/rssi/yield)
  - Browser fans single ESP32 reading across 4 UI nodes with phase-shifted
    wobble (0.88–1.00 × sin(t·0.55 + offsetᵢ))
  - EMA α=0.06 (~3 sec time constant), HUD update throttled 3 Hz

Co-Authored-By: claude-flow <ruv@ruv.net>

* refactor(examples/three.js): organize into demos/screenshots/server/assets + add README

Flatten the 13-file flat layout into purposeful subfolders so the demo
collection has a clean top-level entry point (README.md) and the file roles
are obvious from a directory listing.

Layout:
  demos/         01..05 — numbered for the progression (helpers → cinematic →
                          skinned → skinned-fbx → skinned-realtime)
  screenshots/   one PNG per demo, matching the demo's filename prefix
  server/        serve-demo.py + ruvultra-csi-bridge.py
  assets/        X Bot.fbx (gitignored, used by demos 04 and 05)

Touched files (beyond the renames):
- 04-skinned-fbx.html, 05-skinned-realtime.html: MODEL_URL now resolves
  '../assets/X%20Bot.fbx' instead of './X%20Bot.fbx'
- server/serve-demo.py: chdir() walks 3 levels up to repo root (was 2), and
  the URL banner now lists all 5 demos
- .gitignore: comment refresh — points at assets/ and screenshots/
- 05-skinned-realtime.html also picks up in-flight fps-tune work from this
  branch (Holistic script, SMOOTH_K URL param, slerp gain scaling) since
  those edits and the rename hit the same file

Verified end-to-end:
- python examples/three.js/server/serve-demo.py
- all 5 demos return 200, X Bot.fbx returns 200 from new asset/ path
- demos 04 + 05 render the X Bot mesh; 0 JS errors via browser eval
- screenshots reproduced match the originals

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-17 17:01:02 -04:00
..
demos feat(examples/three.js): cinematic skinned realtime pose demo + folder reorg (#584) 2026-05-17 17:01:02 -04:00
screenshots feat(examples/three.js): cinematic skinned realtime pose demo + folder reorg (#584) 2026-05-17 17:01:02 -04:00
server feat(examples/three.js): cinematic skinned realtime pose demo + folder reorg (#584) 2026-05-17 17:01:02 -04:00
.gitignore feat(examples/three.js): cinematic skinned realtime pose demo + folder reorg (#584) 2026-05-17 17:01:02 -04:00
README.md feat(examples/three.js): cinematic skinned realtime pose demo + folder reorg (#584) 2026-05-17 17:01:02 -04:00

three.js demos

Five progressively richer browser demos of the ADR-097 sensing-helpers scene, ending with a live MediaPipe-Pose → Mixamo X Bot retargeting pipeline driven by a real ESP32 CSI feed.

Run them

python examples/three.js/server/serve-demo.py
# then open one of the URLs the script prints

server/serve-demo.py is a tiny ThreadingHTTPServer with aggressive no-cache headers — the stdlib http.server is single-threaded and times out on the parallel script + FBX fetches the demos make.

Demos

# File What it shows
01 demos/01-helpers.html Plain ADR-097 helpers in the point-cloud viewer
02 demos/02-cinematic.html Cinematic camera + pseudo-CSI visualization on top of #01
03 demos/03-skinned.html GLTF skinned mesh + additive animation blending
04 demos/04-skinned-fbx.html Mixamo X Bot loaded from FBX in the ADR-097 scene
05 demos/05-skinned-realtime.html Webcam → MediaPipe Pose Heavy → Mixamo IK retarget, live ESP32 CSI overlay
Screenshot
01 02
03 04
05

Layout

examples/three.js/
├── README.md
├── .gitignore
├── demos/                       # 5 self-contained HTML demos
│   ├── 01-helpers.html
│   ├── 02-cinematic.html
│   ├── 03-skinned.html
│   ├── 04-skinned-fbx.html
│   └── 05-skinned-realtime.html
├── screenshots/                 # one PNG per demo
│   └── 0N-*.png
├── server/
│   ├── serve-demo.py            # local HTTP server with no-cache headers
│   └── ruvultra-csi-bridge.py   # ESP32 CSI WebSocket bridge (ruvultra:8766)
└── assets/
    └── X Bot.fbx                # gitignored — get your own from mixamo.com
                                 #   (FBX Binary, T-Pose, Without Skin)
                                 # used by demos 04 and 05

Mixamo X Bot

Demos 04 and 05 expect assets/X Bot.fbx. It's gitignored (size + license boundary). Download yours from mixamo.com: pick the "X Bot" character, export as FBX Binary, T-Pose, Without Skin, and drop it into assets/.

Live ESP32 CSI overlay (demo 05 only)

server/ruvultra-csi-bridge.py is the systemd-deployable bridge that runs on the ruvultra host (over Tailscale). It listens for ESP32-S3 CSI on UDP and re-broadcasts it as WebSocket frames at ws://ruvultra:8766/csi. Demo 05 auto-connects; if the socket is down, it falls back to the bundled idle clip plus a synthetic CSI driver.

Open issues

  • #583 — head/face tracking fidelity in 05-skinned-realtime.html. Recommended fix: swap MediaPipe Pose Heavy for MediaPipe Holistic (same API, adds 468-point face mesh + hand landmarks for proper PnP head pose and finger curl tracking).