Editing captured motion as data
A two-performer capture runs 170.17 s and has a 53-second dead middle. Cut it to 52.9 s with a few hundred bytes of edit list — the capture itself is never modified.
$S('#a').at(0).play('clip-a') and the same for #b. Everything below is pasted into the Shell tab; nothing needs the viewport, so an agent can author the same cut headlessly.1 Register the pair
Two performers captured in one metric frame are only coherent together. Registering them as a group makes every edit apply to both, atomically — same spans, same blend windows.
const sparring = $M.group('sparring', ['#a', '#b']);
sparring.collision(); // baseline over the full capture
The baseline report says how close the two hips ever get on the floor. For the capture used here: min 0.80 m, never under the 0.6 m floor for non-contact sparring.
2 Find the exchanges
In the measured capture, the close approaches cluster into 18 exchanges. They are front-loaded: ten land between 2.6 s and 43.2 s, then nothing from 43.2 s to 98.5 s. Padding each cluster by −1.0 s / +1.2 s and merging gaps under 0.4 s gives eight spans:
| # | in | out | dur | closest |
|---|---|---|---|---|
| E01 | 1.60 | 9.00 | 7.40 s | 0.94 m |
| E02 | 14.60 | 22.10 | 7.50 s | 0.96 m |
| E03 | 30.80 | 44.40 | 13.60 s | 0.80 m |
| E04 | 97.50 | 103.40 | 5.90 s | 1.11 m |
| E05 | 104.80 | 110.80 | 6.00 s | 0.93 m |
| E06 | 113.70 | 117.60 | 3.90 s | 1.08 m |
| E07 | 134.70 | 140.60 | 5.90 s | 1.10 m |
| E08 | 158.40 | 161.10 | 2.70 s | 1.17 m |
That removes 44.4 – 97.5 s (53.1 s) and keeps 52.9 s — 31% of the source.
3 Splice them
splice joins the spans. A blend is mandatory: over the transition window (0.25 s ≈ 8 frames by default) every rotation is slerped from the outgoing pose to the incoming one, and the root is offset so the hips continue from where they were — otherwise the body pops or teleports at every cut.
sparring.splice([
[1.6, 9.0], [14.6, 22.1], [30.8, 44.4], [97.5, 103.4],
[104.8, 110.8], [113.7, 117.6], [134.7, 140.6], [158.4, 161.1],
].map(([from, to]) => ({ from, to })), { blend: 0.25 });
editor.timeline.duration; // 52.9 (once the cameras are remapped — step 5)
sparring.asCommand(); // the declarative form of this cut
timeOffset and speedOf are refused until you call breakRegistration() — it warns what is lost (they can clip through each other) and is undoable. A directive on a single member, e.g. $S('#a').play('clip-a', { from: 1, to: 5 }), is refused for the same reason.4 Check for collisions
No renderer will warn you that the fighters now overlap. The collision report measures the hip-to-hip floor distance over the edited timeline and flags every span under the floor:
console.log(sparring.collision()); // { min, minAt, ok, violations: [...], spans: [...] }
sparring.collision({ floor: 0.6 }); // the default floor, in metres
For this cut: ok: true, no span under 0.6 m. The closest approach sits inside a blend window (0.74 m) — the blend interpolates the two performers' positions, so it can bring them closer than either source span does; the check is what tells you.
5 Remap the cameras
Shots authored against the 170 s source need the new timebase. remapShots() keeps every event that lies wholly inside one kept span, moves it to the cut's clock, and drops (and reports) the rest:
const r = sparring.remapShots();
r.kept; r.dropped; // what survived the cut
editor.timeline.duration; // 52.9
6 Slow a beat, hold a pose
Everything is composed from the same span primitive and applies to both performers:
sparring.hold(20, 0.4); // freeze the pose at cut-time 20 s for 0.4 s
sparring.retime([[30.8, 1], [38, 0.5]]); // from source 38 s on, run at half speed
sparring.rootYaw(15); // turn the whole performance 15° about its centre
sparring.rootOffset([0.5, 0, 0]); // move it in the world, same vector for both
7 Undo, clear, commit
- Every call above is one Undo step — the same stack as the Animations tab.
sparring.clear()drops the edit list; the full 170.17 s capture plays again, untouched.- Open the Animations tab: the cut is one teal block per performer with a tick at every span boundary, and a Collision check / Clear cut bar underneath. Drag a block and both performers move together; the code panel shows the same cut as a
$M('sparring').cut(...)line you can edit and save. - Commit from the Git tab. The scene is written compact (no pretty-printing) with the animation floats quantized — see below.
Credits and licences
People
- Tejaswi Gowda (script)
- Pavan Turaga (asset)
- Assegid Kidane (asset)
Assets
- Sparring capture: clips st1_1072026Skeleton and st2_1072026Skeleton (170.17 s): Recorded with an OptiTrack system in "Martial Arts and Motion Capture" (GAME 294), Arizona State University; course run by Pavan Turaga, recorded by Assegid Kidane; the two performers are not named Licence: not stated.
Tools
None used.