@woosh/meep-engine 3.14.3 → 3.14.5

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 (137) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/mat4/m4_rigidity_defect.d.ts +30 -0
  3. package/src/core/geom/3d/mat4/m4_rigidity_defect.d.ts.map +1 -0
  4. package/src/core/geom/3d/mat4/m4_rigidity_defect.js +71 -0
  5. package/src/engine/Engine.d.ts.map +1 -1
  6. package/src/engine/Engine.js +733 -726
  7. package/src/engine/network/NetworkSession.d.ts +24 -4
  8. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  9. package/src/engine/network/NetworkSession.js +28 -4
  10. package/src/engine/network/orchestrator/NetworkPeer.d.ts +96 -7
  11. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  12. package/src/engine/network/orchestrator/NetworkPeer.js +392 -68
  13. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +6 -1
  14. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
  15. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +6 -0
  16. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +105 -13
  17. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
  18. package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +306 -59
  19. package/src/engine/network/replication/Replicator.d.ts +43 -7
  20. package/src/engine/network/replication/Replicator.d.ts.map +1 -1
  21. package/src/engine/network/replication/Replicator.js +309 -111
  22. package/src/engine/network/sim/ActionLog.d.ts +23 -3
  23. package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
  24. package/src/engine/network/sim/ActionLog.js +26 -3
  25. package/src/engine/network/sim/ActionRecordCursor.d.ts +82 -0
  26. package/src/engine/network/sim/ActionRecordCursor.d.ts.map +1 -0
  27. package/src/engine/network/sim/ActionRecordCursor.js +120 -0
  28. package/src/engine/network/sim/RewindEngine.d.ts.map +1 -1
  29. package/src/engine/network/sim/RewindEngine.js +13 -29
  30. package/src/engine/network/sim/SimAction.d.ts +15 -0
  31. package/src/engine/network/sim/SimAction.d.ts.map +1 -1
  32. package/src/engine/network/sim/SimAction.js +15 -0
  33. package/src/engine/network/sim/SimActionExecutor.d.ts +51 -2
  34. package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
  35. package/src/engine/network/sim/SimActionExecutor.js +74 -7
  36. package/src/engine/network/transport/Channel.d.ts +1 -1
  37. package/src/engine/network/transport/Channel.js +1 -1
  38. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  39. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  40. package/src/engine/simulation/Ticker.js +4 -1
  41. package/src/engine/ui/GUIEngine.d.ts.map +1 -1
  42. package/src/engine/ui/GUIEngine.js +6 -0
  43. package/src/shade/playground/particle_system/README.md +98 -0
  44. package/src/shade/playground/particle_system/index.html +124 -0
  45. package/src/shade/playground/particle_system/main.d.ts +2 -0
  46. package/src/shade/playground/particle_system/main.d.ts.map +1 -0
  47. package/src/shade/playground/particle_system/main.js +180 -0
  48. package/src/shade/playground/particle_system/particle_scene.d.ts +52 -0
  49. package/src/shade/playground/particle_system/particle_scene.d.ts.map +1 -0
  50. package/src/shade/playground/particle_system/particle_scene.js +553 -0
  51. package/src/shade/playground/skinned_mesh_soup/README.md +143 -0
  52. package/src/shade/playground/skinned_mesh_soup/index.html +202 -0
  53. package/src/shade/playground/skinned_mesh_soup/main.d.ts +7 -0
  54. package/src/shade/playground/skinned_mesh_soup/main.d.ts.map +1 -0
  55. package/src/shade/playground/skinned_mesh_soup/main.js +567 -0
  56. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts +29 -0
  57. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts.map +1 -0
  58. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.js +428 -0
  59. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  60. package/src/shade/renderer/animation/GPUAnimationManager.js +1958 -1796
  61. package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +357 -0
  62. package/src/shade/renderer/animation/skin_bind_transform.d.ts +25 -0
  63. package/src/shade/renderer/animation/skin_bind_transform.d.ts.map +1 -0
  64. package/src/shade/renderer/animation/skin_bind_transform.js +33 -0
  65. package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts +67 -0
  66. package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts.map +1 -0
  67. package/src/shade/renderer/animation/skin_clip_scale_conflicts.js +199 -0
  68. package/src/shade/renderer/animation/skin_frame_defect.d.ts +34 -0
  69. package/src/shade/renderer/animation/skin_frame_defect.d.ts.map +1 -0
  70. package/src/shade/renderer/animation/skin_frame_defect.js +132 -0
  71. package/src/shade/renderer/animation/skin_joint_matrix.d.ts +24 -0
  72. package/src/shade/renderer/animation/skin_joint_matrix.d.ts.map +1 -0
  73. package/src/shade/renderer/animation/skin_joint_matrix.js +46 -0
  74. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts +70 -0
  75. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts.map +1 -0
  76. package/src/shade/renderer/animation/skin_normalize_mesh_frame.js +216 -0
  77. package/src/shade/renderer/animation/skin_test_fixtures.d.ts +86 -0
  78. package/src/shade/renderer/animation/skin_test_fixtures.d.ts.map +1 -0
  79. package/src/shade/renderer/animation/skin_test_fixtures.js +250 -0
  80. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts +18 -4
  81. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts.map +1 -1
  82. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +18 -4
  83. package/src/shade/renderer/particles/DESIGN.md +5 -5
  84. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +1 -1
  85. package/src/shade/renderer/particles/cull/chunk_particle_cull.js +3 -1
  86. package/src/shade/renderer/particles/data/chunk_particle_emitter.js +1 -1
  87. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +43 -0
  88. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -0
  89. package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +44 -0
  90. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +33 -59
  91. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  92. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +185 -118
  93. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts +15 -10
  94. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  95. package/src/shade/renderer/particles/graph/compile_particle_graph.js +205 -170
  96. package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts +44 -0
  97. package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts.map +1 -0
  98. package/src/shade/renderer/particles/graph/particle_graph_authoring.js +108 -0
  99. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +89 -0
  100. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -0
  101. package/src/shade/renderer/particles/isa/InstructionStream.js +162 -0
  102. package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts +1 -48
  103. package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts.map +1 -1
  104. package/src/shade/renderer/particles/isa/ParticleAssembler.js +25 -114
  105. package/src/shade/renderer/particles/particle_prototype.d.ts.map +1 -1
  106. package/src/shade/renderer/particles/particle_prototype.js +181 -180
  107. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +4 -4
  108. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
  109. package/src/shade/renderer/particles/runtime/create_particle_effect.js +19 -19
  110. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.d.ts.map +1 -1
  111. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.js +4 -2
  112. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +4 -4
  113. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts.map +1 -1
  114. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +7 -5
  115. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.d.ts.map +1 -1
  116. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.js +3 -1
  117. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  118. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +3 -1
  119. package/src/shade/renderer/particles/shaders/shader_particle_finalize.d.ts.map +1 -1
  120. package/src/shade/renderer/particles/shaders/shader_particle_finalize.js +5 -2
  121. package/src/shade/renderer/particles/shaders/shader_particle_render.d.ts.map +1 -1
  122. package/src/shade/renderer/particles/shaders/shader_particle_render.js +9 -2
  123. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  124. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +3 -1
  125. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +1 -1
  126. package/src/shade/renderer/scene/SkinnedMesh.d.ts +11 -0
  127. package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +1 -1
  128. package/src/shade/renderer/scene/SkinnedMesh.js +11 -0
  129. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.d.ts +33 -3
  130. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.d.ts.map +1 -1
  131. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.js +58 -37
  132. package/src/shade/renderer/particles/graph/ParticleGraph.d.ts +0 -54
  133. package/src/shade/renderer/particles/graph/ParticleGraph.d.ts.map +0 -1
  134. package/src/shade/renderer/particles/graph/ParticleGraph.js +0 -99
  135. package/src/shade/renderer/particles/prototypeParticleSystem.d.ts +0 -2
  136. package/src/shade/renderer/particles/prototypeParticleSystem.d.ts.map +0 -1
  137. package/src/shade/renderer/particles/prototypeParticleSystem.js +0 -448
@@ -0,0 +1,143 @@
1
+ # skinned mesh soup — repro
2
+
3
+ The character from `skinned_blas_refit`, loaded the way the game loads one, with the pieces that
4
+ could be making soup on switches.
5
+
6
+ ```bash
7
+ npm run dev
8
+ ```
9
+
10
+ then open `/src/shade/playground/skinned_mesh_soup/index.html`.
11
+
12
+ `?count=N` for N characters, `?skin=off|bind|animated`, `?trace=1` for the path tracer. Every switch
13
+ is in the url so a state worth showing someone can be sent as a link.
14
+
15
+ ## What it turned out to be
16
+
17
+ **`chunk_skin_blend_mesh_local` requires `inverse(mesh.global) × joint.global × inverse_bind` to be
18
+ rigid. For `Michelle.glb` it carries a uniform scale of exactly 100, and a dual quaternion cannot
19
+ carry a scale.**
20
+
21
+ The chunk's own doc comment states the precondition — *"the four products must be rigid, which holds
22
+ whenever the joints and the mesh node share the scaling ancestors"* — and names the shape it was
23
+ written for: an FBX-derived glTF that puts its unit conversion on a root node and authors the
24
+ inverse-bind matrices in **pre-scale** mesh space, so `joint.global × inverse_bind` carries the same
25
+ scale `mesh.global` does and the fold cancels it.
26
+
27
+ Michelle is the other legal shape. Her POSITION data and her inverse-bind matrices are authored in
28
+ the **already-scaled** space — 1.66 m tall, Y-up, as they stand — so
29
+
30
+ ```
31
+ joint.global × inverse_bind = I for every one of her 65 joints
32
+ mesh.global = 0.01 scale × the up-axis rotation, from her "Character" root
33
+ ```
34
+
35
+ and the fold is `inverse(mesh.global) × I`, a bare **100×**. Measured, at bind and under the dance:
36
+ every folded matrix's column length is `100.0000`, where rigid is 1.
37
+
38
+ `dual_quat_from_m4` builds its quaternion from `sqrt(max(0, 1 ± m00 ± m11 ± m22))`. Those terms are
39
+ `1 + 100·(…)`, so the `1` is swamped and every component whose sum goes negative is **clamped to
40
+ zero** by the `max`. The rotation it extracts is not the matrix's rotation, and how wrong it is
41
+ depends on the matrix — so it is wrong differently for every joint. That is the soup: not one
42
+ transform applied badly, but sixty-five of them disagreeing.
43
+
44
+ The two modes made the mechanism visible:
45
+
46
+ | `?skin=` | what it did | why |
47
+ | --- | --- | --- |
48
+ | `bind` | the right character, exactly 1/100 the size | every joint folds to the *same* matrix, `100·Rx(90°)`. Its `w` and `x` terms are both `1 + 100`, its `y` and `z` both clamp to zero, and the normalize recovers `Rx(90°)` — by luck. Only the scale is lost. Sampled output was `expected / 100` to the digit. |
49
+ | `animated` | soup | the joints' matrices differ, each loses a different amount, and the mesh comes apart. Mean error 121 in a mesh-local space where the body is 224 across; worst 165, on vertices bound to **a single joint at weight 1** — so the blend was not even involved. |
50
+
51
+ `__soup_blend_probe.spec.js` is that measurement: the WGSL of `dual_quat_from_m4`,
52
+ `dual_quat_blend4` and `dual_quat_transform_point` ported to JS, run over the character's own
53
+ matrices at bind and at `t = 0.5 s` of `SambaDance`, against the position the glTF formula puts each
54
+ vertex at. No device.
55
+
56
+ ### What it is not
57
+
58
+ `__soup_probe.spec.js` is the ruling-out, and every one of these passes on the parsed asset before a
59
+ GPU is involved:
60
+
61
+ - the geometry is a surface, not a soup — 28 106 triangles, no degenerates, longest edge 0.093
62
+ against a 2.24 diagonal, every meshlet index in range;
63
+ - the skinning attributes are sound — all 19 106 vertices land back on their rest position under
64
+ `joint.global × inverse_bind`, no zero-weight vertices, no out-of-range joint;
65
+ - the source BLAS is exact — 56 211 nodes all reachable, 28 106 leaves each naming a distinct
66
+ triangle whose box matches to the bit, all 28 105 internal nodes exactly their children's union.
67
+
68
+ So it is not the loader, not the meshlet build, not the tree, and not the refit. `skinned_blas_refit`'s
69
+ `verify()` cannot see this at all, and that is worth saying plainly: it holds the tree's root box
70
+ against the bounds chain's box, and **both are derived from the clone's vertex region**. Vertices
71
+ that are wrong in the same way agree with each other perfectly. A green `verify()` and a soup
72
+ picture are consistent.
73
+
74
+ ## What was done about it
75
+
76
+ `renderer/animation/SKINNING_FRAME_PLAN.md`. The short version: glTF says a skinned mesh's node
77
+ transform is ignored, so the frame is not the asset's to choose — it is the engine's.
78
+ `register_skin` now sets it to the skin's own bind transform (`skin_normalize_mesh_frame`), which
79
+ makes the fold the identity at bind and a rigid displacement under a clip, for **both** shapes a
80
+ glTF file arrives in. One matrix per skin, once, at registration. No shader change and no
81
+ per-vertex cost, and a file that was already right is not touched.
82
+
83
+ Same measurement, after:
84
+
85
+ | | folded scale | mean error | worst | worst on a single-joint vertex |
86
+ | --- | --- | --- | --- | --- |
87
+ | bind, mesh node as the file gave it | 100.0000 | 121.2470 | 165.4281 | 165.4281 |
88
+ | posed at 0.5 s, the same | 100.0000 | 121.6197 | 165.5332 | — |
89
+ | bind, on the skin frame | 1.0000 | 0.0002 | 0.0016 | 0.0014 |
90
+ | posed at 0.5 s, on the skin frame | 1.0000 | 0.0001 | 0.0062 | 0.000014 |
91
+ | bind, skin frame and §9 | 1.0000 | 0.0000 | 0.0000 | **0** |
92
+ | posed at 0.5 s, skin frame and §9 | 1.0000 | 0.0001 | 0.0062 | **0.000001** |
93
+
94
+ Neither residual was the frame. The blended ones are dual-quaternion skinning declining to be a
95
+ linear blend, which is what it is for. The `0.0014` at bind was `dual_quat_from_m4`'s square-root
96
+ conditioning near the identity — pre-existing, and maximally exposed by a fold that now lands every
97
+ joint *on* the identity. Plan §9 moved the conversion onto `quat_from_m3`'s branch-picking form,
98
+ which was already in the tree and used by nothing, and it went to zero.
99
+
100
+ Confirmed on an NVIDIA RTX 4090: the frame correction fires and logs, `?skin=bind` draws a full-size
101
+ character, `?skin=animated` dances, and §9's WGSL — which nothing offline type-checks — compiles.
102
+
103
+ So the modes now read the other way round:
104
+
105
+ | `?skin=` | what it should be |
106
+ | --- | --- |
107
+ | `off` | a 1.7 cm figure on her side — nothing deforms the clone, so the mesh node's own transform is all there is. Unchanged by the fix, and still the control for "is the geometry upload sound". |
108
+ | `bind` | a full-size character standing in bind pose, and `await diagnose()` clean |
109
+ | `animated` | dancing, with the traced and rasterized silhouettes the same silhouette |
110
+
111
+ ## The switches
112
+
113
+ - **`?skin=off`** — no skin registered. A `SkinnedMesh` still gets a vertex clone
114
+ (`GPUMeshSkinningContext#obtain_geometry_index` allocates one for every skinned mesh), but nothing
115
+ deforms it, so what is drawn is the memcpy of the source: the rest pose under the mesh node's own
116
+ transform. For Michelle a *correct* result is a 1.7 cm figure on her side. Soup here would be the
117
+ geometry upload — the meshlet copy or its address patch — and nothing to do with skinning.
118
+ - **`?skin=bind`** — the skin registered, no clip. `await diagnose()` in the console reads the
119
+ clone's vertices back off the GPU and holds them against `inverse(mesh.global) × rest`, which is
120
+ what the bind pose must produce. It reports which vertices are wrong and in what shape: a
121
+ vertex-slot range says the workgroup stride loop, whole meshlets say addressing, and a uniform
122
+ factor says this bug. It checks the clone's meshlet records address a region of their own first —
123
+ a clone still pointing at the source's vertex data would be deforming the shared rest pose in
124
+ place. Since the frame is now the skin's, `inverse(mesh.global)` is the identity here and the
125
+ clone should come back holding the source's own positions.
126
+ - **`?skin=animated`** — the file's dance, as `skinned_blas_refit` runs it.
127
+ - **Path tracer** — off by default. The rasterizer reads the deformed vertices directly, with no
128
+ tree in the way, so it is the reading to establish the answer on: soup in *both* is the vertices,
129
+ soup in the tracer alone is the tree.
130
+
131
+ ## Why through `GLTFSceneBundleAssetLoader`
132
+
133
+ `skinned_blas_refit` calls `load_gltf` directly and parses the file once per character. The asset
134
+ loader is what the game and the editor register, and it hands out **the same bundle** to every
135
+ caller — so a second character is not a second parse, it is `instantiate_scene_bundle`: its own node
136
+ tree, its own skins bound to that tree's joints, its own clips retargeted onto it, sharing the
137
+ geometry and the inverse-bind matrices. Running the repro on that path is what makes a fix found
138
+ here a fix for the engine rather than for one prototype page.
139
+
140
+ ## Assets
141
+
142
+ `/test_assets/Michelle.glb`, gitignored and unpublished — see `test_assets/README.md`. The two
143
+ probes skip themselves when it is absent.
@@ -0,0 +1,202 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>meep — skinned mesh soup</title>
6
+ <style>
7
+ :root {
8
+ color-scheme: dark;
9
+ --panel: rgba(20, 22, 26, 0.88);
10
+ --line: #2b3038;
11
+ --text: #d6dae0;
12
+ --muted: #8b939f;
13
+ --pass: #55c46a;
14
+ --fail: #ff6b6b;
15
+ --warn: #e5b54a;
16
+ }
17
+
18
+ * { box-sizing: border-box; }
19
+
20
+ html, body { margin: 0; height: 100%; overflow: hidden; background: #0d0f12; }
21
+
22
+ /* body carries tabindex so the camera controller's keyboard device has something focusable
23
+ to listen on; the focus ring around the whole viewport is not wanted */
24
+ body:focus { outline: none; }
25
+
26
+ #view { display: block; width: 100vw; height: 100vh; }
27
+
28
+ #panel {
29
+ position: fixed;
30
+ top: 12px;
31
+ left: 12px;
32
+ width: 340px;
33
+ padding: 12px 14px;
34
+ background: var(--panel);
35
+ border: 1px solid var(--line);
36
+ border-radius: 8px;
37
+ backdrop-filter: blur(6px);
38
+ color: var(--text);
39
+ font: 13px/1.5 system-ui, -apple-system, Segoe UI, sans-serif;
40
+ }
41
+
42
+ #panel h1 { font-size: 13px; font-weight: 600; margin: 0 0 8px; }
43
+
44
+ label { display: flex; align-items: center; gap: 8px; padding: 3px 0; cursor: pointer; }
45
+ label input { margin: 0; }
46
+ label .hint { color: var(--muted); font-size: 11.5px; }
47
+
48
+ hr { border: 0; border-top: 1px solid var(--line); margin: 10px 0; }
49
+
50
+ button, select, input[type=number] {
51
+ font: inherit;
52
+ padding: 5px 9px;
53
+ border-radius: 6px;
54
+ border: 1px solid var(--line);
55
+ background: #1c1f25;
56
+ color: var(--text);
57
+ }
58
+
59
+ button { cursor: pointer; }
60
+ button:hover:not(:disabled) { border-color: #3d4552; }
61
+ button:disabled { opacity: 0.45; cursor: default; }
62
+
63
+ select { width: 100%; cursor: pointer; }
64
+
65
+ .row { display: flex; gap: 6px; align-items: center; margin-top: 6px; }
66
+ .row button { flex: 0 0 auto; }
67
+
68
+ #character_count {
69
+ flex: 1 1 auto;
70
+ min-width: 0;
71
+ font: 12px ui-monospace, SFMono-Regular, Consolas, monospace;
72
+ background: #14161a;
73
+ }
74
+
75
+ #status { font-size: 12px; color: var(--muted); word-break: break-word; }
76
+ #status.pass { color: var(--pass); }
77
+ #status.fail { color: var(--fail); }
78
+ #status.warn { color: var(--warn); }
79
+
80
+ #stats {
81
+ margin-top: 6px;
82
+ font: 12px ui-monospace, SFMono-Regular, Consolas, monospace;
83
+ color: var(--text);
84
+ }
85
+
86
+ p.note { margin: 8px 0 0; font-size: 11.5px; color: var(--muted); }
87
+
88
+ code { font: 11.5px ui-monospace, SFMono-Regular, Consolas, monospace; color: var(--text); }
89
+
90
+ dl { margin: 6px 0 0; font-size: 11.5px; color: var(--muted); }
91
+ dt { color: var(--text); font-weight: 600; margin-top: 6px; }
92
+ dd { margin: 0; }
93
+ </style>
94
+ </head>
95
+ <body tabindex="0">
96
+
97
+ <canvas id="view"></canvas>
98
+
99
+ <div id="panel">
100
+ <h1>Skinned mesh soup</h1>
101
+
102
+ <p class="note">
103
+ The same character <code>skinned_blas_refit</code> draws, loaded through
104
+ <code>GLTFSceneBundleAssetLoader</code> — one bundle, one instance per placement — with the
105
+ pieces that could be making soup on switches.
106
+ </p>
107
+ <p class="note">
108
+ <strong>The answer, for the record.</strong> <code>chunk_skin_blend_mesh_local</code> needs
109
+ <code>inverse(mesh.global) × joint.global × inverse_bind</code> to be rigid; for this file
110
+ it carried a uniform <strong>100×</strong>, and a dual quaternion cannot carry a scale.
111
+ <code>register_skin</code> now puts the mesh on the skin's own bind transform, so the fold
112
+ is the identity at bind and a rigid displacement under a clip. The README and
113
+ <code>SKINNING_FRAME_PLAN.md</code> have the measurement; this page is where it is confirmed
114
+ on a device.
115
+ </p>
116
+
117
+ <hr>
118
+
119
+ <label for="skin_mode"><span>How the skin is driven</span></label>
120
+ <select id="skin_mode">
121
+ <option value="off">off — no skin registered, nothing deforms the clone</option>
122
+ <option value="bind">bind — skin registered, no clip</option>
123
+ <option value="animated">animated — the file's dance, playing</option>
124
+ </select>
125
+
126
+ <dl>
127
+ <dt>off</dt>
128
+ <dd>
129
+ What is drawn is the clone's memcpy of the source: the rest pose under the mesh node's
130
+ own transform. Michelle's carries the file's centimetre scale and its up-axis rotation,
131
+ so <em>correct</em> here is a 1.7 cm figure on her side. Soup here is the geometry
132
+ upload, not skinning.
133
+ </dd>
134
+ <dt>bind</dt>
135
+ <dd>
136
+ The skinning pass runs every frame over a pose that never moves — and that pose has an
137
+ answer the CPU knows exactly. <code>await diagnose()</code> reads the clone's vertices
138
+ back and says which are wrong, and whether the wrong ones are a vertex-slot range (the
139
+ workgroup stride loop) or whole meshlets (addressing). Should be a full-size character
140
+ standing still, and a clean report.
141
+ </dd>
142
+ <dt>animated</dt>
143
+ <dd>
144
+ Soup only in this mode is the pose: the joint matrices, their range, or the order the
145
+ skin's joints resolved in. Should be dancing, with the traced and rasterized silhouettes
146
+ the same silhouette.
147
+ </dd>
148
+ </dl>
149
+
150
+ <hr>
151
+
152
+ <label>
153
+ <input type="checkbox" id="path_tracing">
154
+ <span>Path tracer <span class="hint">— traverses the BLAS</span></span>
155
+ </label>
156
+ <p class="note">
157
+ Off by default. The rasterizer reads the deformed vertices directly, with no tree in the
158
+ way, so it is the reading to establish the answer on first: soup in <em>both</em> is the
159
+ vertices, soup in the tracer alone is the tree.
160
+ </p>
161
+
162
+ <div class="row">
163
+ <button id="animation">Freeze pose</button>
164
+ </div>
165
+
166
+ <hr>
167
+
168
+ <div class="row">
169
+ <input type="number" id="character_count" value="1" min="1" max="64" step="1">
170
+ <button id="respawn">Characters</button>
171
+ </div>
172
+ <p class="note">
173
+ One parse whatever the count — the asset hands out the model itself, and each placement gets
174
+ its own node tree, skins and clips from <code>instantiate_scene_bundle</code>.
175
+ </p>
176
+
177
+ <hr>
178
+
179
+ <div id="status"></div>
180
+ <div id="stats"></div>
181
+
182
+ <p class="note">
183
+ Every switch is in the url — <code>?count=1&amp;skin=bind&amp;trace=1</code> — so a state
184
+ worth showing someone can be sent as a link.
185
+ </p>
186
+ </div>
187
+
188
+ <script type="module">
189
+ import { start } from './main.js';
190
+
191
+ start().catch(error => {
192
+ const status = document.getElementById('status');
193
+
194
+ status.className = 'fail';
195
+ status.textContent = String(error?.message ?? error);
196
+
197
+ console.error(error);
198
+ });
199
+ </script>
200
+
201
+ </body>
202
+ </html>
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Stand the page up.
3
+ *
4
+ * @returns {Promise<void>}
5
+ */
6
+ export function start(): Promise<void>;
7
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/skinned_mesh_soup/main.js"],"names":[],"mappings":"AAiQA;;;;GAIG;AACH,yBAFa,QAAQ,IAAI,CAAC,CAkTzB"}