cozyclay 2.1.1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (585) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +43 -116
  4. package/bin/agent/agent-runner.mjs +14 -15
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/session-store.mjs +2 -2
  14. package/bin/agent/studio-context-budget.mjs +93 -0
  15. package/bin/agent/studio-motion-reference.mjs +23 -0
  16. package/bin/agent/studio-prompt.mjs +1 -1
  17. package/bin/agent/studio-rebase-text.mjs +32 -0
  18. package/bin/agent/studio-rebase.mjs +26 -39
  19. package/bin/agent/studio-tools.mjs +9 -28
  20. package/bin/agent/tool-arguments.mjs +49 -0
  21. package/bin/agent/turn-frames.mjs +66 -0
  22. package/bin/codex-auth.mjs +9 -2
  23. package/bin/cozyclay.mjs +10 -116
  24. package/bin/live/cli.mjs +3 -3
  25. package/bin/mcp-runtime.mjs +2 -12
  26. package/bin/package-signature.mjs +0 -4
  27. package/dist/app/index.html +7 -6
  28. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  29. package/dist/assets/analytics-BWCqdgWA.js +1 -0
  30. package/dist/assets/app-DfTCxaKk.js +5087 -0
  31. package/dist/assets/app-f-fkV5uP.css +1 -0
  32. package/dist/assets/compile-worker-BxKXh07s.js +5 -0
  33. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  34. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
  35. package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
  36. package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
  37. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  38. package/dist/assets/workflow-D78eiKTa.js +179 -0
  39. package/dist/cozyclay-package.json +1 -1
  40. package/dist/index.html +3 -3
  41. package/dist/sw.js +3 -6
  42. package/dist/workflow/index.html +5 -4
  43. package/mcp/LIVE-PROTOCOL.md +7 -47
  44. package/mcp/README.md +16 -26
  45. package/mcp/live-hub.mjs +6 -10
  46. package/mcp/package.json +1 -3
  47. package/mcp/tool-handlers.mjs +4 -168
  48. package/package.json +3 -22
  49. package/src/App.jsx +151 -2625
  50. package/src/agent-progress.js +80 -0
  51. package/src/agent-turn-mark.js +30 -0
  52. package/src/analytics.js +1 -19
  53. package/src/app-context.js +12 -2
  54. package/src/app-stage.jsx +2 -34
  55. package/src/asset-pane.jsx +6 -5
  56. package/src/camera-block.js +21 -0
  57. package/src/camera-direction.js +120 -0
  58. package/src/character-kind.js +3 -9
  59. package/src/command-bus.js +10 -8
  60. package/src/commands/cast.js +5 -34
  61. package/src/commands/elements/character.js +1 -1
  62. package/src/commands/elements/motion.js +1 -6
  63. package/src/commands/elements/object.js +6 -2
  64. package/src/commands/elements/shot.js +4 -1
  65. package/src/commands/elements.js +38 -14
  66. package/src/commands/index.js +3 -1
  67. package/src/commands/kits.js +37 -0
  68. package/src/commands/motion-timing.js +57 -0
  69. package/src/commands/motion.js +9 -152
  70. package/src/commands/motion3-compile.js +65 -0
  71. package/src/commands/motion3.js +214 -0
  72. package/src/commands/objects.js +6 -4
  73. package/src/commands/shot.js +55 -13
  74. package/src/commands/stage.js +14 -1
  75. package/src/commands/view.js +4 -1
  76. package/src/document-mirrors.js +42 -0
  77. package/src/document-store.js +4 -0
  78. package/src/domains/cast.js +19 -194
  79. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  80. package/src/domains/motion/take-source.js +53 -0
  81. package/src/domains/motion.js +60 -1838
  82. package/src/domains/scenes.js +12 -55
  83. package/src/domains/shots.js +2 -2
  84. package/src/domains/stage.js +4 -2
  85. package/src/draft-ghost.js +47 -0
  86. package/src/draft-ghost.jsx +129 -0
  87. package/src/dualview.jsx +4 -3
  88. package/src/effects/EffectsLayer.jsx +112 -0
  89. package/src/effects/effect-meshes.js +232 -0
  90. package/src/effects/effect-record.js +103 -0
  91. package/src/effects/emitter.js +59 -0
  92. package/src/effects/simulate.js +261 -0
  93. package/src/effects/state.js +59 -0
  94. package/src/execution-telemetry.js +2 -3
  95. package/src/first-success-guide.jsx +5 -5
  96. package/src/ghost-set.js +257 -0
  97. package/src/held-object-edit.jsx +113 -0
  98. package/src/held-object-pose.js +175 -0
  99. package/src/hierarchy-model.js +1 -1
  100. package/src/hierarchy-panel.jsx +1 -1
  101. package/src/imported-mesh.jsx +103 -0
  102. package/src/kits/held.js +84 -0
  103. package/src/kits/index.js +70 -0
  104. package/src/kits/smoking.js +57 -0
  105. package/src/light-blockers.js +93 -0
  106. package/src/motion/beat-board-model.js +84 -0
  107. package/src/motion/beat-board.css +124 -0
  108. package/src/motion/beat-board.jsx +60 -0
  109. package/src/motion/center-of-mass.js +66 -0
  110. package/src/motion/fix-collisions.js +850 -0
  111. package/src/motion/physics-review.js +118 -0
  112. package/src/{ardy → motion}/physics-support.js +1 -76
  113. package/src/motion/rig-snapshot.js +22 -0
  114. package/src/motion/take-segments.jsx +140 -0
  115. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  116. package/src/{ardy → motion}/timeline.css +1 -2
  117. package/src/{ardy → motion}/timeline.jsx +42 -413
  118. package/src/motion/waypoints.js +76 -0
  119. package/src/motion3/beats.js +83 -0
  120. package/src/motion3/body.js +158 -0
  121. package/src/motion3/bones.js +47 -0
  122. package/src/motion3/carried.js +47 -0
  123. package/src/motion3/compile-job.js +79 -0
  124. package/src/motion3/compile-worker.js +10 -0
  125. package/src/motion3/compiler/anchors.js +113 -0
  126. package/src/motion3/compiler/arm-orient.js +128 -0
  127. package/src/motion3/compiler/channels.js +125 -0
  128. package/src/motion3/compiler/compile.js +205 -0
  129. package/src/motion3/compiler/gait-rhythm.js +97 -0
  130. package/src/motion3/compiler/grip.js +171 -0
  131. package/src/motion3/compiler/head-steady.js +48 -0
  132. package/src/motion3/compiler/hold.js +134 -0
  133. package/src/motion3/compiler/idle.js +113 -0
  134. package/src/motion3/compiler/limbs.js +197 -0
  135. package/src/motion3/compiler/math.js +72 -0
  136. package/src/motion3/compiler/pose.js +177 -0
  137. package/src/motion3/compiler/quat.js +49 -0
  138. package/src/motion3/compiler/skeleton.js +74 -0
  139. package/src/motion3/compiler/validate-common.js +32 -0
  140. package/src/motion3/compiler/validate-goals.js +118 -0
  141. package/src/motion3/compiler/validate-objects.js +144 -0
  142. package/src/motion3/compiler/validate-parts.js +118 -0
  143. package/src/motion3/compiler/validate.js +122 -0
  144. package/src/motion3/compiler/vocab.js +74 -0
  145. package/src/motion3/compiler/walk-pelvis.js +275 -0
  146. package/src/motion3/compiler/walk.js +207 -0
  147. package/src/motion3/compiler/wrist-clamp.js +32 -0
  148. package/src/motion3/delta.js +47 -0
  149. package/src/motion3/describe.js +177 -0
  150. package/src/motion3/docs.js +27 -0
  151. package/src/motion3/edit-carry.js +21 -0
  152. package/src/motion3/fix/ease.js +16 -0
  153. package/src/motion3/fix/ground.js +37 -0
  154. package/src/motion3/fix/index.js +52 -0
  155. package/src/motion3/fix/push.js +71 -0
  156. package/src/motion3/fix/reach.js +46 -0
  157. package/src/motion3/fix/retime.js +79 -0
  158. package/src/motion3/fix/self.js +211 -0
  159. package/src/motion3/fix/slide.js +70 -0
  160. package/src/motion3/fix/turn.js +51 -0
  161. package/src/motion3/follow.js +39 -0
  162. package/src/motion3/free-motion.js +204 -0
  163. package/src/motion3/grip-choice.js +130 -0
  164. package/src/motion3/hold-plan.js +157 -0
  165. package/src/motion3/json-patch.js +77 -0
  166. package/src/motion3/key-aim.js +105 -0
  167. package/src/motion3/key-edit.js +82 -0
  168. package/src/motion3/lenient.js +127 -0
  169. package/src/motion3/object-keys.js +236 -0
  170. package/src/motion3/object-pose.js +182 -0
  171. package/src/motion3/object-tracks.js +12 -0
  172. package/src/motion3/people.js +82 -0
  173. package/src/motion3/pipeline.js +102 -0
  174. package/src/motion3/playback.js +51 -0
  175. package/src/motion3/probes.js +91 -0
  176. package/src/motion3/qa/body-checks.js +34 -0
  177. package/src/motion3/qa/check-names.js +14 -0
  178. package/src/motion3/qa/checks.js +125 -0
  179. package/src/motion3/qa/contact-checks.js +51 -0
  180. package/src/motion3/qa/explain.js +80 -0
  181. package/src/motion3/qa/fmt.js +5 -0
  182. package/src/motion3/qa/foot-checks.js +72 -0
  183. package/src/motion3/qa/geometry.js +77 -0
  184. package/src/motion3/qa/goals.js +191 -0
  185. package/src/motion3/qa/ground.js +60 -0
  186. package/src/motion3/qa/hints-contact.js +195 -0
  187. package/src/motion3/qa/hints-goal.js +160 -0
  188. package/src/motion3/qa/hints-knee.js +24 -0
  189. package/src/motion3/qa/hints-motion.js +209 -0
  190. package/src/motion3/qa/hints-object.js +91 -0
  191. package/src/motion3/qa/index.js +44 -0
  192. package/src/motion3/qa/key-checks.js +82 -0
  193. package/src/motion3/qa/near.js +96 -0
  194. package/src/motion3/qa/object-checks.js +135 -0
  195. package/src/motion3/qa/patch.js +161 -0
  196. package/src/motion3/qa/people-checks.js +37 -0
  197. package/src/motion3/qa/severity.js +38 -0
  198. package/src/motion3/qa/spec-checks.js +65 -0
  199. package/src/motion3/qa/style.js +228 -0
  200. package/src/motion3/qa/wrist-checks.js +69 -0
  201. package/src/motion3/qa/wrist-obs.js +60 -0
  202. package/src/motion3/refs.js +129 -0
  203. package/src/motion3/repair-tune.js +121 -0
  204. package/src/motion3/repair.js +83 -0
  205. package/src/motion3/report.js +67 -0
  206. package/src/motion3/rigs/index.js +14 -0
  207. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  208. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  209. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  210. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  211. package/src/motion3/scene.js +60 -0
  212. package/src/motion3/schema/brief.js +89 -0
  213. package/src/motion3/schema/fields.js +44 -0
  214. package/src/motion3/schema/spec-examples.js +46 -0
  215. package/src/motion3/schema/topics.js +113 -0
  216. package/src/motion3/schema/types.js +149 -0
  217. package/src/motion3/search.js +117 -0
  218. package/src/motion3/second-hand.js +112 -0
  219. package/src/motion3/settle.js +136 -0
  220. package/src/motion3/skills/basic.js +83 -0
  221. package/src/motion3/skills/book.js +76 -0
  222. package/src/motion3/skills/car.js +145 -0
  223. package/src/motion3/skills/flip.js +200 -0
  224. package/src/motion3/skills/frames-auto.js +29 -0
  225. package/src/motion3/skills/impact.js +109 -0
  226. package/src/motion3/skills/index.js +85 -0
  227. package/src/motion3/skills/info.js +80 -0
  228. package/src/motion3/skills/smoke-kit.js +109 -0
  229. package/src/motion3/skills/smoke.js +160 -0
  230. package/src/motion3/spec-store.js +59 -0
  231. package/src/motion3/take-edit.js +155 -0
  232. package/src/motion3/take.js +63 -0
  233. package/src/motion3/timeline.js +127 -0
  234. package/src/motion3/views.js +121 -0
  235. package/src/motion3/walk-plan.js +119 -0
  236. package/src/object-catalog.jsx +7 -1
  237. package/src/object-colors.js +98 -0
  238. package/src/object-gizmo-parts.js +115 -0
  239. package/src/object-gizmo.jsx +37 -122
  240. package/src/object-glow.js +139 -0
  241. package/src/object-hidden.js +33 -0
  242. package/src/object-look.js +196 -0
  243. package/src/object-size.js +10 -0
  244. package/src/object-travel.js +71 -6
  245. package/src/panels/CameraPanel.jsx +7 -7
  246. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  247. package/src/panels/LightPanel.jsx +25 -4
  248. package/src/panels/ObjectLookPanel.jsx +88 -0
  249. package/src/panels/PosePanel.jsx +2 -17
  250. package/src/panels/RigControlPanel.jsx +5 -53
  251. package/src/panels/SubjectBox.jsx +4 -1
  252. package/src/panels/details.css +14 -0
  253. package/src/posestudio.jsx +4 -27
  254. package/src/project-browser.jsx +3 -4
  255. package/src/project-resources.js +18 -31
  256. package/src/project.js +13 -104
  257. package/src/props.jsx +25 -280
  258. package/src/qa-label.js +25 -0
  259. package/src/rail-dot-drag.js +23 -0
  260. package/src/render-passes.js +10 -0
  261. package/src/resource-status.jsx +1 -0
  262. package/src/result-modal.jsx +4 -19
  263. package/src/room.jsx +20 -12
  264. package/src/scene-assets.js +8 -6
  265. package/src/scene-light-place.js +72 -0
  266. package/src/scene-lights.jsx +148 -0
  267. package/src/scene-objects.js +31 -139
  268. package/src/scenes.js +15 -105
  269. package/src/semantic-edit.js +2 -5
  270. package/src/set-pieces.jsx +183 -0
  271. package/src/settings-menu.jsx +3 -11
  272. package/src/shell/BottomDock.jsx +9 -51
  273. package/src/shell/DetailsSlot.jsx +16 -200
  274. package/src/shell/GhostsMenuGroup.jsx +74 -0
  275. package/src/shell/LibrarySlot.jsx +1 -3
  276. package/src/shell/MenuBar.jsx +3 -3
  277. package/src/shell/PreferencesDialog.jsx +15 -264
  278. package/src/shell/PreferencesSlot.jsx +2 -8
  279. package/src/shell/TopBar.jsx +5 -84
  280. package/src/shell/ViewportToolbar.jsx +10 -70
  281. package/src/shell/agent-glass.css +19 -6
  282. package/src/shell/details-selection.js +42 -0
  283. package/src/shell/ghosts-menu.css +22 -0
  284. package/src/shell/glass-regions.css +29 -3
  285. package/src/shell/preferences.css +6 -1
  286. package/src/shot-authoring.js +4 -7
  287. package/src/soft-shadows.js +37 -0
  288. package/src/stage-attach-frames.js +12 -12
  289. package/src/stage-canvas-rigs.jsx +3 -3
  290. package/src/stage-capture.jsx +42 -8
  291. package/src/stage-characters.jsx +22 -216
  292. package/src/stage-editor-gizmos.jsx +6 -6
  293. package/src/stage-environment.js +93 -0
  294. package/src/stage-hdr-builtins.js +107 -0
  295. package/src/stage-hdr-image.js +116 -0
  296. package/src/stage-hdr-library.js +70 -0
  297. package/src/stage-hdr-map.js +94 -0
  298. package/src/stage-hdr.js +130 -0
  299. package/src/stage-lighting.js +149 -0
  300. package/src/stage-occlusion.js +210 -0
  301. package/src/stage-sky.jsx +159 -0
  302. package/src/stage-startup-defaults.js +5 -9
  303. package/src/studio-actions.js +16 -14
  304. package/src/studio-agent-commands.js +19 -16
  305. package/src/studio-agent-motion.js +9 -9
  306. package/src/studio-agent-protocol.js +47 -77
  307. package/src/studio-app-binding.js +27 -47
  308. package/src/studio-asset-list.js +17 -0
  309. package/src/studio-contact-sheet.js +37 -5
  310. package/src/studio-elements.js +27 -7
  311. package/src/studio-entity-bounds.js +18 -0
  312. package/src/studio-error-codes.js +15 -0
  313. package/src/studio-export-framing.js +152 -0
  314. package/src/studio-lighting-check.js +103 -0
  315. package/src/studio-motion-visual.js +150 -0
  316. package/src/studio-schema-errors.js +80 -0
  317. package/src/studio-support-gap.js +74 -0
  318. package/src/styles.css +1 -19
  319. package/src/timeline-defaults.js +5 -0
  320. package/src/timeline-extent.js +8 -20
  321. package/src/tutorial-walk.js +17 -0
  322. package/src/ui-scrub.js +1 -1
  323. package/src/use-shot-camera-tracks.js +12 -0
  324. package/src/view-ghosts.js +97 -0
  325. package/src/view-ghosts.jsx +49 -0
  326. package/src/workflow/AgentPanel.jsx +47 -166
  327. package/src/workflow/AgentProgressCard.jsx +109 -0
  328. package/src/workflow/AgentReplyText.jsx +37 -0
  329. package/src/workflow/AgentTurnSteps.jsx +84 -0
  330. package/src/workflow/LoginPanel.jsx +140 -0
  331. package/src/workflow/WorkflowBuilder.jsx +7 -23
  332. package/src/workflow/agent-client.js +54 -51
  333. package/src/workflow/agent-panel.css +71 -1
  334. package/src/workflow/agent-progress-card.css +167 -0
  335. package/src/workflow/agent-progress-view.js +47 -0
  336. package/src/workflow/agent-turn-steps.css +113 -0
  337. package/src/workflow/agent-turn.js +191 -0
  338. package/src/workflow/login-client.js +34 -0
  339. package/src/workflow/motion-input.js +18 -31
  340. package/src/workflow/node-schema.js +1 -1
  341. package/src/workflow/reply-markdown.js +69 -0
  342. package/src/workflow/scene-asset-sync.js +8 -8
  343. package/tools/agent-session-stats.mjs +61 -0
  344. package/tools/dev-full.mjs +5 -56
  345. package/tools/eslint-baseline.json +12 -13
  346. package/tools/knip-baseline.json +0 -179
  347. package/tools/loc-baseline.json +31 -81
  348. package/tools/motion3/extract-body.mjs +103 -0
  349. package/tools/motion3/extract-rig.mjs +85 -0
  350. package/tools/process-supervisor.mjs +0 -88
  351. package/tools/run-tests.mjs +9 -125
  352. package/bin/agent/motion-runtime.mjs +0 -76
  353. package/dist/ardy/cskel27-rest.json +0 -557
  354. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  355. package/dist/assets/app-B_2XB9rw.css +0 -1
  356. package/dist/assets/app-DAuvKPP8.js +0 -4920
  357. package/dist/assets/config-Bx7pYsD9.js +0 -1
  358. package/dist/assets/config-DeoFTYzb.css +0 -1
  359. package/dist/assets/demo-CvKuBgy5.js +0 -1
  360. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  361. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  362. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  363. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  364. package/dist/demo/walk-then-stop.npz +0 -0
  365. package/mcp/ardy-prompts.mjs +0 -218
  366. package/src/ardy/auto-fix-panel.css +0 -108
  367. package/src/ardy/auto-physics.js +0 -1194
  368. package/src/ardy/client.js +0 -153
  369. package/src/ardy/convert.js +0 -313
  370. package/src/ardy/cskel27-neutral.js +0 -39
  371. package/src/ardy/cskel27.js +0 -77
  372. package/src/ardy/export.js +0 -165
  373. package/src/ardy/fix-collisions.js +0 -2257
  374. package/src/ardy/foot-lock.js +0 -230
  375. package/src/ardy/motion-calibration.js +0 -118
  376. package/src/ardy/motion-edit.js +0 -213
  377. package/src/ardy/motion-url.js +0 -55
  378. package/src/ardy/mp4-duration.js +0 -102
  379. package/src/ardy/npz.js +0 -595
  380. package/src/ardy/physics-panel.css +0 -21
  381. package/src/ardy/physics-panel.jsx +0 -89
  382. package/src/ardy/physics-refine.js +0 -111
  383. package/src/ardy/physics-review.js +0 -567
  384. package/src/ardy/physics-temporal.js +0 -54
  385. package/src/ardy/platform-fit-panel.css +0 -64
  386. package/src/ardy/platform-fit-panel.jsx +0 -41
  387. package/src/ardy/platform-fit.js +0 -326
  388. package/src/ardy/playback.js +0 -711
  389. package/src/ardy/pose-pin.js +0 -97
  390. package/src/ardy/prompt-clips.js +0 -30
  391. package/src/ardy/retime.js +0 -211
  392. package/src/ardy/root-drop.js +0 -369
  393. package/src/ardy/timeline-resize.js +0 -13
  394. package/src/ardy/to-cskel27.js +0 -225
  395. package/src/ardy/trim.js +0 -33
  396. package/src/ardy/waypoints.js +0 -369
  397. package/src/ardy-bridge-contract.js +0 -281
  398. package/src/domains/motion/motion-video-capture.js +0 -664
  399. package/src/i2v-motion-client.js +0 -77
  400. package/src/i2v-motion-studio.jsx +0 -180
  401. package/src/line-edit.js +0 -1348
  402. package/src/motion/generation.js +0 -105
  403. package/src/motion-readiness-ui.jsx +0 -67
  404. package/src/motion-readiness.js +0 -40
  405. package/src/motion-resources.js +0 -177
  406. package/src/motion-store.js +0 -65
  407. package/src/motion-trail.js +0 -601
  408. package/src/multimodel-ingest.js +0 -394
  409. package/src/panels/PromptBlocksPanel.jsx +0 -444
  410. package/src/panels/VideoCapturePanel.jsx +0 -181
  411. package/src/pose-extract/browser.js +0 -42
  412. package/src/pose-extract/detector.js +0 -100
  413. package/src/pose-extract/fit.js +0 -227
  414. package/src/pose-extract/image-frame.js +0 -51
  415. package/src/pose-extract/index.js +0 -58
  416. package/src/pose-extract/keyframes.js +0 -74
  417. package/src/pose-extract/landmarks.js +0 -162
  418. package/src/pose-extract/mirror.js +0 -153
  419. package/src/pose-extract/take.js +0 -131
  420. package/src/pose-extract/video-frames.js +0 -91
  421. package/src/take-recipe.js +0 -165
  422. package/src/trail-key-conflicts.js +0 -39
  423. package/src/trail-pick.js +0 -59
  424. package/src/use-i2v-capture-modal.js +0 -15
  425. package/tools/ardy/BRIDGE.md +0 -37
  426. package/tools/ardy/GVHMR-NPZ.md +0 -68
  427. package/tools/ardy/README.md +0 -124
  428. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  429. package/tools/ardy/artifacts.mjs +0 -34
  430. package/tools/ardy/bridge.mjs +0 -1732
  431. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  432. package/tools/ardy/cclay_constrained_generate.py +0 -561
  433. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  434. package/tools/ardy/cclay_motion_edit.py +0 -510
  435. package/tools/ardy/cclay_sequence_generate.py +0 -705
  436. package/tools/ardy/cclay_worker.py +0 -304
  437. package/tools/ardy/dump-npz.py +0 -205
  438. package/tools/ardy/extract-rest.mjs +0 -299
  439. package/tools/ardy/extract.mjs +0 -507
  440. package/tools/ardy/footage.mjs +0 -467
  441. package/tools/ardy/gvhmr-floor.mjs +0 -102
  442. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  443. package/tools/ardy/gvhmr_fastpath.py +0 -168
  444. package/tools/ardy/gvhmr_trajectory.py +0 -240
  445. package/tools/ardy/merge-text-encoder.py +0 -160
  446. package/tools/ardy/mocap-metrics.mjs +0 -200
  447. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  448. package/tools/ardy/motion-stabilize.mjs +0 -348
  449. package/tools/ardy/npz.mjs +0 -414
  450. package/tools/ardy/pose-to-npz.mjs +0 -106
  451. package/tools/ardy/prompt-limits.mjs +0 -5
  452. package/tools/ardy/run-edit-on-box.sh +0 -73
  453. package/tools/ardy/run-local.mjs +0 -233
  454. package/tools/ardy/run-on-box.sh +0 -593
  455. package/tools/ardy/run-sequence-on-box.sh +0 -177
  456. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  457. package/tools/ardy/runners/index.mjs +0 -18
  458. package/tools/ardy/runners/local.mjs +0 -455
  459. package/tools/ardy/runners/proc.mjs +0 -125
  460. package/tools/ardy/runners/remote.mjs +0 -252
  461. package/tools/ardy/setup-local.mjs +0 -189
  462. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  463. package/tools/ardy/setup-text-encoder.py +0 -306
  464. package/tools/ardy/smpl-cskel27.mjs +0 -178
  465. package/tools/ardy/visual-qa.mjs +0 -196
  466. package/tools/ardy/vq-car.mjs +0 -52
  467. package/tools/bench/EXP3.md +0 -61
  468. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  469. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  470. package/tools/bench/cclay_bench_runner.py +0 -42
  471. package/tools/bench/cube-contact.mjs +0 -162
  472. package/tools/bench/exp3.mjs +0 -152
  473. package/tools/bench/extract-bench-lib.mjs +0 -216
  474. package/tools/bench/extract-bench.mjs +0 -302
  475. package/tools/bench/fal-generate.mjs +0 -88
  476. package/tools/bench/fit/README.md +0 -189
  477. package/tools/bench/fit/camera.mjs +0 -59
  478. package/tools/bench/fit/contact.mjs +0 -419
  479. package/tools/bench/fit/footlock.mjs +0 -234
  480. package/tools/bench/fit/motion.mjs +0 -85
  481. package/tools/bench/fit/pin.mjs +0 -27
  482. package/tools/bench/fit/remote.mjs +0 -105
  483. package/tools/bench/fit-bench.mjs +0 -126
  484. package/tools/bench/fit-sanity.mjs +0 -45
  485. package/tools/bench/metrics.mjs +0 -313
  486. package/tools/bench/obs/depth.mjs +0 -258
  487. package/tools/bench/obs/extrinsics.mjs +0 -175
  488. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  489. package/tools/bench/obs/ground.mjs +0 -294
  490. package/tools/bench/obs/heading.mjs +0 -151
  491. package/tools/bench/obs/ladder.mjs +0 -555
  492. package/tools/bench/obs/mannequin-betas.json +0 -124
  493. package/tools/bench/obs/remote.mjs +0 -83
  494. package/tools/bench/obs/rest_joints.py +0 -52
  495. package/tools/bench/obs/ybot-targets.mjs +0 -49
  496. package/tools/bench/obs-bench.mjs +0 -520
  497. package/tools/bench/score.mjs +0 -343
  498. package/tools/bench/summarize.mjs +0 -86
  499. package/tools/demo-worker/README.md +0 -137
  500. package/tools/demo-worker/api-client.mjs +0 -233
  501. package/tools/demo-worker/generate.mjs +0 -301
  502. package/tools/demo-worker/index.mjs +0 -466
  503. package/tools/demo-worker/sign-core.mjs +0 -112
  504. package/tools/demo-worker/sign.mjs +0 -161
  505. package/tools/gt-render/browser.mjs +0 -182
  506. package/tools/gt-render/camera-math.mjs +0 -276
  507. package/tools/gt-render/page.mjs +0 -267
  508. package/tools/gt-render/render.mjs +0 -514
  509. package/tools/gt-render/scene-box.mjs +0 -31
  510. package/tools/gt-render/take-transform.mjs +0 -87
  511. package/tools/kimodo/compare-seam.mjs +0 -108
  512. package/tools/kimodo/constraints.mjs +0 -244
  513. package/tools/kimodo/edit.mjs +0 -259
  514. package/tools/kimodo/effector-constraints.mjs +0 -200
  515. package/tools/kimodo/generate.mjs +0 -646
  516. package/tools/kimodo/local-output.mjs +0 -265
  517. package/tools/kimodo/measure-pose.mjs +0 -65
  518. package/tools/kimodo/measure-preserve.mjs +0 -899
  519. package/tools/kimodo/measure-waypoints.mjs +0 -55
  520. package/tools/kimodo/mlx-generate.py +0 -111
  521. package/tools/kimodo/pose-constraints.mjs +0 -248
  522. package/tools/kimodo/preserve-mask.mjs +0 -543
  523. package/tools/kimodo/read-npz.mjs +0 -189
  524. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  525. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  526. package/tools/kimodo/runner.mjs +0 -284
  527. package/tools/kimodo/seam-batch.mjs +0 -77
  528. package/tools/kimodo/setup-local.mjs +0 -30
  529. package/tools/kimodo/setup-on-box.sh +0 -339
  530. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  531. package/tools/morphgs/assets/gen_truth.py +0 -39
  532. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  533. package/tools/morphgs/assets/playback-check.mjs +0 -30
  534. package/tools/morphgs/demo-gate.sh +0 -26
  535. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  536. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  537. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  538. package/tools/morphgs/setup-on-cluster.sh +0 -126
  539. package/tools/projflow/driver.py +0 -1069
  540. package/tools/projflow/generate.mjs +0 -805
  541. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  542. package/tools/projflow/line-edit-job.mjs +0 -547
  543. package/tools/projflow/replay.mjs +0 -452
  544. package/tools/projflow/runner.mjs +0 -269
  545. package/tools/projflow/service.mjs +0 -742
  546. package/tools/track/DESIGN.md +0 -513
  547. package/tools/track/backfill-provenance.mjs +0 -116
  548. package/tools/track/budget.mjs +0 -8
  549. package/tools/track/check-rig.mjs +0 -209
  550. package/tools/track/diagnostics.schema.json +0 -60
  551. package/tools/track/export-rig.mjs +0 -256
  552. package/tools/track/fallback.mjs +0 -7
  553. package/tools/track/fk-parity-fixture.mjs +0 -161
  554. package/tools/track/gate.mjs +0 -349
  555. package/tools/track/masks.mjs +0 -318
  556. package/tools/track/metrics.mjs +0 -201
  557. package/tools/track/publish-obs.mjs +0 -108
  558. package/tools/track/py/check_env.py +0 -61
  559. package/tools/track/py/eval_lr.py +0 -229
  560. package/tools/track/py/lr_viterbi.py +0 -294
  561. package/tools/track/py/masks.py +0 -405
  562. package/tools/track/py/objective.py +0 -382
  563. package/tools/track/py/rig.py +0 -508
  564. package/tools/track/py/scene.py +0 -326
  565. package/tools/track/py/test_joint_indices.py +0 -123
  566. package/tools/track/py/test_lr_viterbi.py +0 -195
  567. package/tools/track/py/test_masks.py +0 -229
  568. package/tools/track/py/test_rig.py +0 -288
  569. package/tools/track/py/test_scene.py +0 -354
  570. package/tools/track/py/test_track.py +0 -328
  571. package/tools/track/py/track.py +0 -411
  572. package/tools/track/remote.mjs +0 -140
  573. package/tools/track/rig-dump.mjs +0 -227
  574. package/tools/track/run-box-tests.mjs +0 -14
  575. package/tools/track/run-box.mjs +0 -168
  576. package/tools/track/setup-box.sh +0 -59
  577. package/tools/track/study-2d.mjs +0 -728
  578. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  579. /package/src/{ardy → motion}/ground.js +0 -0
  580. /package/src/{ardy → motion}/ik-drag.js +0 -0
  581. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  582. /package/src/{ardy → motion}/ik.js +0 -0
  583. /package/src/{ardy → motion}/physics-surface.js +0 -0
  584. /package/src/{ardy → motion}/playback-clock.js +0 -0
  585. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -0,0 +1,197 @@
1
+ // Two-bone limb IK, foot orientation, wrist and finger posing on a world-space state.
2
+ // Every edit rotates a bone's subtree about that bone's own joint, so the result maps
3
+ // exactly back to local quaternions (skeleton.js toLocal).
4
+ import { add, sub, scale, dot, len, norm, cross, clamp, apply, T, align, axisRot, ry, rx } from "./math.js";
5
+ import { rotateSubtree } from "./skeleton.js";
6
+
7
+ /** Direction fixed to bone b that points along `restDir` in the rest pose. */
8
+ export const carried = (sk, s, b, restDir) => apply(s.R[b], apply(T(sk.restWorldRot[b]), restDir));
9
+
10
+ // Elbow flexion: degrees between the upper arm and the forearm (0 straight), read on the joints as QA
11
+ // reads it (Arm->ForeArm against ForeArm->Hand). Active flexion ends near 145 deg (clinical norms
12
+ // 140-150: AMA 140, Kendall 145, AAOS 150), where the forearm's muscle meets the upper arm's; the
13
+ // passive fold stops a few degrees on. An arm IK follows its wrist target freely to ELBOW_FOLD, eases
14
+ // onto ELBOW_STOP (armReach) and leaves a target nearer the shoulder as a residual (REACH), like one
15
+ // out of reach. Before, twoBone folded an arm flat: the forearm lay in the upper arm and the bend plane
16
+ // was any plane at all (session df6acdcc, 171 deg).
17
+ export const ELBOW_FOLD = 145, ELBOW_STOP = 150;
18
+ // Knee flexion, read on the joints as QA reads it (UpLeg->Leg against Leg->Foot, 0 straight). Active
19
+ // flexion ends near 135-140 deg (AAOS 135); past it the knee folds only passively, under the body's
20
+ // weight, until the thigh rests on the calf: a deep squat with the heels down reaches 150-160 deg, and
21
+ // passive flexion stops near 160 (the heel at the buttock). A leg IK follows its ankle target freely to
22
+ // KNEE_FOLD (the squats the sessions keyed bend 125-138 deg), eases onto KNEE_STOP as the thigh presses
23
+ // on the calf, and leaves a target nearer the hip as a residual (REACH leg_miss). Before, twoBone folded
24
+ // a knee flat: the shin went into the thigh.
25
+ export const KNEE_FOLD = 150, KNEE_STOP = 160;
26
+ /** The flexion (deg) that puts the wrist d metres from the shoulder of an arm with bones a, b (180 at |a - b| or nearer). */
27
+ export const flexionAt = (a, b, d) => (Math.acos(clamp((d * d - a * a - b * b) / (2 * a * b), -1, 1)) * 180) / Math.PI;
28
+ /** The shoulder-wrist distance of an arm with bones a, b folded `deg` degrees. */
29
+ export const reachAt = (a, b, deg) => Math.sqrt(a * a + b * b + 2 * a * b * Math.cos((deg * Math.PI) / 180));
30
+ /**
31
+ * The shoulder-wrist distance an arm with bones a, b takes for a wrist target d metres away: d while
32
+ * the elbow folds ELBOW_FOLD or less (d >= k), then d + (k - d)^2 / 4w (w = k - the ELBOW_STOP distance)
33
+ * down to d = k - 2w, and the stop beyond. The wrist slows at a constant rate (C1, no kink as it
34
+ * engages: half the peak deceleration of an exponential ease as wide) and sits on the stop once the
35
+ * target is 2w inside. A leg passes KNEE_FOLD and KNEE_STOP (the hip-ankle distance).
36
+ */
37
+ export function armReach(a, b, d, fold = ELBOW_FOLD, stop = ELBOW_STOP) {
38
+ const k = reachAt(a, b, fold);
39
+ if (d >= k) return d;
40
+ const w = k - reachAt(a, b, stop);
41
+ return d > k - 2 * w ? d + ((k - d) * (k - d)) / (4 * w) : k - w;
42
+ }
43
+ /**
44
+ * The wrist point an arm (shoulder S, bones a, b) reaches for a wrist target T: T while the elbow
45
+ * folds ELBOW_FOLD or less, else the point armReach(|T - S|) from the shoulder on the ray from the fold
46
+ * centre C through T. No point can stand for every target inside the stop and still move smoothly (a
47
+ * ball has no continuous retraction onto its boundary): the nearest point (the ray from the shoulder)
48
+ * whips round the shoulder as a target passes near it (session df6acdcc: 120 deg in 2 frames). So C
49
+ * sits on the stop's sphere straight behind the shoulder (`back`, the chest's -Z), in the back, where
50
+ * no hand reaches, and a target passing in front of, above, below or beside the shoulder glides.
51
+ * A leg passes the knee's fold and stop, and `back` is the pelvis's up (upOf): no foot goes into the
52
+ * pelvis, and a squat's ankle target under the hip goes on down from it.
53
+ */
54
+ export function foldPoint(S, T, a, b, back, fold = ELBOW_FOLD, stop = ELBOW_STOP) {
55
+ const k = reachAt(a, b, fold), d = len(sub(T, S));
56
+ if (d >= k) return T;
57
+ const r = armReach(a, b, d, fold, stop), m = scale(back, reachAt(a, b, stop)), C = add(S, m), e = sub(T, C);
58
+ const u = len(e) > 1e-9 ? norm(e) : scale(back, -1), em = dot(u, m);
59
+ return add(C, scale(u, -em + Math.sqrt(Math.max(0, em * em - dot(m, m) + r * r))));
60
+ }
61
+ /** The direction behind the shoulders on pose s (the chest bone's rest -Z), where foldPoint's rays start. */
62
+ export const backOf = (sk, s) => carried(sk, s, sk.index.Spine2 ?? sk.index.Hips, [0, 0, -1]);
63
+ /** The direction up from the hips on pose s (the pelvis's rest +Y), where a knee's foldPoint rays start. */
64
+ const upOf = (sk, s) => carried(sk, s, sk.index.Hips, [0, 1, 0]);
65
+ /** The flexion (deg) an arm with bones a, b takes when `deg` is asked (armReach as an angle). */
66
+ export const elbowFold = (a, b, deg) => flexionAt(a, b, armReach(a, b, reachAt(a, b, deg)));
67
+ /** The flexion (deg) a wrist target asks of the arm of `side` on pose s (a leg's ankle target: bones UpLeg, Leg, Foot). */
68
+ export function askedFold(sk, s, side, target, [u, m, e] = ["Arm", "ForeArm", "Hand"]) {
69
+ const S = s.P[sk.index[`${side}${u}`]], E = s.P[sk.index[`${side}${m}`]], W = s.P[sk.index[`${side}${e}`]];
70
+ return flexionAt(len(sub(E, S)), len(sub(W, E)), len(sub(target, S)));
71
+ }
72
+
73
+ /** Ease an elbow folded past ELBOW_FOLD (an FK abend) back as the IK would (elbowFold), the forearm turning in its bend plane. */
74
+ export function limitFold(sk, s, side) {
75
+ const a = sk.index[`${side}Arm`], fa = sk.index[`${side}ForeArm`], up = sub(s.P[fa], s.P[a]), down = sub(s.P[sk.index[`${side}Hand`]], s.P[fa]);
76
+ const deg = (Math.acos(clamp(dot(norm(up), norm(down)), -1, 1)) * 180) / Math.PI, n = cross(up, down);
77
+ if (deg > ELBOW_FOLD && len(n) > 1e-12) rotateSubtree(sk, s, fa, s.P[fa].slice(), axisRot(n, elbowFold(len(up), len(down), deg) - deg));
78
+ }
79
+
80
+ /** The stops twoBone keeps to: the free fold and the stop (deg), and the fold centre's direction on pose s. */
81
+ const STOPS = { elbow: [ELBOW_FOLD, ELBOW_STOP, backOf], knee: [KNEE_FOLD, KNEE_STOP, upOf] };
82
+
83
+ /** Solve root-mid-end so `end` lands on `target` (clamped to reach), bending toward `pole`.
84
+ * `pole0` is the limb's current bend reference carried by the upper bone. Returns the miss (m).
85
+ * `stop`: "elbow" or "knee", the mid joint folds no further than armReach allows with that joint's
86
+ * fold and stop (foldPoint), and the miss is the residual either way (out of reach, or nearer the
87
+ * shoulder or hip than the fold). */
88
+ export function twoBone(sk, s, root, mid, end, target, pole, restBend, stop = null) {
89
+ const Rt = s.P[root], M = s.P[mid], E = s.P[end];
90
+ const a = len(sub(M, Rt)), b = len(sub(E, M)), lim = STOPS[stop];
91
+ // a joint folds no further than armReach lets it: the end goes to foldPoint, and misses the target by the rest
92
+ const W = lim ? foldPoint(Rt, target, a, b, lim[2](sk, s), lim[0], lim[1]) : target, inside = W === target ? 0 : len(sub(W, target));
93
+ const v = sub(W, Rt), d = len(v), u = scale(v, 1 / Math.max(d, 1e-9));
94
+ const dc = clamp(d, Math.abs(a - b) + 1e-4, (a + b) * 0.9995);
95
+ const l = (a * a - b * b + dc * dc) / (2 * dc), h = Math.sqrt(Math.max(a * a - l * l, 0));
96
+ const pv = norm(sub(pole, scale(u, dot(pole, u))));
97
+ const Mn = add(Rt, add(scale(u, l), scale(pv, h)));
98
+ const p0 = carried(sk, s, root, restBend);
99
+ rotateSubtree(sk, s, root, Rt.slice(), align(sub(M, Rt), p0, sub(Mn, Rt), pv));
100
+ const p1 = carried(sk, s, mid, restBend);
101
+ const Tn = add(Rt, scale(u, dc));
102
+ rotateSubtree(sk, s, mid, s.P[mid].slice(), align(sub(s.P[end], s.P[mid]), p1, sub(Tn, s.P[mid]), pv));
103
+ return inside || d - dc;
104
+ }
105
+
106
+ /**
107
+ * Aim a foot: Foot->ToeBase along yaw/pitch of its rest direction, sole kept level. With `tilt` (pose.js tiltFrame: a body
108
+ * turned well past upright) the yaw and pitch are read in that frame and the sole levels to its up, so a foot in a flip
109
+ * points with the shin instead of staying flat to the floor; no toe-off then (the toes are off the ground).
110
+ */
111
+ export function orientFoot(sk, s, side, yawDeg, pitchDeg, tilt = null) {
112
+ const foot = sk.index[`${side}Foot`], toe = sk.index[`${side}ToeBase`];
113
+ const rest = sub(sk.restWorldPos[toe], sk.restWorldPos[foot]);
114
+ const want = tilt ? apply(tilt.R, apply(ry(yawDeg - tilt.yaw), apply(rx(pitchDeg), rest))) : apply(ry(yawDeg), apply(rx(pitchDeg), rest));
115
+ const up = tilt ? apply(tilt.R, [0, 1, 0]) : [0, 1, 0];
116
+ // level the sole: the foot's own carried up vector goes to world up (no roll from the leg IK)
117
+ rotateSubtree(sk, s, foot, s.P[foot].slice(), align(sub(s.P[toe], s.P[foot]), carried(sk, s, foot, [0, 1, 0]), want, up));
118
+ // Toe-off: bend the toe joint so the toes stay flat on the ground while the heel rises.
119
+ const tip = sk.index[`${side}Toe_End`];
120
+ if (!tilt && pitchDeg > 0 && tip !== undefined) {
121
+ const seg = sub(s.P[tip], s.P[toe]), flat = [seg[0], 0, seg[2]];
122
+ if (len(flat) > 1e-6) rotateSubtree(sk, s, toe, s.P[toe].slice(), align(seg, [0, 1, 0], flat, [0, 1, 0]));
123
+ }
124
+ }
125
+
126
+ export function palmAxes(sk, s, side) {
127
+ const h = sk.index[`${side}Hand`], m1 = sk.index[`${side}HandMiddle1`];
128
+ const d = norm(sub(s.P[m1], s.P[h]));
129
+ let l = sub(s.P[sk.index[`${side}HandIndex1`]], s.P[sk.index[`${side}HandPinky1`]]);
130
+ l = norm(sub(l, scale(d, dot(l, d))));
131
+ return { d, l, n: cross(d, l) };
132
+ }
133
+
134
+ /** Turn the elbow `deg` about the shoulder-wrist line: the hand keeps its place and its world turn. */
135
+ export function swivelElbow(sk, s, side, deg) {
136
+ const a = sk.index[`${side}Arm`], h = sk.index[`${side}Hand`], R = axisRot(sub(s.P[h], s.P[a]), deg);
137
+ rotateSubtree(sk, s, a, s.P[a].slice(), R);
138
+ rotateSubtree(sk, s, h, s.P[h].slice(), T(R));
139
+ }
140
+
141
+ /** Wrist twist (about the forearm) and flex (about the knuckle line), degrees, clamped. */
142
+ export function wrist(sk, s, side, twistDeg, flexDeg) {
143
+ if (!twistDeg && !flexDeg) return;
144
+ const h = sk.index[`${side}Hand`], fa = sk.index[`${side}ForeArm`];
145
+ const axT = norm(sub(s.P[h], s.P[fa]));
146
+ const { l } = palmAxes(sk, s, side);
147
+ const axF = norm(sub(l, scale(axT, dot(l, axT))));
148
+ rotateSubtree(sk, s, h, s.P[h].slice(), axisRot(axF, clamp(flexDeg, -70, 70)));
149
+ rotateSubtree(sk, s, h, s.P[h].slice(), axisRot(axT, clamp(twistDeg, -85, 85)));
150
+ }
151
+
152
+ // Finger curl presets, degrees per phalanx (1, 2, 3); thumb is [opposition, curl, curl]. A fist and a grip keep the
153
+ // relaxed thumb here and close it with THUMB_WRAP: these thumb curls turn about the knuckle line and the hand's long axis,
154
+ // which only swings a thumb out to the side (a fist read as a thumbs-up).
155
+ const FINGER_PRESETS = {
156
+ relaxed: { Index: [12, 18, 10], Middle: [15, 20, 12], Ring: [18, 22, 12], Pinky: [20, 24, 14], Thumb: [15, 10, 8] },
157
+ fist: { Index: [80, 95, 60], Middle: [85, 95, 60], Ring: [85, 95, 60], Pinky: [85, 95, 60], Thumb: [15, 10, 8] },
158
+ grip: { Index: [55, 60, 35], Middle: [60, 62, 35], Ring: [62, 62, 35], Pinky: [65, 62, 35], Thumb: [15, 10, 8] },
159
+ cigarette: { Index: [8, 10, 6], Middle: [10, 12, 8], Ring: [60, 75, 45], Pinky: [65, 80, 45], Thumb: [35, 25, 20] },
160
+ point: { Index: [0, 0, 0], Middle: [80, 95, 60], Ring: [85, 95, 60], Pinky: [85, 95, 60], Thumb: [40, 30, 30] },
161
+ };
162
+
163
+ /** Blend presets by weight over `relaxed` and curl every finger toward the palm. */
164
+ export function fingers(sk, s, side, weights) {
165
+ const base = FINGER_PRESETS.relaxed, sign = side === "Left" ? -1 : 1;
166
+ const curl = (f, k) => base[f][k] + Object.entries(weights ?? {}).reduce((acc, [p, w]) => acc + (w || 0) * ((FINGER_PRESETS[p]?.[f]?.[k] ?? base[f][k]) - base[f][k]), 0);
167
+ for (const f of ["Index", "Middle", "Ring", "Pinky", "Thumb"]) {
168
+ for (let k = 0; k < 3; k++) {
169
+ const j = sk.index[`${side}Hand${f}${k + 1}`];
170
+ if (j === undefined) continue;
171
+ const { l, d } = palmAxes(sk, s, side);
172
+ const ax = f === "Thumb" && k === 0 ? d : l;
173
+ rotateSubtree(sk, s, j, s.P[j].slice(), axisRot(ax, sign * curl(f, k)));
174
+ }
175
+ }
176
+ const wrap = [0, 1, 2, 3].map(i => Object.entries(weights ?? {}).reduce((acc, [p, w]) => acc + (w || 0) * (THUMB_WRAP[p]?.[i] ?? 0), 0));
177
+ if (wrap.some(Boolean)) wrapThumb(sk, s, side, wrap);
178
+ }
179
+
180
+ // A closing hand's thumb, by preset weight after the relaxed one, degrees: turned in about the hand's long axis toward the
181
+ // palm and across it, then flexed at its two joints about their own axes (bone x palm normal). The fist's thumb lies over
182
+ // the index and middle fingers' middle phalanges; the grip's closes around a handle against the index finger. Searched on
183
+ // both rigs within the joints' ranges (thumb tip to the index's middle phalanx: fist 8.4 -> 3.7 cm Y Bot, 6.9 -> 2.4 X Bot).
184
+ const THUMB_WRAP = { fist: [-70, -5, 5, 80], grip: [-30, -30, 30, 10] };
185
+ function wrapThumb(sk, s, side, [oppose, across, flex2, flex3]) {
186
+ const sign = side === "Left" ? -1 : 1, J = k => sk.index[`${side}HandThumb${k}`];
187
+ if (J(1) === undefined) return;
188
+ const { d, n } = palmAxes(sk, s, side);
189
+ rotateSubtree(sk, s, J(1), s.P[J(1)].slice(), axisRot(d, sign * oppose));
190
+ // n = d x l flips with the mirror, so the turn across the palm keeps its sign on both hands
191
+ rotateSubtree(sk, s, J(1), s.P[J(1)].slice(), axisRot(n, across));
192
+ for (const [k, deg] of [[2, flex2], [3, flex3]]) {
193
+ const ax = norm(cross(norm(sub(s.P[J(k + 1)], s.P[J(k)])), n));
194
+ rotateSubtree(sk, s, J(k), s.P[J(k)].slice(), axisRot(ax, sign * deg));
195
+ }
196
+ }
197
+
@@ -0,0 +1,72 @@
1
+ // Small dependency-free vector / 3x3 / quaternion helpers for the motion3 compiler.
2
+ // Vectors are [x, y, z]; matrices are row-major [[...],[...],[...]] acting on column
3
+ // vectors (R v); quaternions are three.js order [x, y, z, w].
4
+
5
+ export const add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
6
+ export const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
7
+ export const scale = (a, s) => [a[0] * s, a[1] * s, a[2] * s];
8
+ export const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
9
+ export const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
10
+ export const len = a => Math.hypot(a[0], a[1], a[2]);
11
+ export const dist = (a, b) => len(sub(a, b));
12
+ export function norm(a) {
13
+ const l = len(a);
14
+ return l > 1e-12 ? [a[0] / l, a[1] / l, a[2] / l] : [0, 0, 0];
15
+ }
16
+ export const clamp = (x, lo, hi) => Math.min(hi, Math.max(lo, x));
17
+ const rad = d => d * Math.PI / 180;
18
+ const deg = r => r * 180 / Math.PI;
19
+
20
+ export const I3 = () => [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
21
+ export function mul(A, B) {
22
+ const C = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
23
+ for (let i = 0; i < 3; i++) for (let j = 0; j < 3; j++) C[i][j] = A[i][0] * B[0][j] + A[i][1] * B[1][j] + A[i][2] * B[2][j];
24
+ return C;
25
+ }
26
+ export const apply = (R, v) => [R[0][0] * v[0] + R[0][1] * v[1] + R[0][2] * v[2], R[1][0] * v[0] + R[1][1] * v[1] + R[1][2] * v[2], R[2][0] * v[0] + R[2][1] * v[1] + R[2][2] * v[2]];
27
+ export const T = R => [[R[0][0], R[1][0], R[2][0]], [R[0][1], R[1][1], R[2][1]], [R[0][2], R[1][2], R[2][2]]];
28
+ const cols = (a, b, c) => [[a[0], b[0], c[0]], [a[1], b[1], c[1]], [a[2], b[2], c[2]]];
29
+
30
+ export function rx(d) { const t = rad(d), c = Math.cos(t), s = Math.sin(t); return [[1, 0, 0], [0, c, -s], [0, s, c]]; }
31
+ export function ry(d) { const t = rad(d), c = Math.cos(t), s = Math.sin(t); return [[c, 0, s], [0, 1, 0], [-s, 0, c]]; }
32
+ export function rz(d) { const t = rad(d), c = Math.cos(t), s = Math.sin(t); return [[c, -s, 0], [s, c, 0], [0, 0, 1]]; }
33
+
34
+ /** Rotation by `d` degrees about unit-normalised axis `ax` (Rodrigues). */
35
+ export function axisRot(ax, d) {
36
+ const [x, y, z] = norm(ax), t = rad(d), c = Math.cos(t), s = Math.sin(t), k = 1 - c;
37
+ return [[c + x * x * k, x * y * k - z * s, x * z * k + y * s], [y * x * k + z * s, c + y * y * k, y * z * k - x * s], [z * x * k - y * s, z * y * k + x * s, c + z * z * k]];
38
+ }
39
+
40
+ /** Orthonormal frame whose first column is d and second lies toward p. */
41
+ function frame(d, p) {
42
+ const a = norm(d);
43
+ let b = sub(p, scale(a, dot(p, a)));
44
+ if (len(b) < 1e-9) b = sub([0, 1, 0], scale(a, a[1])), len(b) < 1e-9 && (b = sub([1, 0, 0], scale(a, a[0])));
45
+ b = norm(b);
46
+ return cols(a, b, cross(a, b));
47
+ }
48
+
49
+ /** Rotation taking frame (d0, p0) onto frame (d1, p1). */
50
+ export const align = (d0, p0, d1, p1) => mul(frame(d1, p1), T(frame(d0, p0)));
51
+
52
+ export function quatFromMat(R) {
53
+ const m00 = R[0][0], m11 = R[1][1], m22 = R[2][2], tr = m00 + m11 + m22;
54
+ let x, y, z, w;
55
+ if (tr > 0) { const s = 0.5 / Math.sqrt(tr + 1); w = 0.25 / s; x = (R[2][1] - R[1][2]) * s; y = (R[0][2] - R[2][0]) * s; z = (R[1][0] - R[0][1]) * s; }
56
+ else if (m00 > m11 && m00 > m22) { const s = 2 * Math.sqrt(1 + m00 - m11 - m22); w = (R[2][1] - R[1][2]) / s; x = 0.25 * s; y = (R[0][1] + R[1][0]) / s; z = (R[0][2] + R[2][0]) / s; }
57
+ else if (m11 > m22) { const s = 2 * Math.sqrt(1 + m11 - m00 - m22); w = (R[0][2] - R[2][0]) / s; x = (R[0][1] + R[1][0]) / s; y = 0.25 * s; z = (R[1][2] + R[2][1]) / s; }
58
+ else { const s = 2 * Math.sqrt(1 + m22 - m00 - m11); w = (R[1][0] - R[0][1]) / s; x = (R[0][2] + R[2][0]) / s; y = (R[1][2] + R[2][1]) / s; z = 0.25 * s; }
59
+ const l = Math.hypot(x, y, z, w);
60
+ return [x / l, y / l, z / l, w / l];
61
+ }
62
+
63
+ export function matFromQuat([x, y, z, w]) {
64
+ return [
65
+ [1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)],
66
+ [2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)],
67
+ [2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)],
68
+ ];
69
+ }
70
+
71
+ /** Angle in degrees between two directions. */
72
+ export const angle = (a, b) => deg(Math.acos(clamp(dot(norm(a), norm(b)), -1, 1)));
@@ -0,0 +1,177 @@
1
+ // One frame of the body: channel values -> world-space bone state.
2
+ // Conventions (CozyClay): metres, +Y up, yaw 0 faces +Z, positive yaw turns toward +X,
3
+ // the character's left is +X at yaw 0. Body-frame vectors (poles, body-local targets) are
4
+ // rotated by the root yaw only.
5
+ import { add, sub, scale, norm, len, apply, mul, rx, ry, rz } from "./math.js";
6
+ import { restState, rotateSubtree } from "./skeleton.js";
7
+ import { twoBone, orientFoot, wrist, fingers, limitFold, askedFold, swivelElbow, ELBOW_FOLD, KNEE_FOLD } from "./limbs.js";
8
+ import { holdHand } from "./hold.js";
9
+ import { clampWrist } from "./wrist-clamp.js";
10
+ import { ELBOW_REST } from "./arm-orient.js";
11
+ import { wristBody, bodyDistance } from "../body.js";
12
+
13
+ export const SIDES = { l: "Left", r: "Right" };
14
+ /** How much of an auto-fix elbow turn an arm at IK weight w takes (fix/self.js plans with the same). */
15
+ export const turnWeight = w => { const x = Math.min(1, Math.max(0, w)); return x * x * (3 - 2 * x); };
16
+ const KNEE_REST = [0, 0, 1], LEG = ["UpLeg", "Leg", "Foot"];
17
+
18
+ const v3 = (x, d = [0, 0, 0]) => (Array.isArray(x) || ArrayBuffer.isView(x) ? [x[0], x[1], x[2]] : d);
19
+ const num = (x, d = 0) => (typeof x === "number" && Number.isFinite(x) ? x : d);
20
+
21
+ /** Split a body-frame rotation over a chain of bones, each turning about its own joint. */
22
+ function spread(sk, s, bones, yaw, pitch, roll) {
23
+ const k = bones.length;
24
+ for (const name of bones) {
25
+ const b = sk.index[name];
26
+ rotateSubtree(sk, s, b, s.P[b].slice(), mul(ry(yaw / k), mul(rx(pitch / k), rz(roll / k))));
27
+ }
28
+ }
29
+
30
+ // Soft reach (spec.fix.reach): a wrist target up to 5 cm beyond the arm's reach is pulled onto
31
+ // the reach sphere through a C1 soft knee, so the arm never snaps straight; the overshoot is
32
+ // recorded. Farther targets are staging errors and stay for QA (REACH).
33
+ const SOFT_KNEE = 0.99, SOFT_MAX = 0.9995;
34
+ export const REACH_FIX_MAX = 0.05;
35
+ function softReach(sk, s, a, fa, h, target, diag, k) {
36
+ const R = len(sub(s.P[fa], s.P[a])) + len(sub(s.P[h], s.P[fa])), v = sub(target, s.P[a]), d = len(v);
37
+ const k0 = SOFT_KNEE * R, w = (SOFT_MAX - SOFT_KNEE) * R;
38
+ if (d <= k0 || d - SOFT_MAX * R > REACH_FIX_MAX) return target;
39
+ const dc = k0 + w * (1 - Math.exp(-(d - k0) / w));
40
+ diag[`reach_over_${k}`] = Math.max(0, d - SOFT_MAX * R);
41
+ return add(s.P[a], scale(v, dc / d));
42
+ }
43
+
44
+ /**
45
+ * hooks.handTarget(side, s) -> { target:[x,y,z] } world wrist target before the FK/IK blend
46
+ * hooks.afterArm(side, s, diag) lets the caller measure props carried by that hand.
47
+ * hooks.push(side) -> [x,y,z] | undefined, a world offset added to the IK target (auto-fix); hooks.skinPush(side) the same
48
+ * for the self fix (fix/self.js), taken as far as the arm is on its IK.
49
+ * hooks.softReach clamps unreachable wrist targets smoothly (auto-fix).
50
+ * hooks.foldFree lets the arm IK fold past the elbow's stop (a fit's measuring passes, compile.js).
51
+ * hooks.hold(side, s) -> { w, palm, measure, pole?, second? } | null: a held object's keys lead the hand (hold.js).
52
+ * hooks.keepOut(side, s, target) -> target: the blended wrist target kept out of the body (auto-fix, compile.js).
53
+ * hooks.swivel(side) -> deg | undefined, an elbow turn about the shoulder-wrist line, the hand kept (auto-fix, fix/self.js).
54
+ * diag.arm_fold_<k>: the flexion (deg) the wrist target asked when past ELBOW_FOLD (arm_miss_<k> is then how far inside).
55
+ * diag.leg_fold_<k>: the knee flexion (deg) the ankle target asked when past KNEE_FOLD (leg_miss_<k> is then how far inside).
56
+ */
57
+ // arm_hang (keyed by standIdle and the walk): a free FK forearm hangs plumb from the elbow, as a person's does,
58
+ // instead of carrying on the upper arm's slant out from the body (the hands sat about 9 cm wider than a person's);
59
+ // a working arm (on IK, or a hand that holds something: compile.js hangoff, idle.js busyHands) takes none of it. A rig whose thigh comes near (the X Bot) stops HANG_BACK
60
+ // degrees short of the first angle that brings its forearm or hand within HANG_CLEAR of the skin standing, so the
61
+ // walk's sway does not carry it into the thigh. Faded out as arm_rest raises the arms (none at 45); once per rig and rest.
62
+ const HANG_CLEAR = 0.095, HANG_BACK = 2, hangs = new Map();
63
+ function hangOf(sk, down) {
64
+ const key = `${sk.model}|${down}`;
65
+ if (hangs.has(key)) return hangs.get(key);
66
+ const full = (90 - down) * Math.min(1, Math.max(0, (down - 45) / 27)), wb = wristBody(sk);
67
+ const clearAt = h => {
68
+ const s = restState(sk);
69
+ return [["Left", -1], ["Right", 1]].every(([side, sg]) => {
70
+ const a = sk.index[`${side}Arm`], fa = sk.index[`${side}ForeArm`], hd = sk.index[`${side}Hand`], tip = sk.index[`${side}HandMiddle1`];
71
+ rotateSubtree(sk, s, a, s.P[a].slice(), rz(sg * down));
72
+ rotateSubtree(sk, s, fa, s.P[fa].slice(), rz(sg * h));
73
+ const pts = [0.5, 0.75, 1].map(t => add(s.P[fa], scale(sub(s.P[hd], s.P[fa]), t))).concat([s.P[tip]]);
74
+ return pts.every(p => bodyDistance(wb.body, s, p, wb.skip).d >= HANG_CLEAR);
75
+ });
76
+ };
77
+ let hang = full;
78
+ if (wb && full > 0) {
79
+ for (let h = 0; h <= full; h += 0.5) if (!clearAt(h)) { hang = Math.max(0, h - HANG_BACK); break; }
80
+ }
81
+ hangs.set(key, hang);
82
+ return hang;
83
+ }
84
+
85
+ /**
86
+ * The frame the knee and elbow poles and the feet are read in once the whole body turns well past upright (a flip, a
87
+ * cartwheel): the root's yaw, pitch and roll, the tilt eased in from TILT_FROM to TILT_FULL degrees (the larger of the
88
+ * wrapped pitch and roll). Under TILT_FROM it is null and the poles turn with the yaw only, as they always have, so an
89
+ * upright take is unchanged.
90
+ */
91
+ const TILT_FROM = 60, TILT_FULL = 120;
92
+ const wrap180 = a => a - 360 * Math.round(a / 360);
93
+ export function tiltFrame(yaw, pitch, roll) {
94
+ const p = wrap180(pitch), r = wrap180(roll), u = (Math.max(Math.abs(p), Math.abs(r)) - TILT_FROM) / (TILT_FULL - TILT_FROM);
95
+ if (u <= 0) return null;
96
+ const w = u >= 1 ? 1 : u * u * (3 - 2 * u);
97
+ return { yaw, R: mul(ry(yaw), mul(rx(p * w), rz(r * w))) };
98
+ }
99
+
100
+ export function pose(sk, c, hooks = {}) {
101
+ const s = restState(sk);
102
+ const diag = {};
103
+ // Base: arms down from the bind T-pose (body frame, before everything else).
104
+ const down = num(c.arm_rest, 72), hang = num(c.arm_hang) && num(c.arm_hang) * hangOf(sk, down);
105
+ for (const [side, sg, k] of [["Left", -1, "l"], ["Right", 1, "r"]]) {
106
+ const a = sk.index[`${side}Arm`], fa = sk.index[`${side}ForeArm`], h = hang * (1 - Math.min(1, Math.max(0, num(c[`armw_${k}`]) + num(c[`hangoff_${k}`]))));
107
+ rotateSubtree(sk, s, a, s.P[a].slice(), rz(sg * down));
108
+ if (h) rotateSubtree(sk, s, fa, s.P[fa].slice(), rz(sg * h));
109
+ }
110
+ // FK arm swing (forward +) and elbow bend, body frame.
111
+ for (const [k, side] of Object.entries(SIDES)) {
112
+ const bd = num(c[`abend_${k}`]), sw = num(c[`aswing_${k}`]);
113
+ const fa = sk.index[`${side}ForeArm`], a = sk.index[`${side}Arm`];
114
+ // an FK bend past the fold eases to its stop like the IK (limbs.js elbowFold)
115
+ if (bd) { rotateSubtree(sk, s, fa, s.P[fa].slice(), rx(-bd)); limitFold(sk, s, side); }
116
+ if (sw) rotateSubtree(sk, s, a, s.P[a].slice(), rx(-sw));
117
+ }
118
+ spread(sk, s, ["Neck", "Head"], num(c.head_yaw), num(c.head_pitch), num(c.head_roll));
119
+ spread(sk, s, ["Spine", "Spine1", "Spine2"], num(c.spine_yaw), num(c.spine_pitch), num(c.spine_roll));
120
+ // Root: turn about the hips joint, then put the hips at `root`.
121
+ const hips = sk.index.Hips, yaw = num(c.yaw);
122
+ rotateSubtree(sk, s, hips, s.P[hips].slice(), mul(ry(yaw), mul(rx(num(c.pitch)), rz(num(c.roll)))));
123
+ const shift = sub(v3(c.root, s.P[hips]), s.P[hips]);
124
+ for (let j = 0; j < s.P.length; j++) s.P[j] = add(s.P[j], shift);
125
+ const tilt = tiltFrame(yaw, num(c.pitch), num(c.roll)), body = tilt?.R ?? ry(yaw);
126
+ // Legs: two-bone IK to the ankle targets, the knee no further folded than its stop (limbs.js KNEE_STOP), then aim the feet.
127
+ for (const [k, side] of Object.entries(SIDES)) {
128
+ if (!c[`foot_${k}`]) continue;
129
+ const target = v3(c[`foot_${k}`]);
130
+ diag[`leg_miss_${k}`] = twoBone(sk, s, sk.index[`${side}UpLeg`], sk.index[`${side}Leg`], sk.index[`${side}Foot`],
131
+ target, apply(body, v3(c[`knee_${k}`], KNEE_REST)), KNEE_REST, "knee");
132
+ // a target nearer the hip than the knee folds leaves a residual (leg_miss); leg_fold says what it asked
133
+ const fold = askedFold(sk, s, side, target, LEG);
134
+ if (fold > KNEE_FOLD) diag[`leg_fold_${k}`] = fold;
135
+ orientFoot(sk, s, side, num(c[`fyaw_${k}`], yaw), num(c[`fpitch_${k}`]), tilt);
136
+ }
137
+ // Arms: blend the FK hand toward the hook's target, poles blended the same way.
138
+ for (const k of hooks.armOrder ?? ["r", "l"]) {
139
+ const side = SIDES[k], w = num(c[`armw_${k}`]);
140
+ const a = sk.index[`${side}Arm`], fa = sk.index[`${side}ForeArm`], h = sk.index[`${side}Hand`];
141
+ const poleIk = norm(apply(body, v3(c[`elbow_${k}`], [0, -1, -0.3])));
142
+ diag[`arm_miss_${k}`] = 0;
143
+ if (w > 1e-4 && hooks.handTarget) {
144
+ const fkHand = s.P[h].slice();
145
+ const goal = hooks.handTarget(k, s, diag) ?? fkHand;
146
+ let target = add(fkHand, scale(sub(goal, fkHand), w));
147
+ if (hooks.keepOut) target = hooks.keepOut(k, s, target);
148
+ const push = hooks.push?.(k);
149
+ if (push) target = add(target, push);
150
+ // the self fix's push rides the IK as far as the arm is on it (an arm coming in from FK would jump)
151
+ const skinPush = hooks.skinPush?.(k);
152
+ if (skinPush) target = add(target, scale(skinPush, turnWeight(w)));
153
+ if (hooks.softReach) target = softReach(sk, s, a, fa, h, target, diag, k);
154
+ const poleFk = norm(sub(s.P[fa], scale(add(s.P[a], fkHand), 0.5)));
155
+ const pole = add(scale(poleFk, 1 - w), scale(poleIk, w));
156
+ // a target nearer the shoulder than the elbow folds leaves a residual too; arm_fold says what it asked
157
+ diag[`arm_miss_${k}`] = twoBone(sk, s, a, fa, h, target, pole, ELBOW_REST, hooks.foldFree ? null : "elbow");
158
+ const fold = askedFold(sk, s, side, target);
159
+ if (fold > ELBOW_FOLD && !hooks.foldFree) diag[`arm_fold_${k}`] = fold;
160
+ }
161
+ wrist(sk, s, side, num(c[`wtwist_${k}`]), num(c[`wflex_${k}`]));
162
+ fingers(sk, s, side, c[`fingers_${k}`]);
163
+ const held = hooks.hold?.(k, s);
164
+ // a keyed carry's hand follows its object; else a fit's hand takes a grip on the body under its anchor (grip.js),
165
+ // and while the keys only ease in (before the first key) the arm they ease from takes it too
166
+ if (!held || held.w < 1) hooks.grip?.(k, s, diag);
167
+ if (held) holdHand(sk, s, side, held, ELBOW_REST, diag, k);
168
+ // after a held object's grip, so its hand keeps the grip the hold solved; an arm coming in from FK turns
169
+ // no more than its IK weight (the IK elbow already lands somewhere else than the FK one)
170
+ const turn = hooks.swivel?.(k);
171
+ // the turn keeps the hand's world turn; a wrist it takes past its limits stops there and the hand turns with the elbow
172
+ // (wrist-clamp.js), unless keys lead what the hand holds: that hand keeps the object's grip (hold.js limits its wrist)
173
+ if (turn) { swivelElbow(sk, s, side, turn * turnWeight(w)); if (!held) clampWrist(sk, s, side); }
174
+ hooks.afterArm?.(k, s, diag);
175
+ }
176
+ return { s, diag };
177
+ }
@@ -0,0 +1,49 @@
1
+ // Quaternion helpers for the held-object keys (../object-keys.js) and the hand-from-object solve
2
+ // (hold.js). Quaternions are three.js order [x, y, z, w], as math.js quatFromMat / matFromQuat;
3
+ // a rotation vector is axis * radians.
4
+
5
+ export const qmul = (a, b) => [
6
+ a[3] * b[0] + a[0] * b[3] + a[1] * b[2] - a[2] * b[1],
7
+ a[3] * b[1] - a[0] * b[2] + a[1] * b[3] + a[2] * b[0],
8
+ a[3] * b[2] + a[0] * b[1] - a[1] * b[0] + a[2] * b[3],
9
+ a[3] * b[3] - a[0] * b[0] - a[1] * b[1] - a[2] * b[2],
10
+ ];
11
+ export const qconj = q => [-q[0], -q[1], -q[2], q[3]];
12
+ export const qdot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
13
+ export function qnorm(q) {
14
+ const l = Math.hypot(q[0], q[1], q[2], q[3]);
15
+ return l > 1e-12 ? [q[0] / l, q[1] / l, q[2] / l, q[3] / l] : [0, 0, 0, 1];
16
+ }
17
+
18
+ /** Rotation vector of unit q, the short way round (|v| <= pi). */
19
+ export function qlog(q) {
20
+ const sg = q[3] < 0 ? -1 : 1, x = q[0] * sg, y = q[1] * sg, z = q[2] * sg, w = q[3] * sg, s = Math.hypot(x, y, z);
21
+ if (s < 1e-12) return [2 * x, 2 * y, 2 * z];
22
+ const a = 2 * Math.atan2(s, w) / s;
23
+ return [x * a, y * a, z * a];
24
+ }
25
+
26
+ /** Unit quaternion of rotation vector v. */
27
+ export function qexp(v) {
28
+ const a = Math.hypot(v[0], v[1], v[2]);
29
+ if (a < 1e-12) return qnorm([v[0] / 2, v[1] / 2, v[2] / 2, 1]);
30
+ const s = Math.sin(a / 2) / a;
31
+ return [v[0] * s, v[1] * s, v[2] * s, Math.cos(a / 2)];
32
+ }
33
+
34
+ /** Rotate vector v by unit q. */
35
+ export const qrot = (q, v) => qmul(qmul(q, [v[0], v[1], v[2], 0]), qconj(q)).slice(0, 3);
36
+
37
+ /** The shortest turn taking unit direction a onto unit direction b (half a turn about a normal of a when they are opposite). */
38
+ export function arcQuat(a, b) {
39
+ const w = 1 + a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
40
+ if (w > 1e-9) return qnorm([a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], w]);
41
+ const n = Math.abs(a[0]) < 0.9 ? [0, a[2], -a[1]] : [-a[2], 0, a[0]], l = Math.hypot(...n);
42
+ return [n[0] / l, n[1] / l, n[2] / l, 0];
43
+ }
44
+
45
+ /** The turn by `deg` degrees about unit axis `u` (right-handed). */
46
+ export const axisQuat = (u, deg) => { const h = deg * Math.PI / 360, s = Math.sin(h); return [u[0] * s, u[1] * s, u[2] * s, Math.cos(h)]; };
47
+
48
+ /** a -> b by t along the short arc (t = 0: a, t = 1: b). */
49
+ export const slerp = (a, b, t) => qmul(a, qexp(qlog(qmul(qconj(a), b)).map(c => c * t)));
@@ -0,0 +1,74 @@
1
+ // Rig skeleton for the motion3 compiler: bind data from src/motion3/rigs/<model>.json
2
+ // (extracted by tools/motion3/extract-rig.mjs), in METRES, rig root at the origin
3
+ // facing +Z with +X on the character's left.
4
+ import { apply, add, sub, mul, T, matFromQuat, quatFromMat } from "./math.js";
5
+
6
+ const CM = 0.01;
7
+
8
+ export function createSkeleton(rigJson) {
9
+ const bones = rigJson.bones;
10
+ const names = bones.map(b => b.name);
11
+ const index = Object.fromEntries(names.map((n, i) => [n, i]));
12
+ const parent = bones.map(b => b.parent);
13
+ const children = names.map(() => []);
14
+ parent.forEach((p, i) => { if (p >= 0) children[p].push(i); });
15
+ // subtree[i] = i and every descendant, parents before children
16
+ const subtree = names.map((_, i) => {
17
+ const out = [], stack = [i];
18
+ while (stack.length) { const j = stack.shift(); out.push(j); stack.unshift(...children[j]); }
19
+ return out;
20
+ });
21
+ const restLocalPos = bones.map(b => b.localPos.map(v => v * CM));
22
+ const restLocalRot = bones.map(b => matFromQuat(b.localQuat));
23
+ const restWorldPos = bones.map(b => b.worldPos.map(v => v * CM));
24
+ const restWorldRot = bones.map(b => matFromQuat(b.worldQuat));
25
+ const L = rigJson.landmarks;
26
+ const head = index.Head;
27
+ // Landmarks re-expressed in the Head bone's rest frame, so they ride the head.
28
+ const toHead = p => apply(T(restWorldRot[head]), sub(p.map(v => v * CM), restWorldPos[head]));
29
+ const landmarks = {
30
+ mouth: toHead(L.mouth),
31
+ headCentre: toHead(L.headEllipsoid.centre),
32
+ headRadii: L.headEllipsoid.radii.map(v => v * CM),
33
+ // how far the skin's sole lies below the ToeBase / Toe_End joints at bind (m; qa/geometry.js solePoints)
34
+ sole: { ball: (L.sole?.ball ?? 0) * CM, tip: (L.sole?.tip ?? 0) * CM },
35
+ };
36
+ return { model: rigJson.model, names, index, parent, children, subtree, restLocalPos, restLocalRot, restWorldPos, restWorldRot, landmarks };
37
+ }
38
+
39
+ /** Rest pose as a mutable world-space state { P, R }. */
40
+ export function restState(sk) {
41
+ return { P: sk.restWorldPos.map(p => p.slice()), R: sk.restWorldRot.map(r => r.map(row => row.slice())) };
42
+ }
43
+
44
+ /** Rigidly rotate bone `b`'s subtree by world rotation R about world point `pivot`. */
45
+ export function rotateSubtree(sk, s, b, pivot, R) {
46
+ for (const j of sk.subtree[b]) {
47
+ s.P[j] = add(pivot, apply(R, sub(s.P[j], pivot)));
48
+ s.R[j] = mul(R, s.R[j]);
49
+ }
50
+ }
51
+
52
+ /** World state -> per-bone LOCAL quaternions [x,y,z,w] plus the hips local position (m). */
53
+ export function toLocal(sk, s) {
54
+ const quats = sk.names.map((_, j) => {
55
+ const p = sk.parent[j];
56
+ return quatFromMat(p < 0 ? s.R[j] : mul(T(s.R[p]), s.R[j]));
57
+ });
58
+ return { quats, hips: s.P[sk.index.Hips].slice() };
59
+ }
60
+
61
+ /** Forward kinematics from local quaternions + hips position back to a world state. */
62
+ export function fk(sk, quats, hips) {
63
+ const P = [], R = [];
64
+ for (const j of sk.subtree[sk.index.Hips]) {
65
+ const p = sk.parent[j], Lr = matFromQuat(quats[j]);
66
+ if (p < 0) { R[j] = Lr; P[j] = hips.slice(); continue; }
67
+ R[j] = mul(R[p], Lr);
68
+ P[j] = add(P[p], apply(R[p], sk.restLocalPos[j]));
69
+ }
70
+ return { P, R };
71
+ }
72
+
73
+ /** Point fixed to a bone, given in that bone's own frame (e.g. a landmark). */
74
+ export const bonePoint = (s, b, local) => add(s.P[b], apply(s.R[b], local));
@@ -0,0 +1,32 @@
1
+ // The checks every part of the validator shares: a bone, a scene object, a check point.
2
+ // Messages are exact (the agent fixes them in one edit). See validate.js.
3
+ import { didYouMean } from "./vocab.js";
4
+ import { partsOf } from "../object-pose.js";
5
+
6
+ export function boneOk(err, sk, at, bone) {
7
+ if (!sk || sk.index[bone] !== undefined) return true;
8
+ err(`${at} names unknown bone "${bone}"${didYouMean(String(bone), sk.names)}`);
9
+ return false;
10
+ }
11
+
12
+ /**
13
+ * A scene object id. A group (an Empty: no box of its own) passes only with { groups: true }
14
+ * (carry, hinge, object: refs and object anchors take groups, see C3); elsewhere the message lists its parts.
15
+ */
16
+ export function objectOk(err, scene, at, id, { groups = false } = {}) {
17
+ const ids = Object.keys(scene?.objects ?? {});
18
+ if (!ids.length || ids.includes(id)) return true;
19
+ const parts = partsOf(scene, id);
20
+ if (parts.length && groups) return true;
21
+ if (parts.length) err(`${at} names "${id}", a group (Empty) with no body of its own; its parts are ${parts.join(", ")}. Name one part`);
22
+ else err(`${at} names object "${id}", which is not in the scene${didYouMean(String(id), [...new Set([...ids, ...ids.flatMap(k => scene.objects[k].ancestors ?? []), ...Object.keys(scene.others ?? {})])])}`);
23
+ return false;
24
+ }
25
+
26
+ export function refOk(spec, err, sk, at, ref) {
27
+ const [kind, rest] = String(ref).split(/:(.*)/s);
28
+ if (kind === "joint") return boneOk(err, sk, at, rest);
29
+ if (kind === "anchor") return spec.anchors?.[rest] ? true : err(`${at} "${ref}" names no anchor`);
30
+ if (kind === "prop") { const [p, pt] = (rest ?? "").split("."); return spec.props?.[p]?.points?.[pt] || pt === "origin" ? true : err(`${at} "${ref}" names no prop point`); }
31
+ err(`${at} "${ref}" must be joint:<Bone>, prop:<prop>.<point> or anchor:<name>`);
32
+ }