@woosh/meep-engine 3.14.4 → 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.
- package/package.json +1 -1
- package/src/core/geom/3d/mat4/m4_rigidity_defect.d.ts +30 -0
- package/src/core/geom/3d/mat4/m4_rigidity_defect.d.ts.map +1 -0
- package/src/core/geom/3d/mat4/m4_rigidity_defect.js +71 -0
- package/src/engine/network/orchestrator/NetworkPeer.d.ts +56 -8
- package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
- package/src/engine/network/orchestrator/NetworkPeer.js +263 -67
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +6 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +6 -0
- package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +13 -7
- package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +103 -138
- package/src/engine/network/replication/Replicator.d.ts +29 -6
- package/src/engine/network/replication/Replicator.d.ts.map +1 -1
- package/src/engine/network/replication/Replicator.js +265 -108
- package/src/engine/network/sim/ActionLog.d.ts +2 -0
- package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
- package/src/engine/network/sim/ActionLog.js +2 -0
- package/src/engine/network/sim/ActionRecordCursor.d.ts +82 -0
- package/src/engine/network/sim/ActionRecordCursor.d.ts.map +1 -0
- package/src/engine/network/sim/ActionRecordCursor.js +120 -0
- package/src/engine/network/sim/RewindEngine.d.ts.map +1 -1
- package/src/engine/network/sim/RewindEngine.js +13 -29
- package/src/engine/network/sim/SimActionExecutor.d.ts +2 -3
- package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
- package/src/engine/network/sim/SimActionExecutor.js +10 -11
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/playground/particle_system/particle_scene.d.ts.map +1 -1
- package/src/shade/playground/particle_system/particle_scene.js +38 -37
- package/src/shade/playground/skinned_mesh_soup/README.md +39 -18
- package/src/shade/playground/skinned_mesh_soup/index.html +9 -6
- package/src/shade/playground/skinned_mesh_soup/main.d.ts.map +1 -1
- package/src/shade/playground/skinned_mesh_soup/main.js +16 -13
- package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts +8 -2
- package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts.map +1 -1
- package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.js +23 -4
- package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
- package/src/shade/renderer/animation/GPUAnimationManager.js +1958 -1796
- package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +357 -0
- package/src/shade/renderer/animation/skin_bind_transform.d.ts +25 -0
- package/src/shade/renderer/animation/skin_bind_transform.d.ts.map +1 -0
- package/src/shade/renderer/animation/skin_bind_transform.js +33 -0
- package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts +67 -0
- package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts.map +1 -0
- package/src/shade/renderer/animation/skin_clip_scale_conflicts.js +199 -0
- package/src/shade/renderer/animation/skin_frame_defect.d.ts +34 -0
- package/src/shade/renderer/animation/skin_frame_defect.d.ts.map +1 -0
- package/src/shade/renderer/animation/skin_frame_defect.js +132 -0
- package/src/shade/renderer/animation/skin_joint_matrix.d.ts +24 -0
- package/src/shade/renderer/animation/skin_joint_matrix.d.ts.map +1 -0
- package/src/shade/renderer/animation/skin_joint_matrix.js +46 -0
- package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts +70 -0
- package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts.map +1 -0
- package/src/shade/renderer/animation/skin_normalize_mesh_frame.js +216 -0
- package/src/shade/renderer/animation/skin_test_fixtures.d.ts +86 -0
- package/src/shade/renderer/animation/skin_test_fixtures.d.ts.map +1 -0
- package/src/shade/renderer/animation/skin_test_fixtures.js +250 -0
- package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts +18 -4
- package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts.map +1 -1
- package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +18 -4
- package/src/shade/renderer/particles/DESIGN.md +5 -5
- package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +43 -0
- package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -0
- package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +44 -0
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +33 -59
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +185 -118
- package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts +15 -10
- package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/compile_particle_graph.js +205 -170
- package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts +44 -0
- package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts.map +1 -0
- package/src/shade/renderer/particles/graph/particle_graph_authoring.js +108 -0
- package/src/shade/renderer/particles/particle_prototype.d.ts.map +1 -1
- package/src/shade/renderer/particles/particle_prototype.js +181 -180
- package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +4 -4
- package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
- package/src/shade/renderer/particles/runtime/create_particle_effect.js +19 -19
- package/src/shade/renderer/particles/vm/chunk_particle_vm.js +1 -1
- package/src/shade/renderer/scene/SkinnedMesh.d.ts +11 -0
- package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +1 -1
- package/src/shade/renderer/scene/SkinnedMesh.js +11 -0
- package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.d.ts +33 -3
- package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.js +58 -37
- package/src/shade/renderer/particles/graph/ParticleGraph.d.ts +0 -54
- package/src/shade/renderer/particles/graph/ParticleGraph.d.ts.map +0 -1
- package/src/shade/renderer/particles/graph/ParticleGraph.js +0 -99
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forward cursor over the records of one closed {@link ActionLog} frame.
|
|
3
|
+
*
|
|
4
|
+
* The record layout is self-describing (see {@link ActionLog}), so any reader
|
|
5
|
+
* can walk it — and five did, each with its own copy of the header parse: the
|
|
6
|
+
* replicator's scope scan and action copy, the rewind engine's boundary pass,
|
|
7
|
+
* and the rollback server's history read and retransmission check. This is
|
|
8
|
+
* that parse in one place. {@link next} reads one record's header and exposes
|
|
9
|
+
* the layout as positions into the frame's buffer; what a reader does with the
|
|
10
|
+
* prior-state entries or the action payload is its own business.
|
|
11
|
+
*
|
|
12
|
+
* Allocation-free after construction: a reader owns one cursor and re-opens
|
|
13
|
+
* it per frame. After `next` returns true the buffer's `position` is at the
|
|
14
|
+
* END of the record, so a reader that wants the prior-state entries sets
|
|
15
|
+
* `position = prior_start` and reads `prior_count` of them; `next` seeks from
|
|
16
|
+
* `record_end` regardless of where the reader left the position.
|
|
17
|
+
*
|
|
18
|
+
* ```
|
|
19
|
+
* cursor.open(action_log, frame);
|
|
20
|
+
* while (cursor.next()) {
|
|
21
|
+
* // cursor.action_type_id, cursor.sender_id,
|
|
22
|
+
* // cursor.buffer.raw_bytes[cursor.payload_start .. + cursor.payload_len]
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @author Alex Goldring
|
|
27
|
+
* @copyright Company Named Limited (c) 2026
|
|
28
|
+
*/
|
|
29
|
+
export class ActionRecordCursor {
|
|
30
|
+
/**
|
|
31
|
+
* The open frame's buffer, positioned at {@link record_end} after each
|
|
32
|
+
* successful {@link next}.
|
|
33
|
+
* @type {BinaryBuffer|null}
|
|
34
|
+
*/
|
|
35
|
+
buffer = null;
|
|
36
|
+
|
|
37
|
+
/** @type {number} first byte of the current record */
|
|
38
|
+
record_start = 0;
|
|
39
|
+
|
|
40
|
+
/** @type {number} prior-state entries in the current record */
|
|
41
|
+
prior_count = 0;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* First byte of the first prior-state entry. Each entry is
|
|
45
|
+
* `varint entity_id, uint8 component_type_id, uint32 len, bytes[len]`.
|
|
46
|
+
* @type {number}
|
|
47
|
+
*/
|
|
48
|
+
prior_start = 0;
|
|
49
|
+
|
|
50
|
+
/** @type {number} */
|
|
51
|
+
action_type_id = 0;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The record's provenance byte: a peer id, `SENDER_LOCAL` or
|
|
55
|
+
* `SENDER_DERIVED` (see {@link SimActionExecutor}).
|
|
56
|
+
* @type {number}
|
|
57
|
+
*/
|
|
58
|
+
sender_id = 0;
|
|
59
|
+
|
|
60
|
+
/** @type {number} first byte of the action payload */
|
|
61
|
+
payload_start = 0;
|
|
62
|
+
|
|
63
|
+
/** @type {number} */
|
|
64
|
+
payload_len = 0;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* One past the last byte of the current record — and so the first byte of
|
|
68
|
+
* the next, which is where {@link next} resumes. `0` before the first
|
|
69
|
+
* record.
|
|
70
|
+
* @type {number}
|
|
71
|
+
*/
|
|
72
|
+
record_end = 0;
|
|
73
|
+
|
|
74
|
+
/** @type {number} one past the last byte of the frame */
|
|
75
|
+
#end = 0;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Position the cursor before the first record of `frame`, which must be
|
|
79
|
+
* closed and present in the log (see {@link ActionLog#buffer_for}).
|
|
80
|
+
*
|
|
81
|
+
* @param {ActionLog} action_log
|
|
82
|
+
* @param {number} frame
|
|
83
|
+
*/
|
|
84
|
+
open(action_log, frame) {
|
|
85
|
+
this.buffer = action_log.buffer_for(frame); // rewinds to 0
|
|
86
|
+
this.#end = action_log.write_end_for(frame);
|
|
87
|
+
this.record_end = 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Advance to the next record. Returns false once the frame is exhausted,
|
|
92
|
+
* leaving the fields as they were.
|
|
93
|
+
*
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*/
|
|
96
|
+
next() {
|
|
97
|
+
if (this.record_end >= this.#end) return false;
|
|
98
|
+
|
|
99
|
+
const buffer = this.buffer;
|
|
100
|
+
buffer.position = this.record_end;
|
|
101
|
+
|
|
102
|
+
this.record_start = buffer.position;
|
|
103
|
+
this.prior_count = buffer.readUintVar();
|
|
104
|
+
this.prior_start = buffer.position;
|
|
105
|
+
for (let i = 0; i < this.prior_count; i++) {
|
|
106
|
+
buffer.readUintVar(); // entity_id
|
|
107
|
+
buffer.readUint8(); // component_type_id
|
|
108
|
+
const prior_len = buffer.readUint32();
|
|
109
|
+
buffer.position += prior_len; // prior payload
|
|
110
|
+
}
|
|
111
|
+
this.action_type_id = buffer.readUint8();
|
|
112
|
+
this.sender_id = buffer.readUint8();
|
|
113
|
+
this.payload_len = buffer.readUint32();
|
|
114
|
+
this.payload_start = buffer.position;
|
|
115
|
+
this.record_end = this.payload_start + this.payload_len;
|
|
116
|
+
buffer.position = this.record_end;
|
|
117
|
+
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RewindEngine.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/sim/RewindEngine.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RewindEngine.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/sim/RewindEngine.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;GAmBG;AACH;IAeI;;;;;;OAMG;IACH,uDANW;QACV,UAAc,YAAY;QAC1B,KAAS,yBAAyB;QAClC,kBAAsB,8BAA8B;KACjD,EAiBH;IAdG;;OAEG;IACH,sBAA4B;IAE5B;;OAEG;IACH,8BAAkB;IAElB;;OAEG;IACH,gDAA4C;IAGhD;;;;;OAKG;IACH,kBAFW,MAAM,QA0BhB;IAED;;;;;;;;;OASG;IACH,yBAHW,MAAM,gBACN,MAAM,QAWhB;;CAiDJ"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assert } from "../../../core/assert.js";
|
|
2
|
+
import { ActionRecordCursor } from "./ActionRecordCursor.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Walks the {@link ActionLog} backwards to restore prior component state.
|
|
@@ -28,6 +29,13 @@ export class RewindEngine {
|
|
|
28
29
|
*/
|
|
29
30
|
#record_starts = new Int32Array(64);
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Walks a frame's records to find where each starts.
|
|
34
|
+
* @type {ActionRecordCursor}
|
|
35
|
+
* @private
|
|
36
|
+
*/
|
|
37
|
+
#cursor = new ActionRecordCursor();
|
|
38
|
+
|
|
31
39
|
/**
|
|
32
40
|
* @param {{
|
|
33
41
|
* action_log: ActionLog,
|
|
@@ -64,20 +72,19 @@ export class RewindEngine {
|
|
|
64
72
|
throw new Error(`RewindEngine.undo_frame: frame ${frame} is not present in the action log`);
|
|
65
73
|
}
|
|
66
74
|
|
|
67
|
-
const buffer = this.action_log.buffer_for(frame);
|
|
68
|
-
const end = this.action_log.write_end_for(frame);
|
|
69
|
-
|
|
70
75
|
// 1. First pass: walk forward to find record boundaries.
|
|
76
|
+
const cursor = this.#cursor;
|
|
77
|
+
cursor.open(this.action_log, frame);
|
|
71
78
|
let record_count = 0;
|
|
72
|
-
while (
|
|
79
|
+
while (cursor.next()) {
|
|
73
80
|
if (record_count >= this.#record_starts.length) {
|
|
74
81
|
this.#grow_record_starts();
|
|
75
82
|
}
|
|
76
|
-
this.#record_starts[record_count++] =
|
|
77
|
-
this.#skip_record(buffer);
|
|
83
|
+
this.#record_starts[record_count++] = cursor.record_start;
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
// 2. Second pass: iterate records in reverse, restoring prior state.
|
|
87
|
+
const buffer = cursor.buffer;
|
|
81
88
|
for (let i = record_count - 1; i >= 0; i--) {
|
|
82
89
|
buffer.position = this.#record_starts[i];
|
|
83
90
|
this.#restore_prior_state(buffer);
|
|
@@ -106,29 +113,6 @@ export class RewindEngine {
|
|
|
106
113
|
}
|
|
107
114
|
}
|
|
108
115
|
|
|
109
|
-
/**
|
|
110
|
-
* Walk past a single record without restoring anything. Buffer position
|
|
111
|
-
* advances to the start of the next record (or to `end` if this was the last).
|
|
112
|
-
*
|
|
113
|
-
* @param {BinaryBuffer} buffer
|
|
114
|
-
* @private
|
|
115
|
-
*/
|
|
116
|
-
#skip_record(buffer) {
|
|
117
|
-
// Skip prior_state entries.
|
|
118
|
-
const prior_count = buffer.readUintVar();
|
|
119
|
-
for (let i = 0; i < prior_count; i++) {
|
|
120
|
-
buffer.readUintVar(); // entity_id
|
|
121
|
-
buffer.readUint8(); // component_type_id
|
|
122
|
-
const len = buffer.readUint32();
|
|
123
|
-
buffer.position += len; // skip prior payload
|
|
124
|
-
}
|
|
125
|
-
// Skip action.
|
|
126
|
-
buffer.readUint8(); // action_type_id
|
|
127
|
-
buffer.readUint8(); // sender_id
|
|
128
|
-
const action_len = buffer.readUint32();
|
|
129
|
-
buffer.position += action_len; // skip action payload
|
|
130
|
-
}
|
|
131
|
-
|
|
132
116
|
/**
|
|
133
117
|
* Read the prior_state entries of the record at the buffer's current position
|
|
134
118
|
* and write them back into the live components. Buffer position advances past
|
|
@@ -122,8 +122,7 @@ export class SimActionExecutor {
|
|
|
122
122
|
*
|
|
123
123
|
* Set by a rollback orchestrator for the span in which the frame's local
|
|
124
124
|
* sim runs (see `ServerAuthoritativeServer.__replay_frame`) and cleared
|
|
125
|
-
* immediately after
|
|
126
|
-
* action's `apply` during that span. Everything else — arrived input,
|
|
125
|
+
* immediately after. Everything outside that span — arrived input,
|
|
127
126
|
* locally-authored actions — is left with its real sender.
|
|
128
127
|
*
|
|
129
128
|
* Only the RECORDED byte changes. `sender_id` still drives the
|
|
@@ -192,7 +191,7 @@ export class SimActionExecutor {
|
|
|
192
191
|
* @param {SimAction} action
|
|
193
192
|
* @param {number} [sender_id] peer that originated this action.
|
|
194
193
|
* Defaults to {@link SENDER_LOCAL} for locally-originated actions.
|
|
195
|
-
* Must be in `[0,
|
|
194
|
+
* Must be in `[0, MAX_PEER_ID]` for remote-originated actions.
|
|
196
195
|
* @returns {boolean} true if the action was applied; false if it was
|
|
197
196
|
* rejected by the {@link authorize} gate (and therefore not applied or
|
|
198
197
|
* logged).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SimActionExecutor.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/sim/SimActionExecutor.js"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,2BAFU,MAAM,CAEiB;AAEjC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,6BAFU,MAAM,CAEmB;AAEnC;;;;;GAKG;AACH,0BAFU,MAAM,CAEgB;AAEhC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;
|
|
1
|
+
{"version":3,"file":"SimActionExecutor.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/sim/SimActionExecutor.js"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,2BAFU,MAAM,CAEiB;AAEjC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,6BAFU,MAAM,CAEmB;AAEnC;;;;;GAKG;AACH,0BAFU,MAAM,CAEgB;AAEhC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;IA2DI;;;;;;;;;OASG;IACH,sGATW;QACV,KAAS,yBAAyB;QAClC,UAAc,YAAY;QAC1B,eAAmB,oBAAoB;QACvC,kBAAsB,8BAA8B;QACpD,UAAc,uBAAuB;QACrC,gBAAoB,CAAC,mBAAmB;KACrC,EAyFH;IAvJD;;;;;;;;;OASG;IACH,gBAFU,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAEP;IAEtB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,uBAFuB,MAAM,aAAa,MAAM,wBAAwB,OAAO,CAE9D;IAEjB;;;;;;;;;;;;;;;OAeG;IACH,UAFU,OAAO,CAEA;IAab;;OAEG;IACH,8BAAkB;IAElB;;OAEG;IACH,sBAA4B;IAE5B;;OAEG;IACH,mCAAsC;IAEtC;;OAEG;IACH,gDAA4C;IAE5C;;;;OAIG;IACH,iCAA4B;IAE5B;;;;;;;;;;;OAWG;IACH,kBAFU,mBAAiB,IAAI,CAES;IA4BxC;;;;;;;OAOG;IACH,mBAFU,MAAM,CAEqB;IAErC;;;;;;;OAOG;IACH,kBAFU,MAAM,CAEoB;IAGxC;;;;;;;;;;OAUG;IACH,uCAPW,MAAM,GAGJ,OAAO,CAgJnB;;CACJ;mBA3XkB,uCAAuC"}
|
|
@@ -116,8 +116,7 @@ export class SimActionExecutor {
|
|
|
116
116
|
*
|
|
117
117
|
* Set by a rollback orchestrator for the span in which the frame's local
|
|
118
118
|
* sim runs (see `ServerAuthoritativeServer.__replay_frame`) and cleared
|
|
119
|
-
* immediately after
|
|
120
|
-
* action's `apply` during that span. Everything else — arrived input,
|
|
119
|
+
* immediately after. Everything outside that span — arrived input,
|
|
121
120
|
* locally-authored actions — is left with its real sender.
|
|
122
121
|
*
|
|
123
122
|
* Only the RECORDED byte changes. `sender_id` still drives the
|
|
@@ -233,7 +232,7 @@ export class SimActionExecutor {
|
|
|
233
232
|
* @param {SimAction} action
|
|
234
233
|
* @param {number} [sender_id] peer that originated this action.
|
|
235
234
|
* Defaults to {@link SENDER_LOCAL} for locally-originated actions.
|
|
236
|
-
* Must be in `[0,
|
|
235
|
+
* Must be in `[0, MAX_PEER_ID]` for remote-originated actions.
|
|
237
236
|
* @returns {boolean} true if the action was applied; false if it was
|
|
238
237
|
* rejected by the {@link authorize} gate (and therefore not applied or
|
|
239
238
|
* logged).
|
|
@@ -250,18 +249,18 @@ export class SimActionExecutor {
|
|
|
250
249
|
`execute: action class '${action.constructor.action_type_name ?? action.constructor.name}' has no wire type_id — register it (NetworkSession.defineAction, or SimActionRegistry.register) before executing it`,
|
|
251
250
|
);
|
|
252
251
|
assert.isNonNegativeInteger(sender_id, 'sender_id');
|
|
253
|
-
// A
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
252
|
+
// A sender above the peer range is either the sim-derived sentinel —
|
|
253
|
+
// which a rollback replay reads back as "this record is mine to
|
|
254
|
+
// recompute", so the peer's input would apply once and then disappear
|
|
255
|
+
// on the first replay of its frame, with nothing logged — or a value
|
|
256
|
+
// that does not fit the record's one byte at all. SENDER_LOCAL itself
|
|
257
|
+
// is the caller saying "mine". Real runtime check: asserts strip in
|
|
258
|
+
// prod, and silent input loss is not something to ship behind one.
|
|
259
259
|
if (sender_id > MAX_PEER_ID && sender_id !== SENDER_LOCAL) {
|
|
260
260
|
throw new Error(
|
|
261
|
-
`SimActionExecutor.execute: sender_id ${sender_id} is
|
|
261
|
+
`SimActionExecutor.execute: sender_id ${sender_id} is not a peer id; remote peer ids must be in [0, ${MAX_PEER_ID}] — 254 and 255 are reserved provenance markers`,
|
|
262
262
|
);
|
|
263
263
|
}
|
|
264
|
-
assert.ok(sender_id <= 0xFF, 'sender_id must fit in a uint8');
|
|
265
264
|
|
|
266
265
|
// Expose the originating sender to apply / affected_components / the
|
|
267
266
|
// authorize policy before any of them run.
|
|
@@ -64,11 +64,11 @@ export class FluidObstacleSystem extends System<any> {
|
|
|
64
64
|
/**
|
|
65
65
|
* @param {FluidComponent} fluid
|
|
66
66
|
*/
|
|
67
|
-
static "__#
|
|
67
|
+
static "__#371@#refresh_masks"(fluid: FluidComponent): void;
|
|
68
68
|
/**
|
|
69
69
|
* @param {FluidComponent} fluid
|
|
70
70
|
*/
|
|
71
|
-
static "__#
|
|
71
|
+
static "__#371@#clear_field"(fluid: FluidComponent): void;
|
|
72
72
|
/**
|
|
73
73
|
* Mark every cell of `fluid` whose centre lies within `inflation` of the
|
|
74
74
|
* posed shape as solid. Iteration is clipped to the shape's world AABB
|
|
@@ -80,7 +80,7 @@ export class FluidObstacleSystem extends System<any> {
|
|
|
80
80
|
* @param {number} inflation world-units SDF threshold
|
|
81
81
|
* @param {Float64Array} point length-3 scratch
|
|
82
82
|
*/
|
|
83
|
-
static "__#
|
|
83
|
+
static "__#371@#voxelize"(fluid: FluidComponent, posed: PosedShape3D, aabb: Float64Array, inflation: number, point: Float64Array): void;
|
|
84
84
|
/**
|
|
85
85
|
* Write the obstacle's translation velocity onto every face of every cell
|
|
86
86
|
* it voxelized — the moving-wall boundary condition. Runs AFTER the mask
|
|
@@ -100,7 +100,7 @@ export class FluidObstacleSystem extends System<any> {
|
|
|
100
100
|
* @param {number} wvy
|
|
101
101
|
* @param {number} wvz
|
|
102
102
|
*/
|
|
103
|
-
static "__#
|
|
103
|
+
static "__#371@#stamp_wall_velocity"(fluid: FluidComponent, posed: PosedShape3D, aabb: Float64Array, inflation: number, point: Float64Array, wvx: number, wvy: number, wvz: number): void;
|
|
104
104
|
constructor();
|
|
105
105
|
dependencies: (typeof FluidObstacle)[];
|
|
106
106
|
components_used: (ResourceAccessSpecification<typeof Transform> | ResourceAccessSpecification<typeof RigidBody> | ResourceAccessSpecification<typeof Collider> | ResourceAccessSpecification<typeof FluidComponent> | ResourceAccessSpecification<typeof FluidObstacle>)[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"particle_scene.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/particle_system/particle_scene.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"particle_scene.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/particle_system/particle_scene.js"],"names":[],"mappings":"AAyEA;;;;;;;GAOG;AAEH;;;;;;;;GAQG;AACH;IAJqC,YAAY;IACrB,QAAQ,GAAzB,MAAM;IACJ,MAAM,CA8OlB;AA3QD;;;;GAIG;AACH,0CAA2C;AAE3C,wGAAwG;AACxG,sCAAuC;;;;;WAOzB,MAAM;;;;UACN,MAAM;;;;cACN,MAAM;;;;cACN,MAAM,EAAE;;;;UACR,MAAM,EAAE"}
|
|
@@ -7,7 +7,8 @@ import { CAMERA_UNIFORM_STRUCT } from "../../renderer/camera/CAMERA_UNIFORM_STRU
|
|
|
7
7
|
import { PARTICLE_RECORD_WORD_COUNT } from "../../renderer/particles/ParticleConstants.js";
|
|
8
8
|
import { PARTICLE_COUNTER_COUNT } from "../../renderer/particles/data/PARTICLE_COUNTERS.js";
|
|
9
9
|
import { EMITTER_BLEND, EMITTER_PROJECTION } from "../../renderer/particles/data/PARTICLE_EMITTER_LAYOUT.js";
|
|
10
|
-
import {
|
|
10
|
+
import { NodeGraph } from "../../../core/model/node-graph/NodeGraph.js";
|
|
11
|
+
import { particle_node, particle_wire } from "../../renderer/particles/graph/particle_graph_authoring.js";
|
|
11
12
|
import { VM_BUILTIN, VM_RANDOM_MODE } from "../../renderer/particles/isa/ParticleVMISA.js";
|
|
12
13
|
import { ParticleLayout } from "../../renderer/particles/layout/ParticleLayout.js";
|
|
13
14
|
import { GPUParticleSystem } from "../../renderer/particles/GPUParticleSystem.js";
|
|
@@ -342,49 +343,49 @@ function make_effect({ velocity, velocityJitter = 0, posJitter = 0, accel = 0, l
|
|
|
342
343
|
{ name: "size", components: 1 }, { name: "color", components: 4 },
|
|
343
344
|
]);
|
|
344
345
|
|
|
345
|
-
const spawn = new
|
|
346
|
-
let posN = spawn
|
|
346
|
+
const spawn = new NodeGraph();
|
|
347
|
+
let posN = particle_node(spawn, "builtin", { id: VM_BUILTIN.EMITTER_POSITION });
|
|
347
348
|
if (posJitter > 0) {
|
|
348
|
-
const d = spawn
|
|
349
|
-
const j = spawn
|
|
350
|
-
const p = spawn
|
|
349
|
+
const d = particle_node(spawn, "random", { mode: VM_RANDOM_MODE.UNIT_DISK });
|
|
350
|
+
const j = particle_node(spawn, "scale"); particle_wire(spawn, j, "v", d); particle_wire(spawn, j, "s", [posJitter]);
|
|
351
|
+
const p = particle_node(spawn, "add"); particle_wire(spawn, p, "a", posN); particle_wire(spawn, p, "b", j); posN = p;
|
|
351
352
|
}
|
|
352
|
-
spawn
|
|
353
|
+
particle_wire(spawn, particle_node(spawn, "setAttribute", { name: "position" }), "value", posN);
|
|
353
354
|
|
|
354
|
-
let velN = spawn
|
|
355
|
+
let velN = particle_node(spawn, "const", { value: [velocity[0], velocity[1], velocity[2], 0] });
|
|
355
356
|
if (velocityJitter > 0) {
|
|
356
|
-
const r = spawn
|
|
357
|
-
const rs = spawn
|
|
358
|
-
const vv = spawn
|
|
357
|
+
const r = particle_node(spawn, "random", { mode: VM_RANDOM_MODE.UNIFORM_SIGNED });
|
|
358
|
+
const rs = particle_node(spawn, "scale"); particle_wire(spawn, rs, "v", r); particle_wire(spawn, rs, "s", [velocityJitter]);
|
|
359
|
+
const vv = particle_node(spawn, "add"); particle_wire(spawn, vv, "a", velN); particle_wire(spawn, vv, "b", rs); velN = vv;
|
|
359
360
|
}
|
|
360
|
-
spawn
|
|
361
|
-
spawn
|
|
362
|
-
const lr = spawn
|
|
363
|
-
const lm = spawn
|
|
364
|
-
spawn
|
|
365
|
-
spawn
|
|
366
|
-
spawn
|
|
367
|
-
|
|
368
|
-
const update = new
|
|
369
|
-
const dt = update
|
|
370
|
-
const age = update
|
|
371
|
-
const nage = update
|
|
372
|
-
update
|
|
373
|
-
const lifeA = update
|
|
374
|
-
const na = update
|
|
375
|
-
const dead = update
|
|
376
|
-
update
|
|
377
|
-
let velN2 = update
|
|
361
|
+
particle_wire(spawn, particle_node(spawn, "setAttribute", { name: "velocity" }), "value", velN);
|
|
362
|
+
particle_wire(spawn, particle_node(spawn, "setAttribute", { name: "age" }), "value", [0]);
|
|
363
|
+
const lr = particle_node(spawn, "random", { mode: VM_RANDOM_MODE.UNIFORM });
|
|
364
|
+
const lm = particle_node(spawn, "mad"); particle_wire(spawn, lm, "a", lr); particle_wire(spawn, lm, "b", [life[1] - life[0]]); particle_wire(spawn, lm, "c", [life[0]]);
|
|
365
|
+
particle_wire(spawn, particle_node(spawn, "setAttribute", { name: "lifetime" }), "value", lm);
|
|
366
|
+
particle_wire(spawn, particle_node(spawn, "setAttribute", { name: "size" }), "value", [size]);
|
|
367
|
+
particle_wire(spawn, particle_node(spawn, "setAttribute", { name: "color" }), "value", color);
|
|
368
|
+
|
|
369
|
+
const update = new NodeGraph();
|
|
370
|
+
const dt = particle_node(update, "builtin", { id: VM_BUILTIN.DELTA_TIME });
|
|
371
|
+
const age = particle_node(update, "attribute", { name: "age" });
|
|
372
|
+
const nage = particle_node(update, "add"); particle_wire(update, nage, "a", age); particle_wire(update, nage, "b", dt);
|
|
373
|
+
particle_wire(update, particle_node(update, "setAttribute", { name: "age" }), "value", nage);
|
|
374
|
+
const lifeA = particle_node(update, "attribute", { name: "lifetime" });
|
|
375
|
+
const na = particle_node(update, "div"); particle_wire(update, na, "a", nage); particle_wire(update, na, "b", lifeA);
|
|
376
|
+
const dead = particle_node(update, "compare", { op: "ge" }); particle_wire(update, dead, "a", na); particle_wire(update, dead, "b", [1]);
|
|
377
|
+
particle_wire(update, particle_node(update, "kill"), "condition", dead);
|
|
378
|
+
let velN2 = particle_node(update, "attribute", { name: "velocity" });
|
|
378
379
|
if (accel !== 0) {
|
|
379
|
-
const g = update
|
|
380
|
-
const gd = update
|
|
381
|
-
const nv = update
|
|
382
|
-
update
|
|
380
|
+
const g = particle_node(update, "const", { value: [0, accel, 0, 0] });
|
|
381
|
+
const gd = particle_node(update, "scale"); particle_wire(update, gd, "v", g); particle_wire(update, gd, "s", dt);
|
|
382
|
+
const nv = particle_node(update, "add"); particle_wire(update, nv, "a", velN2); particle_wire(update, nv, "b", gd);
|
|
383
|
+
particle_wire(update, particle_node(update, "setAttribute", { name: "velocity" }), "value", nv); velN2 = nv;
|
|
383
384
|
}
|
|
384
|
-
const pos = update
|
|
385
|
-
const pd = update
|
|
386
|
-
const np = update
|
|
387
|
-
update
|
|
385
|
+
const pos = particle_node(update, "attribute", { name: "position" });
|
|
386
|
+
const pd = particle_node(update, "scale"); particle_wire(update, pd, "v", velN2); particle_wire(update, pd, "s", dt);
|
|
387
|
+
const np = particle_node(update, "add"); particle_wire(update, np, "a", pos); particle_wire(update, np, "b", pd);
|
|
388
|
+
particle_wire(update, particle_node(update, "setAttribute", { name: "position" }), "value", np);
|
|
388
389
|
|
|
389
390
|
return create_particle_effect({ layout, spawn, update });
|
|
390
391
|
}
|
|
@@ -41,12 +41,12 @@ zero** by the `max`. The rotation it extracts is not the matrix's rotation, and
|
|
|
41
41
|
depends on the matrix — so it is wrong differently for every joint. That is the soup: not one
|
|
42
42
|
transform applied badly, but sixty-five of them disagreeing.
|
|
43
43
|
|
|
44
|
-
The two modes
|
|
44
|
+
The two modes made the mechanism visible:
|
|
45
45
|
|
|
46
|
-
| `?skin=` | what
|
|
46
|
+
| `?skin=` | what it did | why |
|
|
47
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
|
|
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
|
|
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
50
|
|
|
51
51
|
`__soup_blend_probe.spec.js` is that measurement: the WGSL of `dual_quat_from_m4`,
|
|
52
52
|
`dual_quat_blend4` and `dual_quat_transform_point` ported to JS, run over the character's own
|
|
@@ -71,22 +71,42 @@ against the bounds chain's box, and **both are derived from the clone's vertex r
|
|
|
71
71
|
that are wrong in the same way agree with each other perfectly. A green `verify()` and a soup
|
|
72
72
|
picture are consistent.
|
|
73
73
|
|
|
74
|
-
##
|
|
74
|
+
## What was done about it
|
|
75
75
|
|
|
76
|
-
The
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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.
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
`mesh.global` always agree about the scale, and the shader's precondition becomes true by
|
|
84
|
-
construction rather than by the exporter's habits. That is a bigger change and it touches every
|
|
85
|
-
asset, not just this one.
|
|
83
|
+
Same measurement, after:
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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 |
|
|
90
110
|
|
|
91
111
|
## The switches
|
|
92
112
|
|
|
@@ -101,7 +121,8 @@ uniform scale, and the position it must still produce.
|
|
|
101
121
|
vertex-slot range says the workgroup stride loop, whole meshlets say addressing, and a uniform
|
|
102
122
|
factor says this bug. It checks the clone's meshlet records address a region of their own first —
|
|
103
123
|
a clone still pointing at the source's vertex data would be deforming the shared rest pose in
|
|
104
|
-
place.
|
|
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.
|
|
105
126
|
- **`?skin=animated`** — the file's dance, as `skinned_blas_refit` runs it.
|
|
106
127
|
- **Path tracer** — off by default. The rasterizer reads the deformed vertices directly, with no
|
|
107
128
|
tree in the way, so it is the reading to establish the answer on: soup in *both* is the vertices,
|
|
@@ -107,10 +107,11 @@
|
|
|
107
107
|
<p class="note">
|
|
108
108
|
<strong>The answer, for the record.</strong> <code>chunk_skin_blend_mesh_local</code> needs
|
|
109
109
|
<code>inverse(mesh.global) × joint.global × inverse_bind</code> to be rigid; for this file
|
|
110
|
-
it
|
|
111
|
-
<code>
|
|
112
|
-
|
|
113
|
-
|
|
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.
|
|
114
115
|
</p>
|
|
115
116
|
|
|
116
117
|
<hr>
|
|
@@ -135,12 +136,14 @@
|
|
|
135
136
|
The skinning pass runs every frame over a pose that never moves — and that pose has an
|
|
136
137
|
answer the CPU knows exactly. <code>await diagnose()</code> reads the clone's vertices
|
|
137
138
|
back and says which are wrong, and whether the wrong ones are a vertex-slot range (the
|
|
138
|
-
workgroup stride loop) or whole meshlets (addressing).
|
|
139
|
+
workgroup stride loop) or whole meshlets (addressing). Should be a full-size character
|
|
140
|
+
standing still, and a clean report.
|
|
139
141
|
</dd>
|
|
140
142
|
<dt>animated</dt>
|
|
141
143
|
<dd>
|
|
142
144
|
Soup only in this mode is the pose: the joint matrices, their range, or the order the
|
|
143
|
-
skin's joints resolved in.
|
|
145
|
+
skin's joints resolved in. Should be dancing, with the traced and rasterized silhouettes
|
|
146
|
+
the same silhouette.
|
|
144
147
|
</dd>
|
|
145
148
|
</dl>
|
|
146
149
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/skinned_mesh_soup/main.js"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -50,22 +50,25 @@ import { load_default_environment } from "../load_default_environment.js";
|
|
|
50
50
|
* - **`?skin=animated`** — the dance, as `skinned_blas_refit` runs it. Soup only here is the pose:
|
|
51
51
|
* the joint matrices, their range, or the order the skin's joints were resolved in.
|
|
52
52
|
*
|
|
53
|
-
* ## What the bisect found
|
|
53
|
+
* ## What the bisect found, and what was done
|
|
54
54
|
*
|
|
55
55
|
* `chunk_skin_blend_mesh_local` folds `inverse(mesh.transform_global)` into each joint matrix and
|
|
56
|
-
* converts the product to a dual quaternion, which can only carry a rigid motion.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* extracts is wrong — and wrong differently per joint, which is what tears the mesh apart.
|
|
56
|
+
* converts the product to a dual quaternion, which can only carry a rigid motion. Michelle's
|
|
57
|
+
* positions and inverse-bind matrices are authored in the already-scaled space, so
|
|
58
|
+
* `joint.global × inverse_bind` is the *identity* and the fold was a bare 100×.
|
|
59
|
+
* `dual_quat_from_m4` reads `sqrt(max(0, 1 ± m00 ± m11 ± m22))` over terms of `1 + 100·(…)`, clamps
|
|
60
|
+
* whichever go negative and normalizes the rest, so the rotation it extracted was wrong — and wrong
|
|
61
|
+
* differently per joint, which is what tore the mesh apart. `?skin=bind` was the right character at
|
|
62
|
+
* 1/100 size (one shared matrix, one shared error) and `?skin=animated` was soup.
|
|
64
63
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
64
|
+
* The frame is now the engine's rather than the asset's: `register_skin` puts each skinned mesh on
|
|
65
|
+
* its skin's own bind transform, so the fold is the identity at bind and a rigid displacement under
|
|
66
|
+
* a clip, for every shape a glTF file arrives in. See `renderer/animation/SKINNING_FRAME_PLAN.md`.
|
|
67
|
+
*
|
|
68
|
+
* All of which was established without a device — `__soup_blend_probe.spec.js` runs the shader's own
|
|
69
|
+
* blend over this character's own matrices, and the emulator tier in
|
|
70
|
+
* `chunk_skin_blend_mesh_local.spec.js` runs the real WGSL over both shapes. This page is where that
|
|
71
|
+
* is confirmed against pixels, which by then is a formality.
|
|
69
72
|
*
|
|
70
73
|
* ## Why through the asset framework
|
|
71
74
|
*
|
|
@@ -10,8 +10,14 @@
|
|
|
10
10
|
* @param {Renderer} options.renderer
|
|
11
11
|
* @param {Scene} options.scene
|
|
12
12
|
* @param {SkinnedMesh} options.mesh
|
|
13
|
-
* @param {number} [options.tolerance] in mesh-local units
|
|
14
|
-
*
|
|
13
|
+
* @param {number} [options.tolerance] in mesh-local units. Since `register_skin` puts the mesh on
|
|
14
|
+
* the skin's bind transform, mesh-local *is* world scale — a metre is a metre here, and at
|
|
15
|
+
* bind the clone should hold the source's own positions. `1e-4` is f32 headroom over a
|
|
16
|
+
* chain — a matrix product, a quaternion conversion and a screw motion, all at single
|
|
17
|
+
* precision — against a smallest real violation of `1.6`, so there are four orders between
|
|
18
|
+
* the two. It was `5e-3` while `dual_quat_from_m4` amplified a near-identity matrix's error
|
|
19
|
+
* by its square root; `SKINNING_FRAME_PLAN.md` §9 took that out, and the same measurement on
|
|
20
|
+
* the CPU now reads zero at bind.
|
|
15
21
|
* @returns {Promise<object>}
|
|
16
22
|
*/
|
|
17
23
|
export function verify_clone_vertices({ renderer, scene, mesh, tolerance }: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify_clone_vertices.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/skinned_mesh_soup/verify_clone_vertices.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"verify_clone_vertices.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/skinned_mesh_soup/verify_clone_vertices.js"],"names":[],"mappings":"AAyJA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IAb6B,QAAQ;IACX,KAAK;IACC,IAAI;IACR,SAAS,GAA1B,MAAM;IAQJ,QAAQ,MAAM,CAAC,CA8P3B"}
|