@lovelace_lol/embody 0.0.14 → 0.0.16
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/README.md +436 -149
- package/dist/index.js +1383 -395
- package/dist/index.js.map +1 -1
- package/dist/three/annotations/modelObservation.d.ts +9 -7
- package/dist/three/annotations/morphGeometry.d.ts +19 -0
- package/dist/three/authoring.d.ts +22 -18
- package/dist/three/authoringTypes.d.ts +45 -4
- package/dist/three/character.d.ts +58 -14
- package/dist/three/editor/morphVertexPreview.d.ts +3 -2
- package/dist/three/index.d.ts +12 -11
- package/dist/three/model.d.ts +2 -0
- package/dist/three/nativePlayback.d.ts +6 -0
- package/dist/three/playbackRendering.d.ts +1 -1
- package/dist/three/scene.d.ts +2 -0
- package/dist/three/sceneBackground.d.ts +11 -0
- package/dist/three/scenery.d.ts +46 -0
- package/dist/three/vrm-animation.d.ts +70 -0
- package/dist/three.js +1383 -395
- package/dist/three.js.map +1 -1
- package/dist/wasm/README.md +436 -149
- package/dist/wasm/embody_wasm.d.ts +251 -105
- package/dist/wasm/embody_wasm.js +541 -31
- package/dist/wasm/embody_wasm_bg.wasm +0 -0
- package/dist/wasm/embody_wasm_bg.wasm.d.ts +123 -98
- package/dist/wasm/highlighting.d.ts +16 -0
- package/dist/wasm/index.d.ts +10 -0
- package/dist/wasm/savedAnimation.d.ts +4 -0
- package/dist/wasm.js +1123 -2
- package/dist/wasm.js.map +1 -1
- package/model-tools/index.d.ts +57 -0
- package/model-tools/index.mjs +354 -0
- package/model-tools/prepare-actor.mjs +79 -0
- package/package.json +19 -5
package/dist/three.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { NoBlending, MultiplyBlending, SubtractiveBlending, AdditiveBlending, NormalBlending, Timer, Quaternion, Vector3,
|
|
3
|
-
import { CC4_HUMANOID_SKELETON_TEMPLATE, HUMANOID_SKELETON_TEMPLATES, DEFAULT_HAIR_COLOR_APPEARANCE,
|
|
1
|
+
import * as THREE11 from 'three';
|
|
2
|
+
import { Matrix4, NoBlending, MultiplyBlending, SubtractiveBlending, AdditiveBlending, NormalBlending, Timer, Quaternion, Vector3, NormalAnimationBlendMode, InterpolateLinear, InterpolateDiscrete, QuaternionKeyframeTrack, VectorKeyframeTrack, AnimationClip, LoadingManager, AnimationMixer, AdditiveAnimationBlendMode, LoopOnce, LoopRepeat, LoopPingPong, PropertyBinding, Euler, AnimationUtils, Box3, MathUtils, Mesh, PlaneGeometry, ShadowMaterial, HemisphereLight, DirectionalLight, SRGBColorSpace, ACESFilmicToneMapping, WebGLRenderer, Scene, Color, PerspectiveCamera, PCFShadowMap, PCFSoftShadowMap, NumberKeyframeTrack, MeshBasicMaterial, TextureLoader, Texture, PMREMGenerator, Group, FloatType, Float32BufferAttribute, BufferAttribute, EquirectangularReflectionMapping, CanvasTexture, WebGPUCoordinateSystem, WebGLCoordinateSystem } from 'three';
|
|
3
|
+
import { CC4_HUMANOID_SKELETON_TEMPLATE, HUMANOID_SKELETON_TEMPLATES, DEFAULT_HAIR_COLOR_APPEARANCE, initEmbodyCore, requireInitializedEmbodyCore } from '@lovelace_lol/embody/wasm';
|
|
4
4
|
export { CC4_HUMANOID_SKELETON_TEMPLATE, DEFAULT_AXIS_TO_CHANNEL, DEFAULT_BONE_MAX_DEGREES, DEFAULT_HAIR_COLOR_APPEARANCE, HAIR_COLOR_PRESETS, HUMANOID_SKELETON_TEMPLATES, JOINT_CONTROL_SECTION, LEGACY_BODY_CONTROL_AUS, VISEME_KEYS, getAUDescriptors } from '@lovelace_lol/embody/wasm';
|
|
5
|
-
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
6
5
|
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
|
|
6
|
+
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
7
7
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
8
8
|
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
|
|
9
9
|
|
|
@@ -32,46 +32,72 @@ function annotationProfileInput(profile) {
|
|
|
32
32
|
}
|
|
33
33
|
return result;
|
|
34
34
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const value = annotationQuery("meshSideOffset", { region, sideSign, size: modelSize });
|
|
40
|
-
return value ? new THREE10.Vector3(value.x, value.y, value.z) : null;
|
|
35
|
+
function readAttribute(attribute) {
|
|
36
|
+
const values = new Float32Array(attribute.count * attribute.itemSize);
|
|
37
|
+
for (let index = 0; index < attribute.count; index++) for (let axis = 0; axis < attribute.itemSize; axis++) values[index * attribute.itemSize + axis] = attribute.getComponent(index, axis);
|
|
38
|
+
return values;
|
|
41
39
|
}
|
|
42
|
-
function
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
const positions = mesh.geometry.getAttribute("position");
|
|
46
|
-
if (!positions) return null;
|
|
40
|
+
function readMorphGeometry(mesh, names) {
|
|
41
|
+
const position = mesh.geometry.getAttribute("position");
|
|
42
|
+
if (!position || position.itemSize !== 3) return null;
|
|
47
43
|
const attributes = names.flatMap((name) => {
|
|
48
44
|
const index = mesh.morphTargetDictionary?.[name];
|
|
49
45
|
const attribute = index == null ? void 0 : mesh.geometry.morphAttributes.position?.[index];
|
|
50
46
|
return attribute ? [attribute] : [];
|
|
51
47
|
});
|
|
52
|
-
|
|
48
|
+
if (!attributes.length || attributes.some((attribute) => attribute.count !== position.count || attribute.itemSize !== 3)) return null;
|
|
49
|
+
const base = readAttribute(position);
|
|
53
50
|
const morphs = new Float32Array(attributes.length * base.length);
|
|
54
51
|
attributes.forEach((attribute, index) => morphs.set(readAttribute(attribute), index * base.length));
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
return { base, morphs, relative: mesh.geometry.morphTargetsRelative };
|
|
53
|
+
}
|
|
54
|
+
function updateAnnotationWorldMatrices(model) {
|
|
55
|
+
model.parent?.updateWorldMatrix(true, false);
|
|
56
|
+
model.updateMatrixWorld(true);
|
|
57
|
+
}
|
|
58
|
+
function sampleMorphVertices(mesh, indices, samples, vertex = new THREE11.Vector3()) {
|
|
57
59
|
const skinned = mesh;
|
|
58
|
-
if (skinned.isSkinnedMesh)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
if (skinned.isSkinnedMesh) {
|
|
61
|
+
for (const bone of skinned.skeleton.bones) bone.updateWorldMatrix(true, false);
|
|
62
|
+
skinned.skeleton.update();
|
|
63
|
+
}
|
|
64
|
+
for (let offset = 0; offset < indices.length; offset++) {
|
|
65
|
+
mesh.getVertexPosition(indices[offset], vertex).applyMatrix4(mesh.matrixWorld);
|
|
66
|
+
samples[offset * 3] = vertex.x;
|
|
67
|
+
samples[offset * 3 + 1] = vertex.y;
|
|
68
|
+
samples[offset * 3 + 2] = vertex.z;
|
|
64
69
|
}
|
|
65
|
-
const value = core.annotation_morph_center(JSON.stringify(region), samples);
|
|
66
|
-
return value.length === 3 ? new THREE10.Vector3().fromArray(value) : null;
|
|
67
70
|
}
|
|
71
|
+
var MorphGeometryObservation = class {
|
|
72
|
+
constructor() {
|
|
73
|
+
__publicField(this, "revision", 0);
|
|
74
|
+
__publicField(this, "namesJson", "[]");
|
|
75
|
+
__publicField(this, "signature", []);
|
|
76
|
+
}
|
|
77
|
+
observe(mesh) {
|
|
78
|
+
const dictionary = Object.entries(mesh.morphTargetDictionary ?? {}).sort(([a], [b]) => a.localeCompare(b));
|
|
79
|
+
const attributes = [mesh.geometry.getAttribute("position"), ...mesh.geometry.morphAttributes.position ?? []];
|
|
80
|
+
const signature = [mesh.geometry, mesh.geometry.morphTargetsRelative, ...dictionary.flat()];
|
|
81
|
+
for (const attribute of attributes) {
|
|
82
|
+
signature.push(attribute);
|
|
83
|
+
if (attribute) signature.push(
|
|
84
|
+
attribute.array,
|
|
85
|
+
attribute.count,
|
|
86
|
+
attribute.itemSize,
|
|
87
|
+
attribute.normalized,
|
|
88
|
+
"data" in attribute ? attribute.data.version : attribute.version,
|
|
89
|
+
"data" in attribute ? attribute.data.stride : 0,
|
|
90
|
+
"offset" in attribute ? attribute.offset : 0
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (signature.length === this.signature.length && signature.every((value, index) => value === this.signature[index])) return;
|
|
94
|
+
this.signature = signature;
|
|
95
|
+
this.revision++;
|
|
96
|
+
this.namesJson = JSON.stringify(dictionary.map(([name]) => name));
|
|
97
|
+
}
|
|
98
|
+
};
|
|
68
99
|
|
|
69
100
|
// three/annotations/modelObservation.ts
|
|
70
|
-
function readAttribute(attribute) {
|
|
71
|
-
const values = new Float32Array(attribute.count * attribute.itemSize);
|
|
72
|
-
for (let index = 0; index < attribute.count; index++) for (let axis = 0; axis < attribute.itemSize; axis++) values[index * attribute.itemSize + axis] = attribute.getComponent(index, axis);
|
|
73
|
-
return values;
|
|
74
|
-
}
|
|
75
101
|
var bounds = (box) => box.isEmpty() ? null : { min: box.min.toArray(), max: box.max.toArray() };
|
|
76
102
|
var AnnotationModelObserver = class {
|
|
77
103
|
constructor(model) {
|
|
@@ -79,6 +105,10 @@ var AnnotationModelObserver = class {
|
|
|
79
105
|
__publicField(this, "objects", /* @__PURE__ */ new Map());
|
|
80
106
|
__publicField(this, "meshes", []);
|
|
81
107
|
__publicField(this, "ids", /* @__PURE__ */ new Map());
|
|
108
|
+
__publicField(this, "morphGeneration", null);
|
|
109
|
+
__publicField(this, "morphObservations", []);
|
|
110
|
+
__publicField(this, "morphGeometry", /* @__PURE__ */ new Map());
|
|
111
|
+
__publicField(this, "vertex", new THREE11.Vector3());
|
|
82
112
|
model.traverse((object) => {
|
|
83
113
|
const id = this.objects.size + 1;
|
|
84
114
|
this.ids.set(object, id);
|
|
@@ -89,13 +119,16 @@ var AnnotationModelObserver = class {
|
|
|
89
119
|
id(object) {
|
|
90
120
|
return this.ids.get(object);
|
|
91
121
|
}
|
|
122
|
+
updateWorldMatrices() {
|
|
123
|
+
updateAnnotationWorldMatrices(this.model);
|
|
124
|
+
}
|
|
92
125
|
capture() {
|
|
93
|
-
this.
|
|
94
|
-
const modelBox = new
|
|
95
|
-
if (modelBox.isEmpty()) modelBox.setFromCenterAndSize(this.model.getWorldPosition(new
|
|
126
|
+
this.updateWorldMatrices();
|
|
127
|
+
const modelBox = new THREE11.Box3().setFromObject(this.model);
|
|
128
|
+
if (modelBox.isEmpty()) modelBox.setFromCenterAndSize(this.model.getWorldPosition(new THREE11.Vector3()), new THREE11.Vector3());
|
|
96
129
|
return {
|
|
97
130
|
bounds: bounds(modelBox),
|
|
98
|
-
quaternion: this.model.getWorldQuaternion(new
|
|
131
|
+
quaternion: this.model.getWorldQuaternion(new THREE11.Quaternion()).toArray(),
|
|
99
132
|
inverse: this.model.matrixWorld.clone().invert().toArray(),
|
|
100
133
|
objects: [...this.objects].map(([id, object]) => {
|
|
101
134
|
const mesh = object;
|
|
@@ -105,8 +138,8 @@ var AnnotationModelObserver = class {
|
|
|
105
138
|
name: object.name,
|
|
106
139
|
kind: mesh.isMesh ? "Mesh" : bone ? "Bone" : object.type,
|
|
107
140
|
parent: object.parent ? this.ids.get(object.parent) ?? null : null,
|
|
108
|
-
position: object.getWorldPosition(new
|
|
109
|
-
bounds: bone ? null : bounds(new
|
|
141
|
+
position: object.getWorldPosition(new THREE11.Vector3()).toArray(),
|
|
142
|
+
bounds: bone ? null : bounds(new THREE11.Box3().setFromObject(object)),
|
|
110
143
|
matrix: object.matrixWorld.toArray(),
|
|
111
144
|
inverse: object.matrixWorld.clone().invert().toArray(),
|
|
112
145
|
morphNames: Object.keys(mesh.morphTargetDictionary ?? {})
|
|
@@ -120,7 +153,7 @@ var AnnotationModelObserver = class {
|
|
|
120
153
|
transforms(runtime) {
|
|
121
154
|
const ids = runtime.tracked_objects();
|
|
122
155
|
if (!ids.length) return new Float32Array();
|
|
123
|
-
this.
|
|
156
|
+
this.updateWorldMatrices();
|
|
124
157
|
const values = new Float32Array(ids.length * 17);
|
|
125
158
|
ids.forEach((id, index) => {
|
|
126
159
|
const object = this.objects.get(id);
|
|
@@ -131,16 +164,51 @@ var AnnotationModelObserver = class {
|
|
|
131
164
|
});
|
|
132
165
|
return values;
|
|
133
166
|
}
|
|
134
|
-
|
|
167
|
+
prepareMorphAnchors(runtime) {
|
|
168
|
+
const generation = runtime.generation();
|
|
169
|
+
if (!runtime.is_current(generation)) return;
|
|
170
|
+
if (this.morphGeneration !== generation) this.morphObservations = [];
|
|
171
|
+
this.morphGeneration = generation;
|
|
172
|
+
const changed = /* @__PURE__ */ new Set();
|
|
173
|
+
for (const mesh of this.meshes) {
|
|
174
|
+
const id = this.ids.get(mesh);
|
|
175
|
+
let observation = this.morphGeometry.get(id);
|
|
176
|
+
if (!observation) {
|
|
177
|
+
observation = new MorphGeometryObservation();
|
|
178
|
+
this.morphGeometry.set(id, observation);
|
|
179
|
+
}
|
|
180
|
+
observation.observe(mesh);
|
|
181
|
+
if (runtime.observe_morph_geometry(generation, id, observation.revision, observation.namesJson)) changed.add(id);
|
|
182
|
+
}
|
|
183
|
+
if (!changed.size) return;
|
|
184
|
+
this.morphObservations = this.morphObservations.filter((observation) => !changed.has(observation.objectId));
|
|
135
185
|
const requests = JSON.parse(runtime.command("morphRequests", "{}", performance.now()));
|
|
136
|
-
const anchors = {};
|
|
137
186
|
for (const request3 of requests) {
|
|
187
|
+
if (!changed.has(request3.objectId)) continue;
|
|
138
188
|
const mesh = this.objects.get(request3.objectId);
|
|
139
189
|
if (!mesh?.isMesh) continue;
|
|
140
|
-
const
|
|
141
|
-
if (
|
|
190
|
+
const geometry = readMorphGeometry(mesh, request3.morphNames);
|
|
191
|
+
if (!geometry) continue;
|
|
192
|
+
const plan = runtime.prepare_morph_anchor(generation, request3.objectId, request3.region.name, geometry.base, geometry.morphs, geometry.relative);
|
|
193
|
+
if (plan.length < 2) continue;
|
|
194
|
+
const indices = plan.slice(1);
|
|
195
|
+
this.morphObservations.push({ planId: plan[0], objectId: request3.objectId, mesh, indices, samples: new Float32Array(indices.length * 3) });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** Transport only: Rust owns the vertex selection, weights and live anchor. */
|
|
199
|
+
observeMorphAnchors(runtime) {
|
|
200
|
+
this.prepareMorphAnchors(runtime);
|
|
201
|
+
if (this.morphGeneration === null || !runtime.is_current(this.morphGeneration)) {
|
|
202
|
+
this.morphObservations = [];
|
|
203
|
+
this.morphGeneration = null;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (!this.morphObservations.length) return;
|
|
207
|
+
this.updateWorldMatrices();
|
|
208
|
+
for (const { planId, mesh, indices, samples } of this.morphObservations) {
|
|
209
|
+
sampleMorphVertices(mesh, indices, samples, this.vertex);
|
|
210
|
+
runtime.observe_morph_anchor(this.morphGeneration, planId, samples);
|
|
142
211
|
}
|
|
143
|
-
return anchors;
|
|
144
212
|
}
|
|
145
213
|
/** Rust selects influenced vertices; Three evaluates the current native pose. */
|
|
146
214
|
observeFocus(runtime, request3) {
|
|
@@ -161,7 +229,7 @@ var AnnotationModelObserver = class {
|
|
|
161
229
|
if (index < 0 || !skinIndices || !skinWeights) continue;
|
|
162
230
|
mesh.skeleton.update();
|
|
163
231
|
const selected = core.annotation_bone_vertices(readAttribute(skinIndices), readAttribute(skinWeights), new Uint32Array([index]));
|
|
164
|
-
const vertex = new
|
|
232
|
+
const vertex = new THREE11.Vector3();
|
|
165
233
|
for (const selectedIndex of selected) {
|
|
166
234
|
mesh.getVertexPosition(selectedIndex, vertex).applyMatrix4(mesh.matrixWorld);
|
|
167
235
|
points.push(vertex.x, vertex.y, vertex.z);
|
|
@@ -191,9 +259,9 @@ function bindSceneRendering(controller, scene, camera, binding) {
|
|
|
191
259
|
if (owner.scene !== scene || owner.camera !== camera) throw new Error("Rendering and camera controllers must share their scene and camera.");
|
|
192
260
|
if (owner.binding) throw new Error("Character scene rendering already has a camera controller.");
|
|
193
261
|
owner.binding = binding;
|
|
194
|
-
const
|
|
195
|
-
if (
|
|
196
|
-
if (
|
|
262
|
+
const snapshot3 = controller.getSnapshot();
|
|
263
|
+
if (snapshot3.renderer) void binding.commit(snapshot3.renderer).catch(binding.onError);
|
|
264
|
+
if (snapshot3.status !== "ready" && snapshot3.status !== "error") void binding.suspend().catch(binding.onError);
|
|
197
265
|
return () => {
|
|
198
266
|
if (owner.binding === binding) {
|
|
199
267
|
owner.drain = binding.suspend();
|
|
@@ -235,8 +303,8 @@ function prepareModelSkinning(model) {
|
|
|
235
303
|
};
|
|
236
304
|
}
|
|
237
305
|
var bindings = /* @__PURE__ */ new WeakMap();
|
|
238
|
-
function bindPlaybackRendering(model, mixer, afterFrame) {
|
|
239
|
-
const binding = { mixer, enabled: true, afterFrame };
|
|
306
|
+
function bindPlaybackRendering(model, mixer, afterFrame, afterSample) {
|
|
307
|
+
const binding = { mixer, enabled: true, disposed: false, afterFrame, afterSample };
|
|
240
308
|
const entries = bindings.get(model) ?? /* @__PURE__ */ new Set();
|
|
241
309
|
entries.add(binding);
|
|
242
310
|
bindings.set(model, entries);
|
|
@@ -245,6 +313,7 @@ function bindPlaybackRendering(model, mixer, afterFrame) {
|
|
|
245
313
|
binding.enabled = enabled;
|
|
246
314
|
},
|
|
247
315
|
dispose() {
|
|
316
|
+
binding.disposed = true;
|
|
248
317
|
entries.delete(binding);
|
|
249
318
|
if (!entries.size) bindings.delete(model);
|
|
250
319
|
}
|
|
@@ -261,8 +330,16 @@ function renderScenePlayback(scene, timer) {
|
|
|
261
330
|
}
|
|
262
331
|
}
|
|
263
332
|
});
|
|
264
|
-
|
|
265
|
-
|
|
333
|
+
const deltaSeconds = timer.getDelta();
|
|
334
|
+
const elapsed = /* @__PURE__ */ new Map();
|
|
335
|
+
for (const mixer of mixers) {
|
|
336
|
+
elapsed.set(mixer, Math.abs(deltaSeconds * mixer.timeScale));
|
|
337
|
+
mixer.update(deltaSeconds);
|
|
338
|
+
}
|
|
339
|
+
for (const binding of active) if (binding.enabled && !binding.disposed) {
|
|
340
|
+
binding.afterSample?.(elapsed.get(binding.mixer));
|
|
341
|
+
}
|
|
342
|
+
for (const binding of active) if (binding.enabled && !binding.disposed) binding.afterFrame(deltaSeconds);
|
|
266
343
|
}
|
|
267
344
|
function createScenePlaybackFrame(scene, document2) {
|
|
268
345
|
const timer = new Timer();
|
|
@@ -348,12 +425,12 @@ function compileModelForRender(renderer, scene, camera, model) {
|
|
|
348
425
|
});
|
|
349
426
|
}
|
|
350
427
|
function drawModelForRender(renderer, scene, camera, model) {
|
|
351
|
-
const viewport = renderer.getViewport(new
|
|
352
|
-
const scissor = renderer.getScissor(new
|
|
428
|
+
const viewport = renderer.getViewport(new THREE11.Vector4());
|
|
429
|
+
const scissor = renderer.getScissor(new THREE11.Vector4());
|
|
353
430
|
const scissorTest = renderer.getScissorTest();
|
|
354
431
|
const restoreRenderTarget = captureRenderTarget(renderer);
|
|
355
432
|
const autoClear = renderer.autoClear;
|
|
356
|
-
const clearColor = renderer.getClearColor(Object.assign(new
|
|
433
|
+
const clearColor = renderer.getClearColor(Object.assign(new THREE11.Color(), { a: 1 }));
|
|
357
434
|
const clearAlpha = renderer.getClearAlpha();
|
|
358
435
|
const native = "isWebGPURenderer" in renderer ? renderer : null;
|
|
359
436
|
const renderObject = native?.getRenderObjectFunction();
|
|
@@ -777,14 +854,14 @@ var CameraDOMControls = class {
|
|
|
777
854
|
this.wrapper.remove();
|
|
778
855
|
}
|
|
779
856
|
};
|
|
780
|
-
var ThreeAnnotationControls = class extends
|
|
857
|
+
var ThreeAnnotationControls = class extends THREE11.EventDispatcher {
|
|
781
858
|
constructor(domElement, runtime, advance, captureInput = true) {
|
|
782
859
|
super();
|
|
783
860
|
this.domElement = domElement;
|
|
784
861
|
this.runtime = runtime;
|
|
785
862
|
this.advance = advance;
|
|
786
863
|
this.captureInput = captureInput;
|
|
787
|
-
__publicField(this, "target", new
|
|
864
|
+
__publicField(this, "target", new THREE11.Vector3());
|
|
788
865
|
__publicField(this, "disposed", false);
|
|
789
866
|
__publicField(this, "previousTouchAction", "");
|
|
790
867
|
__publicField(this, "connected", false);
|
|
@@ -927,12 +1004,12 @@ var ThreeAnnotationRenderer = class {
|
|
|
927
1004
|
this.domElement = domElement;
|
|
928
1005
|
this.onPick = onPick;
|
|
929
1006
|
this.onHover = onHover;
|
|
930
|
-
__publicField(this, "group", new
|
|
1007
|
+
__publicField(this, "group", new THREE11.Group());
|
|
931
1008
|
__publicField(this, "objects", /* @__PURE__ */ new Map());
|
|
932
1009
|
__publicField(this, "overlay");
|
|
933
|
-
__publicField(this, "raycaster", new
|
|
934
|
-
__publicField(this, "pointer", new
|
|
935
|
-
__publicField(this, "viewProjection", new
|
|
1010
|
+
__publicField(this, "raycaster", new THREE11.Raycaster());
|
|
1011
|
+
__publicField(this, "pointer", new THREE11.Vector2());
|
|
1012
|
+
__publicField(this, "viewProjection", new THREE11.Matrix4());
|
|
936
1013
|
__publicField(this, "overlayParent", null);
|
|
937
1014
|
__publicField(this, "releaseParentPosition", null);
|
|
938
1015
|
__publicField(this, "overlayResizeObserver");
|
|
@@ -990,28 +1067,28 @@ var ThreeAnnotationRenderer = class {
|
|
|
990
1067
|
this.attachOverlay();
|
|
991
1068
|
element.addEventListener("click", this.click);
|
|
992
1069
|
}
|
|
993
|
-
sync(
|
|
994
|
-
if (this.revision ===
|
|
995
|
-
if (this.style !==
|
|
996
|
-
this.style =
|
|
997
|
-
const ids = new Set(
|
|
1070
|
+
sync(snapshot3) {
|
|
1071
|
+
if (this.revision === snapshot3.descriptorRevision && this.style === snapshot3.style) return;
|
|
1072
|
+
if (this.style !== snapshot3.style) this.clear();
|
|
1073
|
+
this.style = snapshot3.style;
|
|
1074
|
+
const ids = new Set(snapshot3.descriptors.map((descriptor) => descriptor.id));
|
|
998
1075
|
for (const id of this.objects.keys()) if (!ids.has(id)) this.remove(id);
|
|
999
|
-
for (const descriptor of
|
|
1076
|
+
for (const descriptor of snapshot3.descriptors) {
|
|
1000
1077
|
const signature = JSON.stringify(descriptor);
|
|
1001
1078
|
if (this.objects.get(descriptor.id)?.descriptor === signature) continue;
|
|
1002
1079
|
this.remove(descriptor.id);
|
|
1003
1080
|
this.objects.set(descriptor.id, this.create(descriptor, signature));
|
|
1004
1081
|
}
|
|
1005
|
-
this.revision =
|
|
1082
|
+
this.revision = snapshot3.descriptorRevision;
|
|
1006
1083
|
}
|
|
1007
1084
|
create(descriptor, signature) {
|
|
1008
1085
|
const { style } = descriptor;
|
|
1009
|
-
const sphere = new
|
|
1086
|
+
const sphere = new THREE11.Mesh(new THREE11.SphereGeometry(descriptor.radius, 12, 12), new THREE11.MeshBasicMaterial({ color: style.markerColor, transparent: true }));
|
|
1010
1087
|
const dashed = style.line.style === "dashed" || style.line.style === "dotted";
|
|
1011
|
-
const material = dashed ? new
|
|
1012
|
-
const geometry = new
|
|
1013
|
-
geometry.setAttribute("position", new
|
|
1014
|
-
const line = new
|
|
1088
|
+
const material = dashed ? new THREE11.LineDashedMaterial({ color: style.lineColor, transparent: true, dashSize: style.line.style === "dotted" ? 5e-3 : 0.02, gapSize: 0.01, linewidth: style.line.thickness }) : new THREE11.LineBasicMaterial({ color: style.lineColor, transparent: true, linewidth: style.line.thickness });
|
|
1089
|
+
const geometry = new THREE11.BufferGeometry();
|
|
1090
|
+
geometry.setAttribute("position", new THREE11.BufferAttribute(new Float32Array(51), 3));
|
|
1091
|
+
const line = new THREE11.Line(geometry, material);
|
|
1015
1092
|
const canvas = document.createElement("canvas");
|
|
1016
1093
|
const context = canvas.getContext("2d");
|
|
1017
1094
|
if (!context) {
|
|
@@ -1032,9 +1109,9 @@ var ThreeAnnotationRenderer = class {
|
|
|
1032
1109
|
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
1033
1110
|
context.fillStyle = style.labelColor;
|
|
1034
1111
|
context.fillText(descriptor.label, canvas.width / 2, canvas.height / 2);
|
|
1035
|
-
const texture = new
|
|
1036
|
-
const label = new
|
|
1037
|
-
const arrow = style.line.arrowHead ? new
|
|
1112
|
+
const texture = new THREE11.CanvasTexture(canvas);
|
|
1113
|
+
const label = new THREE11.Sprite(new THREE11.SpriteMaterial({ map: texture, transparent: true, depthTest: true, depthWrite: false, sizeAttenuation: false }));
|
|
1114
|
+
const arrow = style.line.arrowHead ? new THREE11.Mesh(new THREE11.ConeGeometry(descriptor.arrowRadius, descriptor.arrowLength, 8), new THREE11.MeshBasicMaterial({ color: style.lineColor, transparent: true })) : null;
|
|
1038
1115
|
let element = null;
|
|
1039
1116
|
if (this.style === "html") {
|
|
1040
1117
|
element = document.createElement("div");
|
|
@@ -1069,7 +1146,7 @@ var ThreeAnnotationRenderer = class {
|
|
|
1069
1146
|
const rays = this.runtime.occlusion_queries();
|
|
1070
1147
|
const hits = new Float32Array(rays.length / 8 * 2);
|
|
1071
1148
|
for (let offset = 0; offset < rays.length; offset += 8) {
|
|
1072
|
-
this.raycaster.set(new
|
|
1149
|
+
this.raycaster.set(new THREE11.Vector3().fromArray(rays, offset + 1), new THREE11.Vector3().fromArray(rays, offset + 4));
|
|
1073
1150
|
this.raycaster.near = 0;
|
|
1074
1151
|
this.raycaster.far = rays[offset + 7];
|
|
1075
1152
|
const intersection = this.raycaster.intersectObjects(this.meshes, false)[0];
|
|
@@ -1096,7 +1173,7 @@ var ThreeAnnotationRenderer = class {
|
|
|
1096
1173
|
positions.array.set(values.subarray(offset + 19, offset + 70));
|
|
1097
1174
|
positions.needsUpdate = true;
|
|
1098
1175
|
objects.line.geometry.computeBoundingSphere();
|
|
1099
|
-
if (objects.line.material instanceof
|
|
1176
|
+
if (objects.line.material instanceof THREE11.LineDashedMaterial) objects.line.computeLineDistances();
|
|
1100
1177
|
objects.label.position.fromArray(values, offset + 5);
|
|
1101
1178
|
objects.label.scale.set(values[offset + 9], values[offset + 10], 1);
|
|
1102
1179
|
objects.label.material.opacity = values[offset + 11];
|
|
@@ -1219,7 +1296,7 @@ var ThreeAnnotationController = class _ThreeAnnotationController {
|
|
|
1219
1296
|
__publicField(this, "markerStateListeners", /* @__PURE__ */ new Set());
|
|
1220
1297
|
__publicField(this, "regionListeners", /* @__PURE__ */ new Set());
|
|
1221
1298
|
__publicField(this, "renderer", null);
|
|
1222
|
-
__publicField(this, "playbackTimer", new
|
|
1299
|
+
__publicField(this, "playbackTimer", new THREE11.Timer());
|
|
1223
1300
|
__publicField(this, "renderFrame");
|
|
1224
1301
|
__publicField(this, "resizeRenderer");
|
|
1225
1302
|
__publicField(this, "onRenderError");
|
|
@@ -1247,7 +1324,7 @@ var ThreeAnnotationController = class _ThreeAnnotationController {
|
|
|
1247
1324
|
}, nativeOptions.captureInput);
|
|
1248
1325
|
this.state = this.readState();
|
|
1249
1326
|
if (this.manageCamera) this.applyCameraState(this.state.camera);
|
|
1250
|
-
else this.controls.target.copy(this.camera.getWorldDirection(new
|
|
1327
|
+
else this.controls.target.copy(this.camera.getWorldDirection(new THREE11.Vector3()).add(this.camera.position));
|
|
1251
1328
|
this.markerRenderer = new ThreeAnnotationRenderer(this.runtime, this.scene, this.camera, this.domElement, (ids) => this.pick(ids), (id) => {
|
|
1252
1329
|
if (!this.disposed) {
|
|
1253
1330
|
this.call("hover", { id });
|
|
@@ -1450,11 +1527,12 @@ var ThreeAnnotationController = class _ThreeAnnotationController {
|
|
|
1450
1527
|
const observer = this.observer;
|
|
1451
1528
|
const generation = this.runtime.generation();
|
|
1452
1529
|
observer.observe(this.runtime);
|
|
1453
|
-
|
|
1530
|
+
observer.observeMorphAnchors(this.runtime);
|
|
1531
|
+
const queries = JSON.parse(this.runtime.plan_markers("{}"));
|
|
1454
1532
|
for (const initial of queries) {
|
|
1455
1533
|
let query = initial;
|
|
1456
1534
|
while (query && this.isCurrent(generation)) {
|
|
1457
|
-
const raycaster = new
|
|
1535
|
+
const raycaster = new THREE11.Raycaster(new THREE11.Vector3().fromArray(query.origin), new THREE11.Vector3().fromArray(query.direction), 0, query.far);
|
|
1458
1536
|
const hits = sliceSurfaceQueries ? await intersectMeshesInSlices(raycaster, observer.meshes, { isCurrent: () => this.isCurrent(generation), yieldToPaint: waitForCharacterLoadPaint }) : raycaster.intersectObjects(observer.meshes, false);
|
|
1459
1537
|
if (!hits || !this.isCurrent(generation)) return;
|
|
1460
1538
|
query = JSON.parse(this.runtime.surface_result(query.generation, query.id, query.phase, new Float32Array(hits.flatMap((hit) => hit.point.toArray()))));
|
|
@@ -1565,6 +1643,7 @@ var ThreeAnnotationController = class _ThreeAnnotationController {
|
|
|
1565
1643
|
if (this.disposed) return;
|
|
1566
1644
|
this.cancelModelPreparation();
|
|
1567
1645
|
this.call("clear");
|
|
1646
|
+
this.observer?.observeMorphAnchors(this.runtime);
|
|
1568
1647
|
this.hostConfig = null;
|
|
1569
1648
|
this.configSnapshot = null;
|
|
1570
1649
|
this.configSnapshotHost = null;
|
|
@@ -1678,7 +1757,10 @@ var ThreeAnnotationController = class _ThreeAnnotationController {
|
|
|
1678
1757
|
this.controls.target.fromArray(pose, 3);
|
|
1679
1758
|
this.camera.lookAt(this.controls.target);
|
|
1680
1759
|
}
|
|
1681
|
-
if (this.observer)
|
|
1760
|
+
if (this.observer) {
|
|
1761
|
+
this.runtime.observe_transforms(this.observer.transforms(this.runtime));
|
|
1762
|
+
this.observer.observeMorphAnchors(this.runtime);
|
|
1763
|
+
}
|
|
1682
1764
|
this.sync();
|
|
1683
1765
|
this.markerRenderer.update(now);
|
|
1684
1766
|
this.settleFlights();
|
|
@@ -1936,7 +2018,7 @@ var ThreeAnnotationMarkers = class {
|
|
|
1936
2018
|
}
|
|
1937
2019
|
getMarkerPosition(name) {
|
|
1938
2020
|
const p = this.controller.getMarkerPosition(name);
|
|
1939
|
-
return p ? new
|
|
2021
|
+
return p ? new THREE11.Vector3(p.x, p.y, p.z) : null;
|
|
1940
2022
|
}
|
|
1941
2023
|
updateLineStyle(name, line) {
|
|
1942
2024
|
this.controller.setRegionLineStyle(name, line);
|
|
@@ -2111,11 +2193,35 @@ function shouldUseFaceCenterForMarkerAnchor(resolved) {
|
|
|
2111
2193
|
function shouldProjectMarkerAnchorToSurface(resolved, legacyDefault) {
|
|
2112
2194
|
return annotationQuery("projectAnchor", { resolved, legacyDefault });
|
|
2113
2195
|
}
|
|
2196
|
+
function getRuntimeAnnotationSide(region) {
|
|
2197
|
+
return annotationQuery("side", { region });
|
|
2198
|
+
}
|
|
2199
|
+
function getRuntimeAUMeshSideOffset(region, sideSign, modelSize) {
|
|
2200
|
+
const value = annotationQuery("meshSideOffset", { region, sideSign, size: modelSize });
|
|
2201
|
+
return value ? new THREE11.Vector3(value.x, value.y, value.z) : null;
|
|
2202
|
+
}
|
|
2203
|
+
function getRuntimeAUMorphAnchorPoint(region, mesh) {
|
|
2204
|
+
const core = requireInitializedEmbodyCore();
|
|
2205
|
+
const names = annotationQuery("morphNames", { region });
|
|
2206
|
+
const geometry = readMorphGeometry(mesh, names);
|
|
2207
|
+
if (!geometry) return null;
|
|
2208
|
+
const candidates = core.annotation_morph_anchor_candidates(JSON.stringify(region), geometry.base, geometry.morphs, geometry.relative);
|
|
2209
|
+
updateAnnotationWorldMatrices(mesh);
|
|
2210
|
+
const positions = new Float32Array(candidates.length / 2 * 3);
|
|
2211
|
+
sampleMorphVertices(mesh, candidates.filter((_value, index) => index % 2 === 0), positions);
|
|
2212
|
+
const samples = new Float32Array(candidates.length / 2 * 4);
|
|
2213
|
+
for (let index = 0; index < candidates.length / 2; index++) {
|
|
2214
|
+
samples.set(positions.subarray(index * 3, index * 3 + 3), index * 4);
|
|
2215
|
+
samples[index * 4 + 3] = candidates[index * 2 + 1];
|
|
2216
|
+
}
|
|
2217
|
+
const value = core.annotation_weighted_anchor(samples);
|
|
2218
|
+
return value.length === 3 ? new THREE11.Vector3().fromArray(value) : null;
|
|
2219
|
+
}
|
|
2114
2220
|
var AnnotationLifecycleBridge = class {
|
|
2115
|
-
constructor(mode, duration,
|
|
2221
|
+
constructor(mode, duration, apply2) {
|
|
2116
2222
|
this.mode = mode;
|
|
2117
2223
|
this.duration = duration;
|
|
2118
|
-
this.apply =
|
|
2224
|
+
this.apply = apply2;
|
|
2119
2225
|
__publicField(this, "readiness", Promise.resolve());
|
|
2120
2226
|
__publicField(this, "initializing", null);
|
|
2121
2227
|
__publicField(this, "core", null);
|
|
@@ -2257,96 +2363,6 @@ function boneResolutionProfile(profile) {
|
|
|
2257
2363
|
suffixPattern: profile.suffixPattern
|
|
2258
2364
|
};
|
|
2259
2365
|
}
|
|
2260
|
-
var json = (value) => JSON.stringify(value ?? {});
|
|
2261
|
-
function request2(op, payload = {}) {
|
|
2262
|
-
return JSON.parse(requireInitializedEmbodyCore().embody_request(json({ op, payload })));
|
|
2263
|
-
}
|
|
2264
|
-
var getPreset = (id) => request2("preset.get", { id });
|
|
2265
|
-
var extendCharacterConfigWithPreset = (config) => request2("profile.extendConfig", { config });
|
|
2266
|
-
var extractProfileOverrides = (config) => request2("profile.extractOverrides", { config });
|
|
2267
|
-
var mergeCharacterRegionsByName = (base, override) => request2("profile.mergeRegions", { base, override });
|
|
2268
|
-
var getMeshNamesForAUProfile = (profile, auId) => request2("profile.getMeshNamesForAU", { profile, auId });
|
|
2269
|
-
var getHumanoidSpecification = () => request2("humanoid.getSpecification");
|
|
2270
|
-
var setAUMorphTargets = (profile, auId, side, targets) => request2("profile.setAUMorphTargets", { profile, auId, side, targets });
|
|
2271
|
-
var setHumanoidRoleBinding = (profile, role, boneName) => request2("profile.setHumanoidRoleBinding", { profile, role, boneName });
|
|
2272
|
-
var resolveHumanoidCharacterization = (profile) => request2("profile.resolveHumanoidCharacterization", { profile });
|
|
2273
|
-
var hasLeftRightMorphs = (profile, auId) => request2("profile.hasLeftRightMorphs", { profile, auId });
|
|
2274
|
-
var isMixedAU = (profile, auId) => request2("profile.isMixedAU", { profile, auId });
|
|
2275
|
-
var resolveVisemeProfile = (profile) => request2("profile.resolveVisemes", { profile });
|
|
2276
|
-
var isMaxDegreesOnlyAxisBindingUpdate = (update) => request2("bone.isMaxDegreesOnlyUpdate", { update });
|
|
2277
|
-
var findNodeKeyForBone = (boneName, profile) => profile ? request2("bone.findNodeKey", { profile, boneName }) : null;
|
|
2278
|
-
var ensureBoneNodeKey = (profile, boneName) => request2("bone.ensureNodeKey", { profile, boneName });
|
|
2279
|
-
var ensureBilateralBoneNodeKeys = (profile, leftBoneName, rightBoneName) => request2("bone.ensureBilateralNodeKeys", { profile, leftBoneName, rightBoneName });
|
|
2280
|
-
var resolveBoneNameForNodeKey = (profile, nodeKey) => request2("bone.resolveBoneName", { profile, nodeKey });
|
|
2281
|
-
var stripConfiguredBoneAffixes = (profile, boneName) => request2("bone.stripAffixes", { profile: profile ?? {}, boneName });
|
|
2282
|
-
var inferChiralBoneNamePair = (boneNames, selectedBoneName) => request2("bone.inferChiralPair", { boneNames, selectedBoneName });
|
|
2283
|
-
var formatAxisLabel = (axis) => request2("bone.formatAxisLabel", { axis });
|
|
2284
|
-
var formatAxisDirectionLabel = (axis, direction) => request2("bone.formatDirectionLabel", { axis, direction });
|
|
2285
|
-
var getAxisFromChannel = (channel) => channel ? request2("bone.getAxisFromChannel", { channel }) : null;
|
|
2286
|
-
var getBoneAxisBindingState = (profile, boneName, axis) => request2("bone.getAxisState", { profile, boneName, axis });
|
|
2287
|
-
var resolveBoneAxisChannel = (profile, boneName, axis) => request2("bone.resolveAxisChannel", { profile, boneName, axis });
|
|
2288
|
-
var getBilateralAxisBindingState = (profile, boneName, axis) => request2("bone.getBilateralAxisState", { profile, boneName, axis });
|
|
2289
|
-
var getAUBoneBindingState = (profile, auId) => request2("bone.getAUBindingState", { profile, auId });
|
|
2290
|
-
var inferEyeControlScope = (profile, binding) => request2("bone.inferEyeScope", { profile, binding });
|
|
2291
|
-
var inferEyeControlFamily = (binding) => request2("bone.inferEyeFamily", { binding });
|
|
2292
|
-
var applyBoneAxisBindingUpdate = (profile, boneName, axis, update) => request2("bone.applyAxisUpdate", { profile, boneName, axis, update });
|
|
2293
|
-
var applyBilateralAxisBindingUpdate = (profile, boneName, axis, scope, update) => request2("bone.applyBilateralAxisUpdate", { profile, boneName, axis, scope, update });
|
|
2294
|
-
var applyAUBoneBindingUpdate = (profile, auId, update) => request2("bone.applyAUBindingUpdate", { profile, auId, update });
|
|
2295
|
-
var createBoneAxisAu = (profile, boneName, axis, direction, options = {}) => request2("bone.createAxisAU", { profile, boneName, axis, direction, name: options.name });
|
|
2296
|
-
var createBilateralBoneAxisAu = (profile, boneName, axis, scope, direction, options = {}) => request2("bone.createBilateralAxisAU", { profile, boneName, axis, scope, direction, name: options.name });
|
|
2297
|
-
var buildBoneAuOptions = (profile) => request2("bone.buildAUOptions", { profile });
|
|
2298
|
-
var isJointControlAuInfo = (info) => request2("bone.isJointControlInfo", { info });
|
|
2299
|
-
var classifyAuAsJointControl = (profile, auId, options = {}) => request2("bone.classifyJointControl", { profile, auId, boneName: options.boneName, nodeKey: options.nodeKey });
|
|
2300
|
-
var resolveContinuumDisplayLabel = (profile, negativeAuId, positiveAuId) => request2("bone.resolveContinuumLabel", { profile, negativeAuId, positiveAuId }) ?? `AU ${negativeAuId} <-> AU ${positiveAuId}`;
|
|
2301
|
-
function inspectModel(model, meshes, profile) {
|
|
2302
|
-
return new ThreeModelInspector().inspectModel(model, { meshes, profile });
|
|
2303
|
-
}
|
|
2304
|
-
function extractModelData(model, meshes, animations = []) {
|
|
2305
|
-
const inspection = inspectModel(model, meshes);
|
|
2306
|
-
return JSON.parse(requireInitializedEmbodyCore().extract_model_data_json(json(inspection.descriptor), json(serializeAnimationClips(model, animations, inspection))));
|
|
2307
|
-
}
|
|
2308
|
-
async function analyzeModel(options) {
|
|
2309
|
-
const source = options.source;
|
|
2310
|
-
const model = source.type === "gltf" ? source.gltf.scene ?? source.gltf.scenes[0] : source.model;
|
|
2311
|
-
const animations = source.type === "gltf" ? source.gltf.animations : source.animations ?? [];
|
|
2312
|
-
const inspection = inspectModel(model, source.type === "runtime" ? source.meshes : void 0);
|
|
2313
|
-
return JSON.parse(requireInitializedEmbodyCore().analyze_model_descriptor(json(inspection.descriptor), json(serializeAnimationClips(model, animations, inspection)), options.preset ? json(options.preset) : "", json({ suggestCorrections: Boolean(options.suggestCorrections) })));
|
|
2314
|
-
}
|
|
2315
|
-
function validateMappings(meshes, skeleton, profile, options = {}) {
|
|
2316
|
-
let nextMorphId = 1;
|
|
2317
|
-
const morphTargets = [];
|
|
2318
|
-
const descriptorMeshes = (meshes ?? []).map((value, index) => {
|
|
2319
|
-
const mesh = value;
|
|
2320
|
-
const id = index + 1;
|
|
2321
|
-
const morphTargetIds = Object.entries(mesh.morphTargetDictionary ?? mesh.geometry?.morphTargetDictionary ?? {}).map(([name, hostIndex]) => {
|
|
2322
|
-
const morphId = nextMorphId++;
|
|
2323
|
-
morphTargets.push({ id: morphId, meshId: id, name, hostIndex });
|
|
2324
|
-
return morphId;
|
|
2325
|
-
});
|
|
2326
|
-
return { id, name: mesh.name ?? "", morphTargetIds, visible: true };
|
|
2327
|
-
});
|
|
2328
|
-
const bones = (skeleton?.bones ?? []).map((bone, index) => ({ id: index + 1, name: typeof bone === "string" ? bone : bone.name ?? "" }));
|
|
2329
|
-
return JSON.parse(requireInitializedEmbodyCore().validate_profile_model(json(profile), json({ meshes: descriptorMeshes, morphTargets, bones, objects: [] }), json(options)));
|
|
2330
|
-
}
|
|
2331
|
-
function validateHumanoidCharacterization(model, profile) {
|
|
2332
|
-
return JSON.parse(requireInitializedEmbodyCore().validate_humanoid_characterization(json(profile), json(inspectModel(model, void 0, profile).descriptor)));
|
|
2333
|
-
}
|
|
2334
|
-
function normalizeHairColorAppearance(value, fallback = DEFAULT_HAIR_COLOR_APPEARANCE) {
|
|
2335
|
-
return JSON.parse(requireInitializedEmbodyCore().normalize_hair_color_appearance_json(json(value), json(fallback)));
|
|
2336
|
-
}
|
|
2337
|
-
var getHumanoidSkeletonTemplate = (id) => HUMANOID_SKELETON_TEMPLATES.find((template) => template.id === id) ?? null;
|
|
2338
|
-
function getHumanoidSkeletonTemplateBoneNames(template = CC4_HUMANOID_SKELETON_TEMPLATE) {
|
|
2339
|
-
return JSON.parse(requireInitializedEmbodyCore().humanoid_skeleton_template_bone_names(json(template)));
|
|
2340
|
-
}
|
|
2341
|
-
function createValidationSkeletonFromHumanoidTemplate(template = CC4_HUMANOID_SKELETON_TEMPLATE) {
|
|
2342
|
-
return JSON.parse(requireInitializedEmbodyCore().create_validation_skeleton_json(json(template)));
|
|
2343
|
-
}
|
|
2344
|
-
function computeHumanoidSkeletonTemplateRestBounds(template) {
|
|
2345
|
-
return Array.from(requireInitializedEmbodyCore().compute_humanoid_skeleton_template_rest_bounds(json(template)));
|
|
2346
|
-
}
|
|
2347
|
-
function extractHumanoidSkeletonTemplateFromModel(model, options) {
|
|
2348
|
-
return JSON.parse(requireInitializedEmbodyCore().extract_humanoid_skeleton_template_json(json(inspectModel(model).descriptor), json(options)));
|
|
2349
|
-
}
|
|
2350
2366
|
var isBone = (object) => !!(object.isBone || object.type === "Bone");
|
|
2351
2367
|
var finite = (values, description) => {
|
|
2352
2368
|
if (!values.every(Number.isFinite)) {
|
|
@@ -2471,6 +2487,286 @@ function bindModelReferencePose(model, referencePose) {
|
|
|
2471
2487
|
if (index !== referencePose.nodes.length) throw new Error("Incompatible reference pose hierarchy: extra captured nodes");
|
|
2472
2488
|
return bindings2;
|
|
2473
2489
|
}
|
|
2490
|
+
|
|
2491
|
+
// three/vrm-animation.ts
|
|
2492
|
+
var MAX_BYTES = 64 * 1024 * 1024;
|
|
2493
|
+
var identity = new Matrix4();
|
|
2494
|
+
var isBone2 = (node) => node.isBone || node.type === "Bone";
|
|
2495
|
+
function decomposeReference(matrix, name) {
|
|
2496
|
+
const position = new Vector3();
|
|
2497
|
+
const rotation = new Quaternion();
|
|
2498
|
+
const scale = new Vector3();
|
|
2499
|
+
if (!matrix.elements.every(Number.isFinite) || Math.abs(matrix.determinant()) < 1e-20) {
|
|
2500
|
+
throw new Error(`Invalid reference matrix for ${name}`);
|
|
2501
|
+
}
|
|
2502
|
+
matrix.decompose(position, rotation, scale);
|
|
2503
|
+
const reconstructed = new Matrix4().compose(position, rotation, scale);
|
|
2504
|
+
if (matrix.elements.some((v, i) => Math.abs(v - reconstructed.elements[i]) > 1e-5 * Math.max(1, Math.abs(v)))) {
|
|
2505
|
+
throw new Error(`Reference ${name} contains shear, which VRMA retargeting does not support`);
|
|
2506
|
+
}
|
|
2507
|
+
return { translation: position.toArray(), rotation: rotation.toArray(), scale: scale.toArray() };
|
|
2508
|
+
}
|
|
2509
|
+
function objectsInModel(model) {
|
|
2510
|
+
const all = [];
|
|
2511
|
+
model.traverse((node) => all.push(node));
|
|
2512
|
+
const needed = /* @__PURE__ */ new Set([model]);
|
|
2513
|
+
for (const bone of all.filter(isBone2)) {
|
|
2514
|
+
for (let node = bone; node && node !== model; node = node.parent) needed.add(node);
|
|
2515
|
+
}
|
|
2516
|
+
const objects = all.filter((node) => needed.has(node));
|
|
2517
|
+
if (objects.length > 4096) throw new Error("Animation rig exceeds 4096 nodes");
|
|
2518
|
+
const ids = new Map(objects.map((node, i) => [node, String(i)]));
|
|
2519
|
+
return { objects, ids };
|
|
2520
|
+
}
|
|
2521
|
+
function inspectReference(model, referencePose, metersPerUnit, includeRoot = false) {
|
|
2522
|
+
const bindings2 = bindModelReferencePose(model, referencePose);
|
|
2523
|
+
const { objects, ids } = objectsInModel(model);
|
|
2524
|
+
const nodes = objects.map((node) => ({
|
|
2525
|
+
id: ids.get(node),
|
|
2526
|
+
name: node.name,
|
|
2527
|
+
parent: node === model ? null : ids.get(node.parent),
|
|
2528
|
+
// Placement of the model in a scene is not part of its humanoid coordinate system.
|
|
2529
|
+
...decomposeReference(node === model && !includeRoot ? identity : new Matrix4().fromArray(bindings2.get(node).localMatrix), node.name)
|
|
2530
|
+
}));
|
|
2531
|
+
return { rig: { metersPerUnit, nodes, humanoidBones: {} }, objects, ids };
|
|
2532
|
+
}
|
|
2533
|
+
function inspectSkinBind(model, metersPerUnit) {
|
|
2534
|
+
if (model.parent) throw new Error("Use an explicit referencePose for a parented source model");
|
|
2535
|
+
const initial = captureModelReferencePose(model);
|
|
2536
|
+
const initialBindings = bindModelReferencePose(model, initial);
|
|
2537
|
+
const { objects, ids } = objectsInModel(model);
|
|
2538
|
+
const world = /* @__PURE__ */ new Map();
|
|
2539
|
+
const all = [];
|
|
2540
|
+
model.traverse((node) => all.push(node));
|
|
2541
|
+
for (const node of all) {
|
|
2542
|
+
const mesh = node;
|
|
2543
|
+
if (!mesh.isSkinnedMesh) continue;
|
|
2544
|
+
const { bones, boneInverses } = mesh.skeleton;
|
|
2545
|
+
if (bones.length !== boneInverses.length) throw new Error("Invalid skin bind inverse count");
|
|
2546
|
+
bones.forEach((bone, index) => {
|
|
2547
|
+
const inverse = boneInverses[index];
|
|
2548
|
+
if (Math.abs(inverse.determinant()) < 1e-20) throw new Error(`Singular bind matrix for ${bone.name}`);
|
|
2549
|
+
const bind = inverse.clone().invert();
|
|
2550
|
+
const prior = world.get(bone);
|
|
2551
|
+
if (prior && prior.elements.some((value, i) => Math.abs(value - bind.elements[i]) > 1e-4)) {
|
|
2552
|
+
throw new Error(`Conflicting skin bind matrices for ${bone.name}`);
|
|
2553
|
+
}
|
|
2554
|
+
world.set(bone, bind);
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2557
|
+
const nodes = objects.map((node) => {
|
|
2558
|
+
if (isBone2(node) && !world.has(node)) {
|
|
2559
|
+
throw new Error(`No skin bind reference for ${node.name}; supply an explicit authored T-pose reference`);
|
|
2560
|
+
}
|
|
2561
|
+
const nodeWorld = world.get(node) ?? new Matrix4().fromArray(initialBindings.get(node).worldMatrix);
|
|
2562
|
+
const parentWorld = node.parent ? world.get(node.parent) ?? new Matrix4().fromArray(initialBindings.get(node.parent).worldMatrix) : identity;
|
|
2563
|
+
const local = parentWorld.clone().invert().multiply(nodeWorld);
|
|
2564
|
+
return { id: ids.get(node), name: node.name, parent: node === model ? null : ids.get(node.parent), ...decomposeReference(local, node.name) };
|
|
2565
|
+
});
|
|
2566
|
+
return { rig: { metersPerUnit, nodes, humanoidBones: {} }, objects, ids };
|
|
2567
|
+
}
|
|
2568
|
+
function uniqueBone(objects, name) {
|
|
2569
|
+
const matches = objects.filter((node) => isBone2(node) && (node.name === name || node.uuid === name));
|
|
2570
|
+
if (matches.length !== 1) throw new Error(`Animation bone ${name} must resolve uniquely (found ${matches.length})`);
|
|
2571
|
+
return matches[0];
|
|
2572
|
+
}
|
|
2573
|
+
function convertMixamoToVrmAnimation(source, clip, options) {
|
|
2574
|
+
const core = requireInitializedEmbodyCore();
|
|
2575
|
+
if (clip.blendMode !== NormalAnimationBlendMode) {
|
|
2576
|
+
throw new Error("VRMA conversion requires absolute local animation keys; additive clips are unsupported");
|
|
2577
|
+
}
|
|
2578
|
+
const { rig, objects, ids } = options.referencePose ? inspectReference(source, options.referencePose, options.metersPerUnit, true) : inspectSkinBind(source, options.metersPerUnit);
|
|
2579
|
+
const tracks = clip.tracks.map((track) => {
|
|
2580
|
+
const match = /^(.*)\.(quaternion|position|scale)$/.exec(track.name);
|
|
2581
|
+
if (!match) throw new Error(`Unsupported Mixamo track ${track.name}`);
|
|
2582
|
+
const bone = uniqueBone(objects, match[1]);
|
|
2583
|
+
const interpolation = track.getInterpolation();
|
|
2584
|
+
if (interpolation !== InterpolateLinear && interpolation !== InterpolateDiscrete) {
|
|
2585
|
+
throw new Error(`Unsupported interpolation on ${track.name}`);
|
|
2586
|
+
}
|
|
2587
|
+
return {
|
|
2588
|
+
node: ids.get(bone),
|
|
2589
|
+
path: match[2] === "quaternion" ? "rotation" : match[2] === "position" ? "translation" : "scale",
|
|
2590
|
+
interpolation: interpolation === InterpolateDiscrete ? "STEP" : "LINEAR",
|
|
2591
|
+
times: Array.from(track.times),
|
|
2592
|
+
values: Array.from(track.values)
|
|
2593
|
+
};
|
|
2594
|
+
});
|
|
2595
|
+
const normalized = core.normalize_mixamo_animation(JSON.stringify({ version: 1, name: clip.name, durationSeconds: clip.duration, rig, tracks }));
|
|
2596
|
+
return core.encode_vrma_animation(normalized);
|
|
2597
|
+
}
|
|
2598
|
+
function inspectVrmAnimation(bytes) {
|
|
2599
|
+
if (bytes.byteLength > MAX_BYTES) throw new Error("VRMA exceeds 64 MiB");
|
|
2600
|
+
return JSON.parse(requireInitializedEmbodyCore().decode_vrma_animation(bytes));
|
|
2601
|
+
}
|
|
2602
|
+
function createAnimationClipFromVrmAnimation(bytes, target, options) {
|
|
2603
|
+
const core = requireInitializedEmbodyCore();
|
|
2604
|
+
const document2 = inspectVrmAnimation(bytes);
|
|
2605
|
+
const { rig, objects, ids } = inspectReference(target, options.referencePose, options.metersPerUnit);
|
|
2606
|
+
let humanoidBones = options.humanoidBones;
|
|
2607
|
+
if (!humanoidBones) {
|
|
2608
|
+
if (!options.profile) throw new Error("VRMA target requires a characterized profile or explicit humanoidBones");
|
|
2609
|
+
const inspection = new ThreeModelInspector().inspectModel(target, { referencePose: options.referencePose });
|
|
2610
|
+
const result = JSON.parse(core.validate_humanoid_characterization(JSON.stringify(options.profile), JSON.stringify(inspection.descriptor)));
|
|
2611
|
+
if (!result.valid) throw new Error(`Cannot characterize VRMA target: ${result.errors.join("; ")}`);
|
|
2612
|
+
humanoidBones = Object.fromEntries(Object.entries(result.roles).map(([role, binding]) => [role, binding.boneName]));
|
|
2613
|
+
}
|
|
2614
|
+
for (const [role, name] of Object.entries(humanoidBones)) {
|
|
2615
|
+
if (role !== "leftEye" && role !== "rightEye") rig.humanoidBones[role] = ids.get(uniqueBone(objects, name));
|
|
2616
|
+
}
|
|
2617
|
+
const retargeted = JSON.parse(core.retarget_vrma_animation(JSON.stringify(document2), JSON.stringify(rig)));
|
|
2618
|
+
const byId = new Map(objects.map((node) => [ids.get(node), node]));
|
|
2619
|
+
const tracks = retargeted.tracks.map((track) => {
|
|
2620
|
+
const node = byId.get(track.node);
|
|
2621
|
+
if (!node) throw new Error(`Retargeted node ${track.node} is not in the target model`);
|
|
2622
|
+
const interpolation = track.interpolation === "STEP" ? InterpolateDiscrete : InterpolateLinear;
|
|
2623
|
+
return track.path === "rotation" ? new QuaternionKeyframeTrack(`${node.uuid}.quaternion`, track.times, track.values, interpolation) : new VectorKeyframeTrack(`${node.uuid}.position`, track.times, track.values, interpolation);
|
|
2624
|
+
});
|
|
2625
|
+
return new AnimationClip(options.name ?? document2.name, retargeted.durationSeconds, tracks);
|
|
2626
|
+
}
|
|
2627
|
+
function convertMixamoFbxToVrmAnimations(bytes, options) {
|
|
2628
|
+
requireInitializedEmbodyCore();
|
|
2629
|
+
if (!bytes.byteLength || bytes.byteLength > MAX_BYTES) throw new Error("FBX must be between 1 byte and 64 MiB");
|
|
2630
|
+
const textures = /* @__PURE__ */ new Set();
|
|
2631
|
+
class AnimationTextureLoader extends TextureLoader {
|
|
2632
|
+
load(url) {
|
|
2633
|
+
if (url.startsWith("blob:")) URL.revokeObjectURL(url);
|
|
2634
|
+
const texture = new Texture();
|
|
2635
|
+
textures.add(texture);
|
|
2636
|
+
return texture;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
const manager = new LoadingManager();
|
|
2640
|
+
manager.addHandler(/.*/, new AnimationTextureLoader(manager));
|
|
2641
|
+
manager.setURLModifier(() => {
|
|
2642
|
+
throw new Error("External FBX resources are not allowed for animation conversion");
|
|
2643
|
+
});
|
|
2644
|
+
let source;
|
|
2645
|
+
try {
|
|
2646
|
+
source = new FBXLoader(manager).parse(bytes, "");
|
|
2647
|
+
if (!source.animations.length) throw new Error("FBX has no animation clips");
|
|
2648
|
+
let hasSkin = false;
|
|
2649
|
+
source.traverse((node) => {
|
|
2650
|
+
if (node.isSkinnedMesh) hasSkin = true;
|
|
2651
|
+
});
|
|
2652
|
+
const referencePose = hasSkin ? void 0 : captureModelReferencePose(source);
|
|
2653
|
+
return source.animations.map((clip) => ({ name: clip.name, bytes: convertMixamoToVrmAnimation(source, clip, { ...options, referencePose }) }));
|
|
2654
|
+
} finally {
|
|
2655
|
+
const materials2 = /* @__PURE__ */ new Set();
|
|
2656
|
+
source?.traverse((node) => {
|
|
2657
|
+
const mesh = node;
|
|
2658
|
+
mesh.geometry?.dispose();
|
|
2659
|
+
if (mesh.material) for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) materials2.add(material);
|
|
2660
|
+
node.skeleton?.dispose();
|
|
2661
|
+
});
|
|
2662
|
+
for (const material of materials2) material.dispose();
|
|
2663
|
+
for (const texture of textures) texture.dispose();
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
var json = (value) => JSON.stringify(value ?? {});
|
|
2667
|
+
function request2(op, payload = {}) {
|
|
2668
|
+
return JSON.parse(requireInitializedEmbodyCore().embody_request(json({ op, payload })));
|
|
2669
|
+
}
|
|
2670
|
+
var getPreset = (id) => request2("preset.get", { id });
|
|
2671
|
+
var extendCharacterConfigWithPreset = (config) => request2("profile.extendConfig", { config });
|
|
2672
|
+
var extractProfileOverrides = (config) => request2("profile.extractOverrides", { config });
|
|
2673
|
+
var mergeCharacterRegionsByName = (base, override) => request2("profile.mergeRegions", { base, override });
|
|
2674
|
+
var getMeshNamesForAUProfile = (profile, auId) => request2("profile.getMeshNamesForAU", { profile, auId });
|
|
2675
|
+
var resolveMorphMeshSelections = (profile, categories, availableNames) => request2("profile.resolveMorphMeshSelections", { profile, categories, availableNames });
|
|
2676
|
+
var getHumanoidSpecification = () => request2("humanoid.getSpecification");
|
|
2677
|
+
var setAUMorphTargets = (profile, auId, side, targets) => request2("profile.setAUMorphTargets", { profile, auId, side, targets });
|
|
2678
|
+
var setHumanoidRoleBinding = (profile, role, boneName) => request2("profile.setHumanoidRoleBinding", { profile, role, boneName });
|
|
2679
|
+
var resolveHumanoidCharacterization = (profile) => request2("profile.resolveHumanoidCharacterization", { profile });
|
|
2680
|
+
var hasLeftRightMorphs = (profile, auId) => request2("profile.hasLeftRightMorphs", { profile, auId });
|
|
2681
|
+
var isMixedAU = (profile, auId) => request2("profile.isMixedAU", { profile, auId });
|
|
2682
|
+
var resolveVisemeProfile = (profile) => request2("profile.resolveVisemes", { profile });
|
|
2683
|
+
var isMaxDegreesOnlyAxisBindingUpdate = (update) => request2("bone.isMaxDegreesOnlyUpdate", { update });
|
|
2684
|
+
var findNodeKeyForBone = (boneName, profile) => profile ? request2("bone.findNodeKey", { profile, boneName }) : null;
|
|
2685
|
+
var ensureBoneNodeKey = (profile, boneName) => request2("bone.ensureNodeKey", { profile, boneName });
|
|
2686
|
+
var ensureBilateralBoneNodeKeys = (profile, leftBoneName, rightBoneName) => request2("bone.ensureBilateralNodeKeys", { profile, leftBoneName, rightBoneName });
|
|
2687
|
+
var resolveBoneNameForNodeKey = (profile, nodeKey) => request2("bone.resolveBoneName", { profile, nodeKey });
|
|
2688
|
+
var stripConfiguredBoneAffixes = (profile, boneName) => request2("bone.stripAffixes", { profile: profile ?? {}, boneName });
|
|
2689
|
+
var inferChiralBoneNamePair = (boneNames, selectedBoneName) => request2("bone.inferChiralPair", { boneNames, selectedBoneName });
|
|
2690
|
+
var formatAxisLabel = (axis) => request2("bone.formatAxisLabel", { axis });
|
|
2691
|
+
var formatAxisDirectionLabel = (axis, direction) => request2("bone.formatDirectionLabel", { axis, direction });
|
|
2692
|
+
var getAxisFromChannel = (channel) => channel ? request2("bone.getAxisFromChannel", { channel }) : null;
|
|
2693
|
+
var getBoneAxisBindingState = (profile, boneName, axis) => request2("bone.getAxisState", { profile, boneName, axis });
|
|
2694
|
+
var resolveBoneAxisChannel = (profile, boneName, axis) => request2("bone.resolveAxisChannel", { profile, boneName, axis });
|
|
2695
|
+
var getBilateralAxisBindingState = (profile, boneName, axis) => request2("bone.getBilateralAxisState", { profile, boneName, axis });
|
|
2696
|
+
var getAUBoneBindingState = (profile, auId) => request2("bone.getAUBindingState", { profile, auId });
|
|
2697
|
+
var getAUBoneBindingStates = (profile, auIds) => request2("bone.getAUBindingStates", {
|
|
2698
|
+
profile: {
|
|
2699
|
+
auToBones: profile.auToBones,
|
|
2700
|
+
boneNodes: profile.boneNodes,
|
|
2701
|
+
bonePrefix: profile.bonePrefix,
|
|
2702
|
+
boneSuffix: profile.boneSuffix,
|
|
2703
|
+
suffixPattern: profile.suffixPattern,
|
|
2704
|
+
compositeRotations: profile.compositeRotations,
|
|
2705
|
+
continuumPairs: profile.continuumPairs,
|
|
2706
|
+
humanoidCharacterization: profile.humanoidCharacterization
|
|
2707
|
+
},
|
|
2708
|
+
auIds
|
|
2709
|
+
});
|
|
2710
|
+
var inferEyeControlScope = (profile, binding) => request2("bone.inferEyeScope", { profile, binding });
|
|
2711
|
+
var inferEyeControlFamily = (binding) => request2("bone.inferEyeFamily", { binding });
|
|
2712
|
+
var applyBoneAxisBindingUpdate = (profile, boneName, axis, update) => request2("bone.applyAxisUpdate", { profile, boneName, axis, update });
|
|
2713
|
+
var applyBilateralAxisBindingUpdate = (profile, boneName, axis, scope, update) => request2("bone.applyBilateralAxisUpdate", { profile, boneName, axis, scope, update });
|
|
2714
|
+
var applyAUBoneBindingUpdate = (profile, auId, update) => request2("bone.applyAUBindingUpdate", { profile, auId, update });
|
|
2715
|
+
var createBoneAxisAu = (profile, boneName, axis, direction, options = {}) => request2("bone.createAxisAU", { profile, boneName, axis, direction, name: options.name });
|
|
2716
|
+
var createBilateralBoneAxisAu = (profile, boneName, axis, scope, direction, options = {}) => request2("bone.createBilateralAxisAU", { profile, boneName, axis, scope, direction, name: options.name });
|
|
2717
|
+
var buildBoneAuOptions = (profile) => request2("bone.buildAUOptions", { profile });
|
|
2718
|
+
var isJointControlAuInfo = (info) => request2("bone.isJointControlInfo", { info });
|
|
2719
|
+
var classifyAuAsJointControl = (profile, auId, options = {}) => request2("bone.classifyJointControl", { profile, auId, boneName: options.boneName, nodeKey: options.nodeKey });
|
|
2720
|
+
var resolveContinuumDisplayLabel = (profile, negativeAuId, positiveAuId) => request2("bone.resolveContinuumLabel", { profile, negativeAuId, positiveAuId }) ?? `AU ${negativeAuId} <-> AU ${positiveAuId}`;
|
|
2721
|
+
function inspectModel(model, meshes, profile) {
|
|
2722
|
+
return new ThreeModelInspector().inspectModel(model, { meshes, profile });
|
|
2723
|
+
}
|
|
2724
|
+
function extractModelData(model, meshes, animations = []) {
|
|
2725
|
+
const inspection = inspectModel(model, meshes);
|
|
2726
|
+
return JSON.parse(requireInitializedEmbodyCore().extract_model_data_json(json(inspection.descriptor), json(serializeAnimationClips(model, animations, inspection))));
|
|
2727
|
+
}
|
|
2728
|
+
async function analyzeModel(options) {
|
|
2729
|
+
const source = options.source;
|
|
2730
|
+
const model = source.type === "gltf" ? source.gltf.scene ?? source.gltf.scenes[0] : source.model;
|
|
2731
|
+
const animations = source.type === "gltf" ? source.gltf.animations : source.animations ?? [];
|
|
2732
|
+
const inspection = inspectModel(model, source.type === "runtime" ? source.meshes : void 0);
|
|
2733
|
+
return JSON.parse(requireInitializedEmbodyCore().analyze_model_descriptor(json(inspection.descriptor), json(serializeAnimationClips(model, animations, inspection)), options.preset ? json(options.preset) : "", json({ suggestCorrections: Boolean(options.suggestCorrections) })));
|
|
2734
|
+
}
|
|
2735
|
+
function validateMappings(meshes, skeleton, profile, options = {}) {
|
|
2736
|
+
let nextMorphId = 1;
|
|
2737
|
+
const morphTargets = [];
|
|
2738
|
+
const descriptorMeshes = (meshes ?? []).map((value, index) => {
|
|
2739
|
+
const mesh = value;
|
|
2740
|
+
const id = index + 1;
|
|
2741
|
+
const morphTargetIds = Object.entries(mesh.morphTargetDictionary ?? mesh.geometry?.morphTargetDictionary ?? {}).map(([name, hostIndex]) => {
|
|
2742
|
+
const morphId = nextMorphId++;
|
|
2743
|
+
morphTargets.push({ id: morphId, meshId: id, name, hostIndex });
|
|
2744
|
+
return morphId;
|
|
2745
|
+
});
|
|
2746
|
+
return { id, name: mesh.name ?? "", morphTargetIds, visible: true };
|
|
2747
|
+
});
|
|
2748
|
+
const bones = (skeleton?.bones ?? []).map((bone, index) => ({ id: index + 1, name: typeof bone === "string" ? bone : bone.name ?? "" }));
|
|
2749
|
+
return JSON.parse(requireInitializedEmbodyCore().validate_profile_model(json(profile), json({ meshes: descriptorMeshes, morphTargets, bones, objects: [] }), json(options)));
|
|
2750
|
+
}
|
|
2751
|
+
function validateHumanoidCharacterization(model, profile) {
|
|
2752
|
+
return JSON.parse(requireInitializedEmbodyCore().validate_humanoid_characterization(json(profile), json(inspectModel(model, void 0, profile).descriptor)));
|
|
2753
|
+
}
|
|
2754
|
+
function normalizeHairColorAppearance(value, fallback = DEFAULT_HAIR_COLOR_APPEARANCE) {
|
|
2755
|
+
return JSON.parse(requireInitializedEmbodyCore().normalize_hair_color_appearance_json(json(value), json(fallback)));
|
|
2756
|
+
}
|
|
2757
|
+
var getHumanoidSkeletonTemplate = (id) => HUMANOID_SKELETON_TEMPLATES.find((template) => template.id === id) ?? null;
|
|
2758
|
+
function getHumanoidSkeletonTemplateBoneNames(template = CC4_HUMANOID_SKELETON_TEMPLATE) {
|
|
2759
|
+
return JSON.parse(requireInitializedEmbodyCore().humanoid_skeleton_template_bone_names(json(template)));
|
|
2760
|
+
}
|
|
2761
|
+
function createValidationSkeletonFromHumanoidTemplate(template = CC4_HUMANOID_SKELETON_TEMPLATE) {
|
|
2762
|
+
return JSON.parse(requireInitializedEmbodyCore().create_validation_skeleton_json(json(template)));
|
|
2763
|
+
}
|
|
2764
|
+
function computeHumanoidSkeletonTemplateRestBounds(template) {
|
|
2765
|
+
return Array.from(requireInitializedEmbodyCore().compute_humanoid_skeleton_template_rest_bounds(json(template)));
|
|
2766
|
+
}
|
|
2767
|
+
function extractHumanoidSkeletonTemplateFromModel(model, options) {
|
|
2768
|
+
return JSON.parse(requireInitializedEmbodyCore().extract_humanoid_skeleton_template_json(json(inspectModel(model).descriptor), json(options)));
|
|
2769
|
+
}
|
|
2474
2770
|
function withComponentTracks(clip) {
|
|
2475
2771
|
let expanded = false;
|
|
2476
2772
|
const tracks = clip.tracks.flatMap((track) => {
|
|
@@ -2483,16 +2779,18 @@ function withComponentTracks(clip) {
|
|
|
2483
2779
|
if (!Number.isInteger(width) || width < 1 || euler && width !== 3) throw new Error(`Invalid component track ${JSON.stringify(track.name)}.`);
|
|
2484
2780
|
expanded = true;
|
|
2485
2781
|
return Array.from({ length: width }, (_, component) => {
|
|
2486
|
-
const
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2782
|
+
const values = track.values.slice(0, track.times.length * parts);
|
|
2783
|
+
for (let sample = 0; sample < values.length; sample += 1) values[sample] = track.values[sample * width + component];
|
|
2784
|
+
const Track = track.constructor;
|
|
2785
|
+
const scalar = new Track(`${track.name}[${euler ? ["x", "y", "z"][component] : component}]`, track.times.slice(), values);
|
|
2786
|
+
scalar.createInterpolant = track.createInterpolant;
|
|
2490
2787
|
return scalar;
|
|
2491
2788
|
});
|
|
2492
2789
|
});
|
|
2493
2790
|
if (!expanded) return clip;
|
|
2494
|
-
const
|
|
2495
|
-
indexed
|
|
2791
|
+
const Clip = clip.constructor;
|
|
2792
|
+
const indexed = new Clip(clip.name, clip.duration, tracks, clip.blendMode);
|
|
2793
|
+
indexed.userData = JSON.parse(JSON.stringify(clip.userData));
|
|
2496
2794
|
return indexed;
|
|
2497
2795
|
}
|
|
2498
2796
|
function concreteTrackName(target, inspection) {
|
|
@@ -2564,6 +2862,23 @@ var ThreeNativePlaybackConnector = class {
|
|
|
2564
2862
|
__publicField(this, "disposed", false);
|
|
2565
2863
|
__publicField(this, "onError");
|
|
2566
2864
|
__publicField(this, "onRelease");
|
|
2865
|
+
__publicField(this, "afterSample");
|
|
2866
|
+
__publicField(this, "afterFrame", (deltaSeconds) => {
|
|
2867
|
+
this.flushEvents();
|
|
2868
|
+
if (!this.disposed) this.binding.onFrame?.(deltaSeconds);
|
|
2869
|
+
});
|
|
2870
|
+
__publicField(this, "sampleResponse", (deltaSeconds) => {
|
|
2871
|
+
if (this.disposed || !this.renderingEnabled) return;
|
|
2872
|
+
try {
|
|
2873
|
+
this.afterSample?.(deltaSeconds);
|
|
2874
|
+
} catch (error) {
|
|
2875
|
+
const failure = asError(error);
|
|
2876
|
+
queueMicrotask(() => {
|
|
2877
|
+
if (this.onError) this.onError(failure);
|
|
2878
|
+
else throw failure;
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
});
|
|
2567
2882
|
__publicField(this, "finished", (event) => {
|
|
2568
2883
|
const handle = this.actionHandles.get(event.action);
|
|
2569
2884
|
if (!handle || this.disposed) return;
|
|
@@ -2586,8 +2901,14 @@ var ThreeNativePlaybackConnector = class {
|
|
|
2586
2901
|
this.binding = { ...binding, mixer: binding.mixer ?? new AnimationMixer(binding.model) };
|
|
2587
2902
|
this.onError = binding.onError;
|
|
2588
2903
|
this.onRelease = binding.onRelease;
|
|
2904
|
+
this.afterSample = binding.afterSample;
|
|
2589
2905
|
this.binding.mixer.addEventListener("finished", this.finished);
|
|
2590
|
-
this.rendering = bindPlaybackRendering(
|
|
2906
|
+
this.rendering = bindPlaybackRendering(
|
|
2907
|
+
binding.model,
|
|
2908
|
+
this.binding.mixer,
|
|
2909
|
+
this.afterFrame,
|
|
2910
|
+
this.sampleResponse
|
|
2911
|
+
);
|
|
2591
2912
|
}
|
|
2592
2913
|
/** Prepare bindings first; old actions are released before the new binding is exposed. */
|
|
2593
2914
|
rebind(modelKey, profileKey, binding = this.binding) {
|
|
@@ -2598,7 +2919,12 @@ var ThreeNativePlaybackConnector = class {
|
|
|
2598
2919
|
this.rendering.dispose();
|
|
2599
2920
|
this.binding = { ...binding, mixer: binding.mixer ?? new AnimationMixer(binding.model) };
|
|
2600
2921
|
this.binding.mixer.addEventListener("finished", this.finished);
|
|
2601
|
-
this.rendering = bindPlaybackRendering(
|
|
2922
|
+
this.rendering = bindPlaybackRendering(
|
|
2923
|
+
binding.model,
|
|
2924
|
+
this.binding.mixer,
|
|
2925
|
+
this.afterFrame,
|
|
2926
|
+
this.sampleResponse
|
|
2927
|
+
);
|
|
2602
2928
|
this.rendering.setEnabled(this.renderingEnabled);
|
|
2603
2929
|
});
|
|
2604
2930
|
}
|
|
@@ -2932,7 +3258,15 @@ var intensity = (options, fallback = 1) => {
|
|
|
2932
3258
|
const value = options.intensityScale ?? options.intensity ?? options.weight;
|
|
2933
3259
|
return finite2(value) ? Math.max(0, Math.min(2, value)) : fallback;
|
|
2934
3260
|
};
|
|
3261
|
+
var weightRatio = (outgoing, options) => outgoing.getEffectiveWeight() / (finite2(options.mixerWeight) && options.mixerWeight > 0 ? options.mixerWeight : 1);
|
|
3262
|
+
var weighted = (sample, neutral, ratio, quaternion) => {
|
|
3263
|
+
if (ratio === 1) return sample;
|
|
3264
|
+
if (quaternion) return new Quaternion().fromArray(neutral ?? [0, 0, 0, 1]).slerp(new Quaternion().fromArray(sample), ratio).toArray();
|
|
3265
|
+
return sample.map((value, axis) => (neutral?.[axis] ?? 0) + (value - (neutral?.[axis] ?? 0)) * ratio);
|
|
3266
|
+
};
|
|
2935
3267
|
var presetId = (value) => value?.trim().toLowerCase() || "cc4";
|
|
3268
|
+
var HAIR_RESPONSE = "__embody_hair_response";
|
|
3269
|
+
var HAIR_IMPULSE_CLIP = "__embody_hair_impulse";
|
|
2936
3270
|
function supportsRustEmbodyPreset(value) {
|
|
2937
3271
|
return requireInitializedEmbodyCore().has_preset(presetId(value));
|
|
2938
3272
|
}
|
|
@@ -2956,10 +3290,12 @@ var Embody = class _Embody {
|
|
|
2956
3290
|
__publicField(this, "profileMode");
|
|
2957
3291
|
__publicField(this, "preset");
|
|
2958
3292
|
__publicField(this, "sources", /* @__PURE__ */ new Map());
|
|
3293
|
+
__publicField(this, "vrmAnimations", /* @__PURE__ */ new Map());
|
|
2959
3294
|
__publicField(this, "playbacks", /* @__PURE__ */ new Map());
|
|
2960
3295
|
__publicField(this, "current", /* @__PURE__ */ new Map());
|
|
2961
3296
|
__publicField(this, "playDepth", 0);
|
|
2962
3297
|
__publicField(this, "pendingEvents", []);
|
|
3298
|
+
__publicField(this, "pendingReleases", []);
|
|
2963
3299
|
__publicField(this, "bindingGeneration", 0);
|
|
2964
3300
|
__publicField(this, "disposed", false);
|
|
2965
3301
|
__publicField(this, "rendering", true);
|
|
@@ -2967,6 +3303,7 @@ var Embody = class _Embody {
|
|
|
2967
3303
|
__publicField(this, "nextLiveTarget", 0);
|
|
2968
3304
|
__publicField(this, "liveTargets", /* @__PURE__ */ new Map());
|
|
2969
3305
|
__publicField(this, "hairObjects", /* @__PURE__ */ new Map());
|
|
3306
|
+
__publicField(this, "hairHeadObservation");
|
|
2970
3307
|
__publicField(this, "hairConfig", {});
|
|
2971
3308
|
__publicField(this, "hairAppearance", {
|
|
2972
3309
|
hairColor: copy(DEFAULT_HAIR_COLOR_APPEARANCE),
|
|
@@ -3016,6 +3353,7 @@ var Embody = class _Embody {
|
|
|
3016
3353
|
this.assertAlive();
|
|
3017
3354
|
const model = options.model;
|
|
3018
3355
|
if (!model?.isObject3D) throw new Error("onReady requires a Three Object3D model.");
|
|
3356
|
+
this.applier.clearMeshHighlights();
|
|
3019
3357
|
const reference = this.references.get(model) ?? options.referencePose ?? (!this.boundModel ? this.config.referencePose : void 0) ?? captureModelReferencePose(model);
|
|
3020
3358
|
const inspection = this.inspector.inspectModel(model, { meshes: options.meshes, profile: this.profile, referencePose: reference });
|
|
3021
3359
|
this.bind(model, reference, inspection, this.profileSource, this.profile);
|
|
@@ -3055,7 +3393,10 @@ var Embody = class _Embody {
|
|
|
3055
3393
|
}
|
|
3056
3394
|
this.core.free();
|
|
3057
3395
|
this.core = core;
|
|
3058
|
-
if (replacingModel)
|
|
3396
|
+
if (replacingModel) {
|
|
3397
|
+
this.sources.clear();
|
|
3398
|
+
this.vrmAnimations.clear();
|
|
3399
|
+
}
|
|
3059
3400
|
this.boundModel = model;
|
|
3060
3401
|
this.inspection = inspection;
|
|
3061
3402
|
this.referenceBindings = referenceBindings;
|
|
@@ -3076,10 +3417,17 @@ var Embody = class _Embody {
|
|
|
3076
3417
|
this.connector = new ThreeNativePlaybackConnector(session, {
|
|
3077
3418
|
model,
|
|
3078
3419
|
inspection,
|
|
3420
|
+
onFrame: (deltaSeconds) => {
|
|
3421
|
+
if (this.disposed) return;
|
|
3422
|
+
this.applier.applyMeshHighlightFrame(parse(this.core.advance_mesh_highlight(deltaSeconds)));
|
|
3423
|
+
},
|
|
3079
3424
|
onRelease: (id) => {
|
|
3080
3425
|
const playback = this.playbacks.get(id);
|
|
3081
|
-
if (playback?.terminal)
|
|
3082
|
-
|
|
3426
|
+
if (!playback?.terminal) return;
|
|
3427
|
+
if (this.playDepth) this.pendingReleases.push(playback);
|
|
3428
|
+
else this.forget(playback);
|
|
3429
|
+
},
|
|
3430
|
+
afterSample: (mixerDeltaSeconds) => this.sampleHairResponse(mixerDeltaSeconds, true)
|
|
3083
3431
|
});
|
|
3084
3432
|
} catch (error) {
|
|
3085
3433
|
session.free();
|
|
@@ -3090,17 +3438,20 @@ var Embody = class _Embody {
|
|
|
3090
3438
|
this.connector.setTimeScale(this.timeScale);
|
|
3091
3439
|
this.connector.subscribe((event) => this.onPlaybackEvent(event));
|
|
3092
3440
|
this.hairConfig = parse(this.core.get_hair_config_json());
|
|
3441
|
+
this.bindHairHead(model, inspection);
|
|
3093
3442
|
this.registerHairObjects(inspection.allMeshes.filter((mesh) => ["hair", "eyebrow"].includes(this.meshCategory(mesh))));
|
|
3094
3443
|
if (preserveLiveState) this.commitLive();
|
|
3095
3444
|
}
|
|
3096
3445
|
bindModel(meshes) {
|
|
3097
3446
|
const model = this.requireModel();
|
|
3447
|
+
this.applier.clearMeshHighlights();
|
|
3098
3448
|
const reference = this.references.get(model);
|
|
3099
3449
|
const inspection = this.inspector.inspectModel(model, { meshes, profile: this.profile, referencePose: reference });
|
|
3100
3450
|
this.bind(model, reference, inspection, this.profileSource, this.profile);
|
|
3101
3451
|
}
|
|
3102
3452
|
extendReferencePose() {
|
|
3103
3453
|
const model = this.requireModel();
|
|
3454
|
+
this.applier.clearMeshHighlights();
|
|
3104
3455
|
const reference = extendModelReferencePose(model, this.references.get(model));
|
|
3105
3456
|
const inspection = this.inspector.inspectModel(model, { profile: this.profile, referencePose: reference });
|
|
3106
3457
|
this.bind(model, reference, inspection, this.profileSource, this.profile);
|
|
@@ -3115,6 +3466,7 @@ var Embody = class _Embody {
|
|
|
3115
3466
|
this.profile = profile;
|
|
3116
3467
|
return;
|
|
3117
3468
|
}
|
|
3469
|
+
this.applier.clearMeshHighlights();
|
|
3118
3470
|
const reference = this.references.get(this.boundModel);
|
|
3119
3471
|
const inspection = this.inspector.inspectModel(this.boundModel, { profile, referencePose: reference });
|
|
3120
3472
|
this.bind(this.boundModel, reference, inspection, source, profile, false);
|
|
@@ -3139,12 +3491,16 @@ var Embody = class _Embody {
|
|
|
3139
3491
|
const playback = this.playbacks.get(event.handle);
|
|
3140
3492
|
if (!playback) return;
|
|
3141
3493
|
playback.terminal = true;
|
|
3142
|
-
if (!playback.connector.getAction(playback.id))
|
|
3143
|
-
this.playbacks.delete(playback.id);
|
|
3144
|
-
if (this.current.get(playback.source.name) === playback) this.current.delete(playback.source.name);
|
|
3145
|
-
}
|
|
3494
|
+
if (!playback.connector.getAction(playback.id)) this.forget(playback);
|
|
3146
3495
|
playback.notify({ ...event, actionId: event.handle, clipName: playback.source.name });
|
|
3147
3496
|
}
|
|
3497
|
+
flushPendingPlaybackEvents() {
|
|
3498
|
+
while (!this.playDepth && this.pendingReleases.length) {
|
|
3499
|
+
const playback = this.pendingReleases.shift();
|
|
3500
|
+
if (this.playbacks.get(playback.id) === playback) this.forget(playback);
|
|
3501
|
+
}
|
|
3502
|
+
while (!this.playDepth && this.pendingEvents.length) this.onPlaybackEvent(this.pendingEvents.shift());
|
|
3503
|
+
}
|
|
3148
3504
|
compile(source, options) {
|
|
3149
3505
|
const buildOptions = { ..."options" in source ? source.options : {}, ...options, intensityScale: intensity(options) };
|
|
3150
3506
|
switch (source.kind) {
|
|
@@ -3214,7 +3570,10 @@ var Embody = class _Embody {
|
|
|
3214
3570
|
const candidates = outgoing?.getClip().tracks;
|
|
3215
3571
|
const oldTrack = candidates?.[index]?.name === track.name ? candidates[index] : candidates?.find((candidate) => candidate.name === track.name);
|
|
3216
3572
|
if (oldTrack && outgoing) {
|
|
3217
|
-
|
|
3573
|
+
const sample = Array.from(oldTrack.createInterpolant().evaluate(outgoing.time));
|
|
3574
|
+
const quaternion = track.ValueTypeName === "quaternion";
|
|
3575
|
+
const ratio = weightRatio(outgoing, options);
|
|
3576
|
+
previous = outgoing.blendMode === AdditiveAnimationBlendMode ? weighted(sample, void 0, ratio, quaternion) : weighted(sample, rest, ratio, quaternion);
|
|
3218
3577
|
if (outgoing.blendMode === AdditiveAnimationBlendMode && rest) {
|
|
3219
3578
|
previous = track.ValueTypeName === "quaternion" ? new Quaternion().fromArray(rest).multiply(new Quaternion().fromArray(previous)).toArray() : previous.map((value, axis) => value + rest[axis]);
|
|
3220
3579
|
}
|
|
@@ -3350,7 +3709,7 @@ var Embody = class _Embody {
|
|
|
3350
3709
|
validateEasing(options) {
|
|
3351
3710
|
if (options.easing !== void 0 && !["linear", "easeIn", "easeOut", "easeInOut", "easeInOutCubic"].includes(options.easing)) throw new Error(`Unknown playback easing: ${options.easing}`);
|
|
3352
3711
|
}
|
|
3353
|
-
handle(source, options, autoPlay) {
|
|
3712
|
+
handle(source, options, autoPlay, owner = "caller") {
|
|
3354
3713
|
options = { ..."options" in source ? source.options : {}, ...options };
|
|
3355
3714
|
const listeners = /* @__PURE__ */ new Set();
|
|
3356
3715
|
const generation = this.bindingGeneration;
|
|
@@ -3378,8 +3737,10 @@ var Embody = class _Embody {
|
|
|
3378
3737
|
const connector = this.requireConnector();
|
|
3379
3738
|
const inheritedStarts = /* @__PURE__ */ new Map();
|
|
3380
3739
|
const clip = this.materialize(source, options, inheritedStarts);
|
|
3381
|
-
const hairMotion = this.authoredHeadMotion(clip);
|
|
3382
3740
|
const previous = this.current.get(source.name);
|
|
3741
|
+
const outgoing = previous ? connector.getAction(previous.id) : void 0;
|
|
3742
|
+
const predecessorTime = outgoing ? outgoing.time : Number.NaN;
|
|
3743
|
+
const predecessorWeightRatio = outgoing ? weightRatio(outgoing, options) : 1;
|
|
3383
3744
|
const settings = this.nativeOptions(options, clip);
|
|
3384
3745
|
if (previous && connector.getAction(previous.id)) settings.replace = previous.id;
|
|
3385
3746
|
if (current) newPromise();
|
|
@@ -3391,12 +3752,30 @@ var Embody = class _Embody {
|
|
|
3391
3752
|
id = connector.playNativeClip(clip, settings);
|
|
3392
3753
|
} catch (error) {
|
|
3393
3754
|
this.playDepth -= 1;
|
|
3394
|
-
|
|
3755
|
+
this.flushPendingPlaybackEvents();
|
|
3756
|
+
throw error;
|
|
3757
|
+
}
|
|
3758
|
+
try {
|
|
3759
|
+
if (source.kind === "curves") {
|
|
3760
|
+
this.core.record_semantic_slot(
|
|
3761
|
+
source.name,
|
|
3762
|
+
json2(source.input),
|
|
3763
|
+
intensity(options),
|
|
3764
|
+
options.easing ?? "linear",
|
|
3765
|
+
predecessorTime,
|
|
3766
|
+
predecessorWeightRatio
|
|
3767
|
+
);
|
|
3768
|
+
} else this.core.release_semantic_slot(source.name);
|
|
3769
|
+
} catch (error) {
|
|
3770
|
+
connector.control(id, { type: "stop" });
|
|
3771
|
+
this.playDepth -= 1;
|
|
3772
|
+
this.flushPendingPlaybackEvents();
|
|
3395
3773
|
throw error;
|
|
3396
3774
|
}
|
|
3397
3775
|
const playback = {
|
|
3398
3776
|
id,
|
|
3399
3777
|
source,
|
|
3778
|
+
owner,
|
|
3400
3779
|
options: { ...options, intensityScale: intensity(options) },
|
|
3401
3780
|
connector,
|
|
3402
3781
|
terminal: false,
|
|
@@ -3438,7 +3817,7 @@ var Embody = class _Embody {
|
|
|
3438
3817
|
if (this.connector !== connector || !connector.getAction(id)) return result;
|
|
3439
3818
|
ensure();
|
|
3440
3819
|
const seconds = finite2(playback.options.fadeOutSec) ? playback.options.fadeOutSec : 0;
|
|
3441
|
-
if (seconds > 0
|
|
3820
|
+
if (seconds > 0) connector.fadeOut(id, seconds);
|
|
3442
3821
|
else control({ type: "stop" });
|
|
3443
3822
|
return result;
|
|
3444
3823
|
};
|
|
@@ -3472,11 +3851,7 @@ var Embody = class _Embody {
|
|
|
3472
3851
|
if (finite2(fade) && fade > 0) connector.fadeIn(id, fade);
|
|
3473
3852
|
} finally {
|
|
3474
3853
|
this.playDepth -= 1;
|
|
3475
|
-
|
|
3476
|
-
}
|
|
3477
|
-
if (hairMotion && connector.getAction(id)) {
|
|
3478
|
-
this.playHairImpulse(hairMotion.horizontal, hairMotion.vertical);
|
|
3479
|
-
this.setHairGravity(hairMotion.pitch, clip.duration);
|
|
3854
|
+
this.flushPendingPlaybackEvents();
|
|
3480
3855
|
}
|
|
3481
3856
|
return result;
|
|
3482
3857
|
};
|
|
@@ -3514,7 +3889,10 @@ var Embody = class _Embody {
|
|
|
3514
3889
|
}
|
|
3515
3890
|
forget(playback) {
|
|
3516
3891
|
this.playbacks.delete(playback.id);
|
|
3517
|
-
if (this.current.get(playback.source.name) === playback)
|
|
3892
|
+
if (this.current.get(playback.source.name) === playback) {
|
|
3893
|
+
this.current.delete(playback.source.name);
|
|
3894
|
+
this.core.release_semantic_slot(playback.source.name);
|
|
3895
|
+
}
|
|
3518
3896
|
}
|
|
3519
3897
|
updatePlayback(playback, patch) {
|
|
3520
3898
|
this.validateEasing(patch);
|
|
@@ -3530,6 +3908,7 @@ var Embody = class _Embody {
|
|
|
3530
3908
|
if (["weight", "intensity", "intensityScale", "blendMode", "easing", "balance", "balanceMap", "mixWeights"].some((key) => key in patch)) {
|
|
3531
3909
|
const clip = this.materialize(playback.source, options, playback.inheritedStarts);
|
|
3532
3910
|
connector.replaceClip(playback.id, clip);
|
|
3911
|
+
if (playback.source.kind === "curves") this.core.restyle_semantic_slot(playback.source.name, intensity(options), options.easing ?? "linear");
|
|
3533
3912
|
if ("blendMode" in patch) connector.control(playback.id, { type: "setBlendMode", blendMode: clip.blendMode === AdditiveAnimationBlendMode ? "additive" : "normal" });
|
|
3534
3913
|
}
|
|
3535
3914
|
if ("mixerWeight" in patch) connector.control(playback.id, { type: "setWeight", weight: patch.mixerWeight });
|
|
@@ -3555,6 +3934,38 @@ var Embody = class _Embody {
|
|
|
3555
3934
|
playback.options = options;
|
|
3556
3935
|
if (!connector.getAction(playback.id)) this.forget(playback);
|
|
3557
3936
|
}
|
|
3937
|
+
supportsVrmAnimation() {
|
|
3938
|
+
return true;
|
|
3939
|
+
}
|
|
3940
|
+
convertMixamoFbxToVrmAnimations(bytes, options) {
|
|
3941
|
+
this.requireModel();
|
|
3942
|
+
return convertMixamoFbxToVrmAnimations(bytes, options);
|
|
3943
|
+
}
|
|
3944
|
+
getVrmAnimationInfo(bytes) {
|
|
3945
|
+
this.requireModel();
|
|
3946
|
+
const document2 = inspectVrmAnimation(bytes);
|
|
3947
|
+
return { name: document2.name, durationSeconds: document2.durationSeconds, trackCount: document2.tracks.length };
|
|
3948
|
+
}
|
|
3949
|
+
/** Registers a source; Animation owns the later playback request. */
|
|
3950
|
+
registerVrmAnimation(name, bytes, options) {
|
|
3951
|
+
if (typeof name !== "string" || !name.trim()) throw new Error("VRM Animation requires a nonempty clip name.");
|
|
3952
|
+
const model = this.requireModel();
|
|
3953
|
+
const clip = createAnimationClipFromVrmAnimation(bytes, model, {
|
|
3954
|
+
name,
|
|
3955
|
+
metersPerUnit: options.metersPerUnit,
|
|
3956
|
+
referencePose: this.references.get(model),
|
|
3957
|
+
profile: this.profile
|
|
3958
|
+
});
|
|
3959
|
+
this.registerAnimationClips([clip]);
|
|
3960
|
+
this.vrmAnimations.set(name, { model, clip });
|
|
3961
|
+
}
|
|
3962
|
+
/** Stale editor cleanup must not remove a later same-name native source. */
|
|
3963
|
+
releaseVrmAnimation(name) {
|
|
3964
|
+
const registered = this.vrmAnimations.get(name);
|
|
3965
|
+
const source = this.sources.get(name);
|
|
3966
|
+
if (registered && !this.disposed && registered.model === this.boundModel && source?.kind === "native" && source.clip === registered.clip) this.removeAnimationClip(name);
|
|
3967
|
+
this.vrmAnimations.delete(name);
|
|
3968
|
+
}
|
|
3558
3969
|
registerAnimationClips(clips) {
|
|
3559
3970
|
const model = this.requireModel();
|
|
3560
3971
|
for (const clip of clips) {
|
|
@@ -3566,12 +3977,13 @@ var Embody = class _Embody {
|
|
|
3566
3977
|
}
|
|
3567
3978
|
loadAnimationClips(clips) {
|
|
3568
3979
|
this.stopAllAnimations();
|
|
3980
|
+
this.vrmAnimations.clear();
|
|
3569
3981
|
for (const source of [...this.sources.values()]) if (source.kind === "native") {
|
|
3570
3982
|
this.sources.delete(source.name);
|
|
3571
3983
|
this.core.remove_animation_clip(source.name);
|
|
3572
3984
|
}
|
|
3573
3985
|
this.registerAnimationClips(clips ?? []);
|
|
3574
|
-
this.
|
|
3986
|
+
this.sampleHairResponse(0);
|
|
3575
3987
|
}
|
|
3576
3988
|
removeAnimationClip(name) {
|
|
3577
3989
|
this.stopAnimation(name);
|
|
@@ -3586,12 +3998,12 @@ var Embody = class _Embody {
|
|
|
3586
3998
|
if (!source) throw new Error(`Unknown animation clip: ${name}`);
|
|
3587
3999
|
return this.handle(source, options, true);
|
|
3588
4000
|
}
|
|
3589
|
-
build(source, options, play) {
|
|
4001
|
+
build(source, options, play, owner = "caller") {
|
|
3590
4002
|
this.requireModel();
|
|
3591
4003
|
this.validateEasing(options);
|
|
3592
4004
|
this.compile(source, options);
|
|
3593
4005
|
this.sources.set(source.name, source);
|
|
3594
|
-
return this.handle(source, options, play);
|
|
4006
|
+
return this.handle(source, options, play, owner);
|
|
3595
4007
|
}
|
|
3596
4008
|
buildClip(name, curves, options = {}) {
|
|
3597
4009
|
if (options.semanticAnimation) throw new Error("Decode legacy named animation tracks to numeric AU curves before building a clip.");
|
|
@@ -3624,7 +4036,7 @@ var Embody = class _Embody {
|
|
|
3624
4036
|
this.core.cleanup_snippet(name);
|
|
3625
4037
|
}
|
|
3626
4038
|
crossfadeTo(name, seconds, options = {}) {
|
|
3627
|
-
const outgoing = [...this.current.values()].filter((playback) =>
|
|
4039
|
+
const outgoing = [...this.current.values()].filter((playback) => playback.owner === "caller");
|
|
3628
4040
|
const handle = this.playAnimation(name, { ...options, fadeInSec: seconds });
|
|
3629
4041
|
for (const playback of outgoing) if (playback.connector.getAction(playback.id)) playback.connector.fadeOut(playback.id, seconds);
|
|
3630
4042
|
return handle;
|
|
@@ -3635,28 +4047,35 @@ var Embody = class _Embody {
|
|
|
3635
4047
|
if (playback.connector.getAction(playback.id)) playback.connector.control(playback.id, { type: "stop" });
|
|
3636
4048
|
this.forget(playback);
|
|
3637
4049
|
}
|
|
4050
|
+
/** Stop every caller playback. Live setter targets and hair actions keep running. */
|
|
3638
4051
|
stopAllAnimations() {
|
|
3639
4052
|
for (const playback of [...this.playbacks.values()]) {
|
|
3640
|
-
if (playback.
|
|
4053
|
+
if (playback.owner !== "caller") continue;
|
|
3641
4054
|
if (playback.connector.getAction(playback.id)) playback.connector.control(playback.id, { type: "stop" });
|
|
3642
4055
|
this.forget(playback);
|
|
3643
4056
|
}
|
|
3644
4057
|
}
|
|
3645
4058
|
pauseAnimation(name) {
|
|
4059
|
+
if (name === HAIR_RESPONSE) this.core.set_hair_response_paused(true);
|
|
3646
4060
|
const p = this.current.get(name);
|
|
3647
4061
|
if (p) p.connector.control(p.id, { type: "pause" });
|
|
3648
4062
|
}
|
|
3649
4063
|
resumeAnimation(name) {
|
|
4064
|
+
if (name === HAIR_RESPONSE) {
|
|
4065
|
+
this.core.set_hair_response_paused(false);
|
|
4066
|
+
this.sampleHairResponse(0);
|
|
4067
|
+
}
|
|
3650
4068
|
const p = this.current.get(name);
|
|
3651
4069
|
if (!p) return;
|
|
3652
4070
|
if (p.terminal) p.replay({ ...p.options, time: p.connector.getAction(p.id)?.time ?? 0 });
|
|
3653
4071
|
else p.connector.control(p.id, { type: "resume" });
|
|
3654
4072
|
}
|
|
4073
|
+
// Hair actions follow the hair configuration and Embody.stop()/start(), not caller pause commands.
|
|
3655
4074
|
pauseAllAnimations() {
|
|
3656
|
-
for (const p of this.playbacks.values()) if (!p.terminal) p.connector.control(p.id, { type: "pause" });
|
|
4075
|
+
for (const p of this.playbacks.values()) if (!p.terminal && p.owner !== "hair") p.connector.control(p.id, { type: "pause" });
|
|
3657
4076
|
}
|
|
3658
4077
|
resumeAllAnimations() {
|
|
3659
|
-
for (const name of [...this.current
|
|
4078
|
+
for (const [name, p] of [...this.current]) if (p.owner !== "hair") this.resumeAnimation(name);
|
|
3660
4079
|
}
|
|
3661
4080
|
updateClipParams(name, options) {
|
|
3662
4081
|
const p = this.current.get(name);
|
|
@@ -3757,7 +4176,8 @@ var Embody = class _Embody {
|
|
|
3757
4176
|
const handle = this.handle(
|
|
3758
4177
|
{ name: releaseClip.name, kind: "compiled", clip: releaseClip },
|
|
3759
4178
|
{ loopMode: "once", finalPose: "release", mixerWeight: action.getEffectiveWeight() },
|
|
3760
|
-
true
|
|
4179
|
+
true,
|
|
4180
|
+
"live"
|
|
3761
4181
|
);
|
|
3762
4182
|
connector.fadeOut(handle.actionId, duration);
|
|
3763
4183
|
const record = { track: released, handle, releasing: true };
|
|
@@ -3775,11 +4195,17 @@ var Embody = class _Embody {
|
|
|
3775
4195
|
const outgoing = previous?.handle.actionId && connector.getAction(previous.handle.actionId);
|
|
3776
4196
|
let nativeTrack = track;
|
|
3777
4197
|
if (duration > 0 && outgoing) {
|
|
3778
|
-
const
|
|
4198
|
+
const outgoingTrack = outgoing.getClip().tracks[0];
|
|
4199
|
+
const start = weighted(
|
|
4200
|
+
Array.from(outgoingTrack.createInterpolant().evaluate(outgoing.time)),
|
|
4201
|
+
this.restValues(outgoingTrack),
|
|
4202
|
+
outgoing.getEffectiveWeight(),
|
|
4203
|
+
outgoingTrack.ValueTypeName === "quaternion"
|
|
4204
|
+
);
|
|
3779
4205
|
nativeTrack = { ...track, values: [...start, ...endpoint(track)], inheritStart: false };
|
|
3780
4206
|
}
|
|
3781
4207
|
const targetClip = { ...clip, name, tracks: [nativeTrack] };
|
|
3782
|
-
const handle = this.handle({ name, kind: "compiled", clip: targetClip }, { loopMode: "once", finalPose: "hold" }, true);
|
|
4208
|
+
const handle = this.handle({ name, kind: "compiled", clip: targetClip }, { loopMode: "once", finalPose: "hold" }, true, "live");
|
|
3783
4209
|
this.liveTargets.set(key, { track, handle, releasing: false });
|
|
3784
4210
|
handles.push(handle);
|
|
3785
4211
|
}
|
|
@@ -3824,6 +4250,49 @@ var Embody = class _Embody {
|
|
|
3824
4250
|
getContinuum(negative, positive) {
|
|
3825
4251
|
return this.core.get_continuum(negative, positive);
|
|
3826
4252
|
}
|
|
4253
|
+
/**
|
|
4254
|
+
* One clip slot's own semantic AU contribution right now (`{auId: value}`),
|
|
4255
|
+
* evaluated in Rust at the native action's time and effective weight.
|
|
4256
|
+
* Null when the slot is not playing or was not an AU-curve clip.
|
|
4257
|
+
*/
|
|
4258
|
+
readAUContribution(name) {
|
|
4259
|
+
const playback = this.current.get(name);
|
|
4260
|
+
const action = playback?.connector.getAction(playback.id);
|
|
4261
|
+
return playback && action ? parse(this.core.read_semantic_slot(name, action.time, action.getEffectiveWeight())) : null;
|
|
4262
|
+
}
|
|
4263
|
+
/**
|
|
4264
|
+
* On-demand AU state: live control values, active AU-curve clip
|
|
4265
|
+
* contributions and their sum. Baked/native and typed clips cannot be
|
|
4266
|
+
* inverted to AU values and are listed as `unreadable`, never estimated.
|
|
4267
|
+
*/
|
|
4268
|
+
readAUState() {
|
|
4269
|
+
const active = [];
|
|
4270
|
+
const unreadable = [];
|
|
4271
|
+
for (const [name, playback] of this.current) {
|
|
4272
|
+
const action = playback.connector.getAction(playback.id);
|
|
4273
|
+
if (!action) continue;
|
|
4274
|
+
if (playback.source.kind === "curves") active.push({ name, time: action.time, weight: action.getEffectiveWeight() });
|
|
4275
|
+
else if (playback.source.kind !== "compiled") unreadable.push(name);
|
|
4276
|
+
}
|
|
4277
|
+
return { ...parse(this.core.read_semantic_state(json2(active))), unreadable };
|
|
4278
|
+
}
|
|
4279
|
+
/**
|
|
4280
|
+
* Distance from a slot's current contribution to AU destinations, measured
|
|
4281
|
+
* along profile continuum pairs, and whether it reverses unfinished travel.
|
|
4282
|
+
* A slot that is not playing is at zero. Callers choose any timing policy.
|
|
4283
|
+
*/
|
|
4284
|
+
measureAUGoal(name, destinations) {
|
|
4285
|
+
const playback = this.current.get(name);
|
|
4286
|
+
const action = playback?.connector.getAction(playback.id);
|
|
4287
|
+
const unfinished = !!playback && !!action && !playback.terminal && action.time < action.getClip().duration;
|
|
4288
|
+
return parse(this.core.measure_au_goal(
|
|
4289
|
+
name,
|
|
4290
|
+
action?.time ?? Number.NaN,
|
|
4291
|
+
action?.getEffectiveWeight() ?? Number.NaN,
|
|
4292
|
+
json2(destinations),
|
|
4293
|
+
unfinished
|
|
4294
|
+
));
|
|
4295
|
+
}
|
|
3827
4296
|
transitionAU(id, value, durationMs = 200, balance = this.core.get_au_balance(id)) {
|
|
3828
4297
|
return this.transition(this.mutateLive(() => this.core.set_au_signed(id, value, balance), durationMs));
|
|
3829
4298
|
}
|
|
@@ -3923,7 +4392,9 @@ var Embody = class _Embody {
|
|
|
3923
4392
|
this.meshProfile(name).visible = visible;
|
|
3924
4393
|
}
|
|
3925
4394
|
highlightMesh(name, color, value) {
|
|
3926
|
-
this.
|
|
4395
|
+
this.requireModel();
|
|
4396
|
+
this.core.request_mesh_highlight(name ?? void 0, color, value);
|
|
4397
|
+
this.applier.applyMeshHighlightFrame(parse(this.core.evaluate_mesh_highlight_frame()));
|
|
3927
4398
|
}
|
|
3928
4399
|
getMeshMaterialConfig(name) {
|
|
3929
4400
|
return this.applier.getMeshMaterialConfig(this.requireModel(), name);
|
|
@@ -3941,7 +4412,9 @@ var Embody = class _Embody {
|
|
|
3941
4412
|
const material = mesh && (Array.isArray(mesh.material) ? mesh.material[0] : mesh.material);
|
|
3942
4413
|
if (material) this.hairAppearance[`${category}Color`] = { ...this.hairAppearance[`${category}Color`], baseColor: material.color ? `#${material.color.getHexString()}` : "#000000", emissive: material.emissive ? `#${material.emissive.getHexString()}` : "#000000", emissiveIntensity: material.emissiveIntensity ?? 0 };
|
|
3943
4414
|
}
|
|
3944
|
-
this.
|
|
4415
|
+
for (const [name, playback] of [...this.current]) if (playback.owner === "hair") this.stopAnimation(name);
|
|
4416
|
+
this.core.reset_hair_response();
|
|
4417
|
+
this.sampleHairResponse(0);
|
|
3945
4418
|
return [...this.hairObjects.values()].map((mesh) => ({ name: mesh.name, isMesh: true, isEyebrow: this.meshCategory(mesh) === "eyebrow" }));
|
|
3946
4419
|
}
|
|
3947
4420
|
getRegisteredHairObjects() {
|
|
@@ -3981,12 +4454,69 @@ var Embody = class _Embody {
|
|
|
3981
4454
|
setHairOutline(value) {
|
|
3982
4455
|
Object.assign(this.hairAppearance, { showOutline: value.show }, value.color ? { outlineColor: value.color } : {}, value.opacity !== void 0 ? { outlineOpacity: value.opacity } : {});
|
|
3983
4456
|
}
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
4457
|
+
hairClipOptions(options) {
|
|
4458
|
+
return { ...options, meshNames: [...this.hairObjects.keys()], source: "hair", blendMode: "additive" };
|
|
4459
|
+
}
|
|
4460
|
+
bindHairHead(model, inspection) {
|
|
4461
|
+
this.hairHeadObservation = void 0;
|
|
4462
|
+
const binding = parse(this.core.get_hair_head_binding_json());
|
|
4463
|
+
const head = binding && inspection.boneBindings.get(binding.boneId);
|
|
4464
|
+
if (!binding || !head) return;
|
|
4465
|
+
const chain = [];
|
|
4466
|
+
for (let node2 = head; node2 && node2 !== model; node2 = node2.parent) chain.push(node2);
|
|
4467
|
+
if (!chain.length || chain[chain.length - 1].parent !== model) return;
|
|
4468
|
+
const reference = new Quaternion();
|
|
4469
|
+
const node = new Quaternion();
|
|
4470
|
+
for (let index = chain.length - 1; index >= 0; index -= 1) {
|
|
4471
|
+
const rotation = this.referenceBindings.get(chain[index])?.transform.rotation;
|
|
4472
|
+
reference.multiply(rotation ? node.set(rotation.x, rotation.y, rotation.z, rotation.w) : chain[index].quaternion);
|
|
4473
|
+
}
|
|
4474
|
+
this.hairHeadObservation = {
|
|
4475
|
+
boneName: binding.boneName,
|
|
4476
|
+
chain,
|
|
4477
|
+
reference: binding.reference,
|
|
4478
|
+
modelToReference: new Quaternion().fromArray(binding.reference).multiply(reference.normalize().invert()),
|
|
4479
|
+
rotation: new Quaternion()
|
|
4480
|
+
};
|
|
4481
|
+
}
|
|
4482
|
+
sampleHairResponse(deltaSeconds, observeHead = false) {
|
|
4483
|
+
if (this.disposed || !this.connector || !this.inspection) return;
|
|
4484
|
+
if (!this.hairConfig.enabled || !this.hairObjects.size) {
|
|
4485
|
+
this.stopAnimation(HAIR_RESPONSE);
|
|
4486
|
+
return;
|
|
4487
|
+
}
|
|
4488
|
+
const previous = this.current.get(HAIR_RESPONSE);
|
|
4489
|
+
const action = previous?.connector.getAction(previous.id);
|
|
4490
|
+
const head = this.hairHeadObservation;
|
|
4491
|
+
let pose = null;
|
|
4492
|
+
if (observeHead && head) {
|
|
4493
|
+
const rotation = head.rotation.copy(head.modelToReference);
|
|
4494
|
+
for (let index = head.chain.length - 1; index >= 0; index -= 1) rotation.multiply(head.chain[index].quaternion);
|
|
4495
|
+
pose = { boneName: head.boneName, reference: head.reference, current: rotation.normalize().toArray() };
|
|
4496
|
+
}
|
|
4497
|
+
const ir = parse(this.core.build_hair_response_clip(
|
|
4498
|
+
HAIR_RESPONSE,
|
|
4499
|
+
JSON.stringify(pose),
|
|
4500
|
+
action?.paused ? 0 : deltaSeconds,
|
|
4501
|
+
json2({ meshNames: [...this.hairObjects.keys()] })
|
|
4502
|
+
));
|
|
4503
|
+
if (!ir.tracks.length) {
|
|
4504
|
+
this.stopAnimation(HAIR_RESPONSE);
|
|
4505
|
+
return;
|
|
4506
|
+
}
|
|
4507
|
+
const clip = createAnimationClipFromClipIR(ir, this.inspection);
|
|
4508
|
+
clip.blendMode = AdditiveAnimationBlendMode;
|
|
4509
|
+
const oldTracks = action?.getClip().tracks;
|
|
4510
|
+
const sameBindings = oldTracks && oldTracks.length === clip.tracks.length && clip.tracks.every((track, index) => {
|
|
4511
|
+
const old = oldTracks[index];
|
|
4512
|
+
return old.name === track.name && old.ValueTypeName === track.ValueTypeName && old.getValueSize() === track.getValueSize();
|
|
4513
|
+
});
|
|
4514
|
+
if (previous && action && sameBindings) {
|
|
4515
|
+
this.connector.replaceClip(previous.id, clip);
|
|
4516
|
+
previous.source = { name: HAIR_RESPONSE, kind: "native", clip };
|
|
4517
|
+
} else {
|
|
4518
|
+
const handle = this.handle({ name: HAIR_RESPONSE, kind: "native", clip }, { loopMode: "repeat", blendMode: "additive" }, true, "hair");
|
|
4519
|
+
if (action?.paused || this.core.is_hair_response_paused()) handle.pause();
|
|
3990
4520
|
}
|
|
3991
4521
|
}
|
|
3992
4522
|
setHairPhysicsEnabled(value) {
|
|
@@ -3996,8 +4526,12 @@ var Embody = class _Embody {
|
|
|
3996
4526
|
return !!this.hairConfig.enabled;
|
|
3997
4527
|
}
|
|
3998
4528
|
setHairPhysicsConfig(value) {
|
|
4529
|
+
this.assertAlive();
|
|
3999
4530
|
this.hairConfig = parse(this.core.set_hair_config_json(json2(value)));
|
|
4000
|
-
this.
|
|
4531
|
+
if (!this.hairConfig.enabled) {
|
|
4532
|
+
for (const [name, playback] of [...this.current]) if (playback.owner === "hair") this.stopAnimation(name);
|
|
4533
|
+
}
|
|
4534
|
+
this.sampleHairResponse(0);
|
|
4001
4535
|
}
|
|
4002
4536
|
getHairPhysicsConfig() {
|
|
4003
4537
|
return copy(this.hairConfig);
|
|
@@ -4013,41 +4547,12 @@ var Embody = class _Embody {
|
|
|
4013
4547
|
if (state.position) mesh.position.set(state.position.x, state.position.y, state.position.z);
|
|
4014
4548
|
if (state.color) this.applier.applyHairAppearance([mesh], state.color);
|
|
4015
4549
|
}
|
|
4016
|
-
authoredHeadMotion(clip) {
|
|
4017
|
-
if (!this.hairConfig.enabled || clip.name.startsWith("__embody_hair") || !this.hairObjects.size) return void 0;
|
|
4018
|
-
const tracks = [];
|
|
4019
|
-
for (const track of clip.tracks) {
|
|
4020
|
-
if (track.ValueTypeName !== "quaternion") continue;
|
|
4021
|
-
const parsed = PropertyBinding.parseTrackName(track.name);
|
|
4022
|
-
let object = PropertyBinding.findNode(this.requireModel(), parsed.nodeName);
|
|
4023
|
-
if (parsed.objectName === "bones" && object) {
|
|
4024
|
-
const skeleton = object.skeleton;
|
|
4025
|
-
object = skeleton?.bones.find((bone) => bone.name === String(parsed.objectIndex)) ?? skeleton?.bones[Number(parsed.objectIndex)];
|
|
4026
|
-
}
|
|
4027
|
-
if (!object) continue;
|
|
4028
|
-
const cubic = track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true;
|
|
4029
|
-
const stride = track.getValueSize();
|
|
4030
|
-
const reference = this.restValues(track);
|
|
4031
|
-
if (!reference) continue;
|
|
4032
|
-
const samples = [];
|
|
4033
|
-
for (let index = 0; index < track.times.length; index += 1) {
|
|
4034
|
-
const offset = index * stride + (cubic ? 4 : 0);
|
|
4035
|
-
samples.push(Array.from(track.values.subarray(offset, offset + 4)));
|
|
4036
|
-
}
|
|
4037
|
-
tracks.push({ boneName: object.name, current: object.quaternion.toArray(), reference, samples });
|
|
4038
|
-
}
|
|
4039
|
-
return parse(this.core.resolve_hair_motion_json(json2(tracks), clip.blendMode === AdditiveAnimationBlendMode)) ?? void 0;
|
|
4040
|
-
}
|
|
4041
|
-
setHairGravity(pitch, durationSeconds) {
|
|
4042
|
-
const clip = parse(this.core.build_hair_gravity_clip("__embody_hair_gravity", pitch, durationSeconds, json2({ meshNames: [...this.hairObjects.keys()], source: "hair" })));
|
|
4043
|
-
if (!clip.tracks.length) return;
|
|
4044
|
-
this.handle({ name: clip.name, kind: "compiled", clip }, { loopMode: "once", finalPose: "hold", blendMode: "additive" }, true);
|
|
4045
|
-
}
|
|
4046
4550
|
playHairImpulse(horizontal, vertical) {
|
|
4047
4551
|
if (!this.hairConfig.enabled || !this.hairObjects.size) return void 0;
|
|
4048
4552
|
const curves = parse(this.wasm.build_hair_impulse_curves(json2(this.hairConfig), this.hairConfig.impulseClipDuration, horizontal, vertical));
|
|
4049
4553
|
if (!Object.keys(curves).length) return void 0;
|
|
4050
|
-
|
|
4554
|
+
const options = this.hairClipOptions({ loopMode: "once", finalPose: "release" });
|
|
4555
|
+
return this.build({ name: HAIR_IMPULSE_CLIP, kind: "curves", input: curves, options: copy(options) }, options, true, "hair");
|
|
4051
4556
|
}
|
|
4052
4557
|
start() {
|
|
4053
4558
|
this.assertAlive();
|
|
@@ -4068,11 +4573,14 @@ var Embody = class _Embody {
|
|
|
4068
4573
|
dispose() {
|
|
4069
4574
|
if (this.disposed) return;
|
|
4070
4575
|
this.connector?.dispose();
|
|
4576
|
+
this.applier.clearMeshHighlights();
|
|
4071
4577
|
this.core.free();
|
|
4072
4578
|
this.playbacks.clear();
|
|
4073
4579
|
this.current.clear();
|
|
4074
4580
|
this.sources.clear();
|
|
4581
|
+
this.vrmAnimations.clear();
|
|
4075
4582
|
this.hairObjects.clear();
|
|
4583
|
+
this.hairHeadObservation = void 0;
|
|
4076
4584
|
this.meshes.length = 0;
|
|
4077
4585
|
this.meshByName.clear();
|
|
4078
4586
|
this.bones = {};
|
|
@@ -4114,7 +4622,7 @@ function createSceneInkBackgroundCss(palette = DEFAULT_PALETTE) {
|
|
|
4114
4622
|
createSceneInkBackgroundCss();
|
|
4115
4623
|
|
|
4116
4624
|
// three/sceneInkBackground.ts
|
|
4117
|
-
var toCssColor = (tone) => `#${new
|
|
4625
|
+
var toCssColor = (tone) => `#${new THREE11.Color(tone).getHexString()}`;
|
|
4118
4626
|
function createSceneInkBackground(onBackgroundChange) {
|
|
4119
4627
|
let currentBackground = "";
|
|
4120
4628
|
let disposed = false;
|
|
@@ -4254,7 +4762,7 @@ function sampleTexture(texture) {
|
|
|
4254
4762
|
if (alpha < 0.18) {
|
|
4255
4763
|
continue;
|
|
4256
4764
|
}
|
|
4257
|
-
const color = new
|
|
4765
|
+
const color = new THREE11.Color(
|
|
4258
4766
|
data[index] / 255,
|
|
4259
4767
|
data[index + 1] / 255,
|
|
4260
4768
|
data[index + 2] / 255
|
|
@@ -4299,7 +4807,7 @@ function buildPalette(samples) {
|
|
|
4299
4807
|
const hsl = { h: 0, s: 0, l: 0 };
|
|
4300
4808
|
sample.color.getHSL(hsl);
|
|
4301
4809
|
const bucketId = Math.floor(hsl.h * 18);
|
|
4302
|
-
const bucket = buckets.get(bucketId) ?? { color: new
|
|
4810
|
+
const bucket = buckets.get(bucketId) ?? { color: new THREE11.Color(0, 0, 0), score: 0 };
|
|
4303
4811
|
bucket.color.r += sample.color.r * sample.score;
|
|
4304
4812
|
bucket.color.g += sample.color.g * sample.score;
|
|
4305
4813
|
bucket.color.b += sample.color.b * sample.score;
|
|
@@ -4332,18 +4840,18 @@ function buildPalette(samples) {
|
|
|
4332
4840
|
function massageBackgroundTone(color) {
|
|
4333
4841
|
const hsl = { h: 0, s: 0, l: 0 };
|
|
4334
4842
|
color.getHSL(hsl);
|
|
4335
|
-
const saturation =
|
|
4336
|
-
const lightness =
|
|
4337
|
-
return new
|
|
4843
|
+
const saturation = THREE11.MathUtils.clamp(Math.max(hsl.s * 1.35, 0.46), 0.46, 0.82);
|
|
4844
|
+
const lightness = THREE11.MathUtils.clamp(hsl.l + 0.08, 0.42, 0.66);
|
|
4845
|
+
return new THREE11.Color().setHSL(hsl.h, saturation, lightness);
|
|
4338
4846
|
}
|
|
4339
4847
|
function createCompanionTone(source, index) {
|
|
4340
4848
|
const hsl = { h: 0, s: 0, l: 0 };
|
|
4341
4849
|
source.getHSL(hsl);
|
|
4342
4850
|
const hueOffset = index === 1 ? 0.075 : -0.065;
|
|
4343
|
-
return new
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4851
|
+
return new THREE11.Color().setHSL(
|
|
4852
|
+
THREE11.MathUtils.euclideanModulo(hsl.h + hueOffset, 1),
|
|
4853
|
+
THREE11.MathUtils.clamp(hsl.s * (index === 1 ? 0.92 : 0.82), 0.38, 0.76),
|
|
4854
|
+
THREE11.MathUtils.clamp(hsl.l + (index === 1 ? 0.04 : -0.03), 0.38, 0.68)
|
|
4347
4855
|
);
|
|
4348
4856
|
}
|
|
4349
4857
|
function colorDistance(a, b) {
|
|
@@ -4382,6 +4890,11 @@ var prepare = (model, castShadows = true) => {
|
|
|
4382
4890
|
});
|
|
4383
4891
|
return meshes;
|
|
4384
4892
|
};
|
|
4893
|
+
var prepareCharacterModelResult = (model, animations, gltf, options) => ({ model, meshes: prepare(model, options.castShadows), animations, gltf });
|
|
4894
|
+
var bufferFormat = (data) => {
|
|
4895
|
+
const header = new TextDecoder().decode(new Uint8Array(data, 0, Math.min(data.byteLength, 4096)));
|
|
4896
|
+
return header.startsWith("Kaydara FBX Binary \0") || /^\s*FBXHeaderExtension\s*:/m.test(header) ? "fbx" : "gltf";
|
|
4897
|
+
};
|
|
4385
4898
|
var progress = (listener) => {
|
|
4386
4899
|
let last = -1;
|
|
4387
4900
|
return (event) => {
|
|
@@ -4414,8 +4927,8 @@ function disposeCharacterModel(scene, model) {
|
|
|
4414
4927
|
});
|
|
4415
4928
|
}
|
|
4416
4929
|
function loadCharacterModel(url, options = {}) {
|
|
4417
|
-
const done = (model, animations, gltf) => (
|
|
4418
|
-
if (/\.fbx(
|
|
4930
|
+
const done = (model, animations, gltf) => prepareCharacterModelResult(model, animations, gltf, options);
|
|
4931
|
+
if ((options.format ?? (/\.fbx(?:[?#]|$)/i.test(url) ? "fbx" : "gltf")) === "fbx") {
|
|
4419
4932
|
fbxLoader ?? (fbxLoader = new FBXLoader());
|
|
4420
4933
|
return new Promise((resolve, reject) => fbxLoader.load(url, (model) => resolve(done(model, model.animations ?? [], null)), progress(options.onProgress), reject));
|
|
4421
4934
|
}
|
|
@@ -4423,8 +4936,15 @@ function loadCharacterModel(url, options = {}) {
|
|
|
4423
4936
|
return new Promise((resolve, reject) => loader.load(url, (gltf) => resolve(done(gltf.scene, gltf.animations ?? [], gltf)), progress(options.onProgress), reject));
|
|
4424
4937
|
}
|
|
4425
4938
|
function parseCharacterModel(data, resourcePath = "", options = {}) {
|
|
4939
|
+
if ((options.format ?? bufferFormat(data)) === "fbx") {
|
|
4940
|
+
return Promise.resolve().then(() => {
|
|
4941
|
+
fbxLoader ?? (fbxLoader = new FBXLoader());
|
|
4942
|
+
const model = fbxLoader.parse(data, resourcePath);
|
|
4943
|
+
return prepareCharacterModelResult(model, model.animations ?? [], null, options);
|
|
4944
|
+
});
|
|
4945
|
+
}
|
|
4426
4946
|
const loader = getGltfLoader(options.dracoDecoderPath);
|
|
4427
|
-
return new Promise((resolve, reject) => loader.parse(data, resourcePath, (gltf) => resolve(
|
|
4947
|
+
return new Promise((resolve, reject) => loader.parse(data, resourcePath, (gltf) => resolve(prepareCharacterModelResult(gltf.scene, gltf.animations ?? [], gltf, options)), reject));
|
|
4428
4948
|
}
|
|
4429
4949
|
|
|
4430
4950
|
// three/modelMorphLayout.ts
|
|
@@ -4574,6 +5094,63 @@ function assertRenderableCharacterMorphAttributes(model) {
|
|
|
4574
5094
|
}
|
|
4575
5095
|
});
|
|
4576
5096
|
}
|
|
5097
|
+
var owners2 = /* @__PURE__ */ new WeakMap();
|
|
5098
|
+
var snapshot = (scene) => ({ value: scene.background, intensity: scene.backgroundIntensity, rotation: scene.backgroundRotation.clone() });
|
|
5099
|
+
var apply = (scene, value) => {
|
|
5100
|
+
scene.background = value.value;
|
|
5101
|
+
scene.backgroundIntensity = value.intensity;
|
|
5102
|
+
scene.backgroundRotation.copy(value.rotation);
|
|
5103
|
+
};
|
|
5104
|
+
var ownerFor = (scene) => {
|
|
5105
|
+
let owner = owners2.get(scene);
|
|
5106
|
+
if (!owner) {
|
|
5107
|
+
owner = { base: snapshot(scene), override: null, disposed: false };
|
|
5108
|
+
owners2.set(scene, owner);
|
|
5109
|
+
}
|
|
5110
|
+
return owner;
|
|
5111
|
+
};
|
|
5112
|
+
function setSceneBaseBackground(scene, value) {
|
|
5113
|
+
const owner = ownerFor(scene);
|
|
5114
|
+
owner.base = { value, intensity: 1, rotation: new Euler() };
|
|
5115
|
+
if (!owner.override?.value) apply(scene, owner.base);
|
|
5116
|
+
}
|
|
5117
|
+
function acquireSceneBackground(scene, onSceneDispose) {
|
|
5118
|
+
const owner = ownerFor(scene);
|
|
5119
|
+
if (owner.disposed) throw new Error("The scenery scene has been disposed.");
|
|
5120
|
+
if (owner.override) throw new Error("The scene already has a scenery renderer.");
|
|
5121
|
+
owner.base = snapshot(scene);
|
|
5122
|
+
const override = { value: null, onSceneDispose };
|
|
5123
|
+
owner.override = override;
|
|
5124
|
+
let disposed = false;
|
|
5125
|
+
return {
|
|
5126
|
+
set(value, intensity2, rotationDegrees) {
|
|
5127
|
+
if (disposed || owner.disposed) throw new Error("The scenery background has been disposed.");
|
|
5128
|
+
override.value = { value, intensity: intensity2, rotation: new Euler(0, rotationDegrees * Math.PI / 180, 0) };
|
|
5129
|
+
apply(scene, override.value);
|
|
5130
|
+
},
|
|
5131
|
+
clear() {
|
|
5132
|
+
if (disposed) return;
|
|
5133
|
+
override.value = null;
|
|
5134
|
+
apply(scene, owner.base);
|
|
5135
|
+
},
|
|
5136
|
+
dispose() {
|
|
5137
|
+
if (disposed) return;
|
|
5138
|
+
disposed = true;
|
|
5139
|
+
if (owner.override === override) {
|
|
5140
|
+
owner.override = null;
|
|
5141
|
+
apply(scene, owner.base);
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
};
|
|
5145
|
+
}
|
|
5146
|
+
function disposeSceneBackground(scene) {
|
|
5147
|
+
const owner = ownerFor(scene);
|
|
5148
|
+
if (owner.disposed) return;
|
|
5149
|
+
owner.disposed = true;
|
|
5150
|
+
owner.override?.onSceneDispose();
|
|
5151
|
+
}
|
|
5152
|
+
|
|
5153
|
+
// three/scene.ts
|
|
4577
5154
|
var DEFAULT_CHARACTER_LIGHTING_PRESETS = {
|
|
4578
5155
|
cleanStudio: { id: "cleanStudio", label: "Soft Studio", settings: { envMapEnabled: true, environmentIntensity: 0.24, environmentBlur: 0.04, exposure: 1.08, ambientIntensity: 0.32, keyIntensity: 0.52, fillIntensity: 0.18, rimIntensity: 0.08, shadowOpacity: 0.22 } },
|
|
4579
5156
|
softFill: { id: "softFill", label: "Soft Fill", settings: { envMapEnabled: true, environmentIntensity: 0.3, environmentBlur: 0.04, exposure: 1.1, ambientIntensity: 0.38, keyIntensity: 0.44, fillIntensity: 0.24, rimIntensity: 0.1, shadowOpacity: 0.18 } },
|
|
@@ -4818,7 +5395,8 @@ function constructDefaultCharacterScene(container, options, renderer, disposeRen
|
|
|
4818
5395
|
Object.assign(renderer.domElement.style, { display: "block", width: "100%", height: "100%" });
|
|
4819
5396
|
const scene = new Scene();
|
|
4820
5397
|
const background = options.background === void 0 ? sceneType.background : options.background;
|
|
4821
|
-
scene
|
|
5398
|
+
setSceneBaseBackground(scene, background == null ? null : new Color(background));
|
|
5399
|
+
lifetime.add(() => disposeSceneBackground(scene));
|
|
4822
5400
|
const camera = new PerspectiveCamera(options.cameraFov ?? 45, width / height, 0.1, 1e3);
|
|
4823
5401
|
const preset = DEFAULT_CHARACTER_LIGHTING_PRESETS[options.lightingPreset ?? sceneType.lightingPreset]?.settings ?? DEFAULT_CHARACTER_LIGHTING_SETTINGS;
|
|
4824
5402
|
const lighting = createLighting(scene, { ...preset, ...options.lighting });
|
|
@@ -4839,7 +5417,7 @@ function constructDefaultCharacterScene(container, options, renderer, disposeRen
|
|
|
4839
5417
|
const type = CHARACTER_SCENE_TYPES[value.type ?? DEFAULT_CHARACTER_SCENE_TYPE_ID] ?? CHARACTER_SCENE_TYPES[DEFAULT_CHARACTER_SCENE_TYPE_ID];
|
|
4840
5418
|
currentSceneType = type.id;
|
|
4841
5419
|
const background2 = value.background === void 0 ? type.background : value.background;
|
|
4842
|
-
scene
|
|
5420
|
+
setSceneBaseBackground(scene, background2 == null ? null : new Color(background2));
|
|
4843
5421
|
camera.fov = value.cameraFov ?? 45;
|
|
4844
5422
|
camera.updateProjectionMatrix();
|
|
4845
5423
|
const preset2 = DEFAULT_CHARACTER_LIGHTING_PRESETS[value.lightingPreset ?? type.lightingPreset] ?? DEFAULT_CHARACTER_LIGHTING_PRESETS[DEFAULT_CHARACTER_LIGHTING_PRESET_ID];
|
|
@@ -4974,17 +5552,17 @@ function prepareCameraCoordinates(scene, camera, backend) {
|
|
|
4974
5552
|
}
|
|
4975
5553
|
});
|
|
4976
5554
|
const previous = Array.from(cameras, (current) => [current, current.coordinateSystem]);
|
|
4977
|
-
const
|
|
5555
|
+
const apply2 = (current, coordinates2) => {
|
|
4978
5556
|
current.coordinateSystem = coordinates2;
|
|
4979
5557
|
current.updateProjectionMatrix();
|
|
4980
5558
|
};
|
|
4981
5559
|
const coordinates = backend === "webgpu" ? WebGPUCoordinateSystem : WebGLCoordinateSystem;
|
|
4982
|
-
cameras.forEach((current) =>
|
|
5560
|
+
cameras.forEach((current) => apply2(current, coordinates));
|
|
4983
5561
|
shadows.forEach((shadow) => {
|
|
4984
5562
|
shadow.needsUpdate = true;
|
|
4985
5563
|
});
|
|
4986
5564
|
return () => {
|
|
4987
|
-
previous.forEach(([current, original]) =>
|
|
5565
|
+
previous.forEach(([current, original]) => apply2(current, original));
|
|
4988
5566
|
shadows.forEach((shadow) => {
|
|
4989
5567
|
shadow.needsUpdate = true;
|
|
4990
5568
|
});
|
|
@@ -4998,7 +5576,8 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
4998
5576
|
let currentSceneType = sceneType.id;
|
|
4999
5577
|
const scene = new Scene();
|
|
5000
5578
|
const background = options.background === void 0 ? sceneType.background : options.background;
|
|
5001
|
-
scene
|
|
5579
|
+
setSceneBaseBackground(scene, background == null ? null : new Color(background));
|
|
5580
|
+
lifetime.add(() => disposeSceneBackground(scene));
|
|
5002
5581
|
const camera = new PerspectiveCamera(options.cameraFov ?? 45, 1, 0.1, 1e3);
|
|
5003
5582
|
const preset = DEFAULT_CHARACTER_LIGHTING_PRESETS[options.lightingPreset ?? sceneType.lightingPreset]?.settings ?? DEFAULT_CHARACTER_LIGHTING_SETTINGS;
|
|
5004
5583
|
const lighting = createMutableCharacterLighting(scene, { ...preset, ...options.lighting });
|
|
@@ -5038,10 +5617,10 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5038
5617
|
return { ...common, backend: null, renderer: null };
|
|
5039
5618
|
};
|
|
5040
5619
|
const notify = () => {
|
|
5041
|
-
const
|
|
5620
|
+
const snapshot3 = getSnapshot();
|
|
5042
5621
|
for (const listener of listeners) {
|
|
5043
5622
|
try {
|
|
5044
|
-
listener(
|
|
5623
|
+
listener(snapshot3);
|
|
5045
5624
|
} catch (failure) {
|
|
5046
5625
|
console.error("Character rendering subscriber failed.", failure);
|
|
5047
5626
|
}
|
|
@@ -5168,9 +5747,9 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5168
5747
|
rollback(() => oldRenderer?.dispose());
|
|
5169
5748
|
status = "ready";
|
|
5170
5749
|
error = null;
|
|
5171
|
-
const
|
|
5750
|
+
const snapshot3 = getSnapshot();
|
|
5172
5751
|
notify();
|
|
5173
|
-
return
|
|
5752
|
+
return snapshot3;
|
|
5174
5753
|
} catch (failure) {
|
|
5175
5754
|
if (suspended) {
|
|
5176
5755
|
try {
|
|
@@ -5249,6 +5828,17 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5249
5828
|
}
|
|
5250
5829
|
} };
|
|
5251
5830
|
},
|
|
5831
|
+
acquireRendererAsync: async () => {
|
|
5832
|
+
for (; ; ) {
|
|
5833
|
+
if (disposed) throw renderingAborted();
|
|
5834
|
+
const settling = pending;
|
|
5835
|
+
if (settling) {
|
|
5836
|
+
await settling.catch(() => void 0);
|
|
5837
|
+
continue;
|
|
5838
|
+
}
|
|
5839
|
+
return rendering.acquireRenderer();
|
|
5840
|
+
}
|
|
5841
|
+
},
|
|
5252
5842
|
releaseSceneResources: (release) => {
|
|
5253
5843
|
if (disposal) return disposal.then(release, release);
|
|
5254
5844
|
const previous = pending;
|
|
@@ -5332,7 +5922,7 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5332
5922
|
const type = CHARACTER_SCENE_TYPES[value.type ?? DEFAULT_CHARACTER_SCENE_TYPE_ID] ?? CHARACTER_SCENE_TYPES[DEFAULT_CHARACTER_SCENE_TYPE_ID];
|
|
5333
5923
|
currentSceneType = type.id;
|
|
5334
5924
|
const background2 = value.background === void 0 ? type.background : value.background;
|
|
5335
|
-
scene
|
|
5925
|
+
setSceneBaseBackground(scene, background2 == null ? null : new Color(background2));
|
|
5336
5926
|
camera.fov = value.cameraFov ?? 45;
|
|
5337
5927
|
camera.updateProjectionMatrix();
|
|
5338
5928
|
const preset2 = DEFAULT_CHARACTER_LIGHTING_PRESETS[value.lightingPreset ?? type.lightingPreset] ?? DEFAULT_CHARACTER_LIGHTING_PRESETS[DEFAULT_CHARACTER_LIGHTING_PRESET_ID];
|
|
@@ -5375,9 +5965,9 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5375
5965
|
async function createDefaultCharacterSceneAsync(container, options = {}) {
|
|
5376
5966
|
const runtime = createDefaultCharacterSceneRuntime(container, options);
|
|
5377
5967
|
try {
|
|
5378
|
-
const
|
|
5379
|
-
if (!
|
|
5380
|
-
const handle =
|
|
5968
|
+
const snapshot3 = await runtime.ready;
|
|
5969
|
+
if (!snapshot3.renderer) throw new Error("Character scene renderer was not initialized.");
|
|
5970
|
+
const handle = snapshot3.backend === "webgl" ? { ...runtime, renderer: snapshot3.renderer, backend: "webgl" } : { ...runtime, renderer: snapshot3.renderer, backend: "webgpu" };
|
|
5381
5971
|
Object.defineProperties(handle, {
|
|
5382
5972
|
renderer: { get: () => runtime.renderer },
|
|
5383
5973
|
backend: { get: () => runtime.backend },
|
|
@@ -5464,7 +6054,14 @@ async function createCharacterHost(options) {
|
|
|
5464
6054
|
if (!rendering.getSnapshot().renderer) throw error;
|
|
5465
6055
|
}
|
|
5466
6056
|
check(signal);
|
|
5467
|
-
|
|
6057
|
+
const lease = await rendering.acquireRendererAsync();
|
|
6058
|
+
try {
|
|
6059
|
+
check(signal);
|
|
6060
|
+
return lease;
|
|
6061
|
+
} catch (error) {
|
|
6062
|
+
lease.release();
|
|
6063
|
+
throw error;
|
|
6064
|
+
}
|
|
5468
6065
|
};
|
|
5469
6066
|
const makeEngine = (value, character) => {
|
|
5470
6067
|
let profile = character.profile ?? options.embody?.profile;
|
|
@@ -5713,6 +6310,302 @@ async function createCharacterHost(options) {
|
|
|
5713
6310
|
throw error;
|
|
5714
6311
|
}
|
|
5715
6312
|
}
|
|
6313
|
+
var finite4 = (value) => typeof value === "number" && Number.isFinite(value);
|
|
6314
|
+
var identity2 = (value) => typeof value === "string" && value.trim().length > 0;
|
|
6315
|
+
var messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
6316
|
+
function copyLayout(layout) {
|
|
6317
|
+
if (layout === null) return { skybox: null, instances: [] };
|
|
6318
|
+
if (!layout || !Array.isArray(layout.instances)) throw new Error("Scenery requires an instances array.");
|
|
6319
|
+
const ids = /* @__PURE__ */ new Set();
|
|
6320
|
+
const instances = layout.instances.map((instance) => {
|
|
6321
|
+
if (!instance || !identity2(instance.instanceId) || !identity2(instance.assetId)) throw new Error("Each scenery placement requires an instanceId and assetId.");
|
|
6322
|
+
if (ids.has(instance.instanceId)) throw new Error(`Duplicate scenery instanceId: ${instance.instanceId}`);
|
|
6323
|
+
ids.add(instance.instanceId);
|
|
6324
|
+
const position = instance.position;
|
|
6325
|
+
if (!position || !finite4(position.x) || !finite4(position.y) || !finite4(position.z)) throw new Error(`Scenery placement ${instance.instanceId} requires finite X/Y/Z coordinates.`);
|
|
6326
|
+
return { instanceId: instance.instanceId, assetId: instance.assetId, position: { x: position.x, y: position.y, z: position.z } };
|
|
6327
|
+
});
|
|
6328
|
+
const skybox = layout.skybox;
|
|
6329
|
+
if (skybox !== null) {
|
|
6330
|
+
if (!skybox || !["skybox", "backdrop", "color"].includes(skybox.kind)) throw new Error("Scenery background kind must be skybox, backdrop or color.");
|
|
6331
|
+
if (skybox.assetId !== null && !identity2(skybox.assetId)) throw new Error("The scenery background assetId must be a nonempty string or null.");
|
|
6332
|
+
if (skybox.kind === "color" && skybox.assetId !== null) throw new Error("A solid scenery color cannot use an image asset.");
|
|
6333
|
+
if (typeof skybox.color !== "string" || !/^#[\da-f]{6}$/i.test(skybox.color)) throw new Error("Scenery background color must use six-digit #rrggbb.");
|
|
6334
|
+
if (!finite4(skybox.intensity) || skybox.intensity < 0 || !finite4(skybox.rotation)) throw new Error("Scenery background intensity must be nonnegative and rotation must be finite degrees.");
|
|
6335
|
+
}
|
|
6336
|
+
return { instances, skybox: skybox === null ? null : { kind: skybox.kind, assetId: skybox.assetId, color: skybox.color, intensity: skybox.intensity, rotation: skybox.rotation } };
|
|
6337
|
+
}
|
|
6338
|
+
function disposeRoots(roots) {
|
|
6339
|
+
const geometries = /* @__PURE__ */ new Set(), materials2 = /* @__PURE__ */ new Set(), textures = /* @__PURE__ */ new Set();
|
|
6340
|
+
const skeletons = /* @__PURE__ */ new Set();
|
|
6341
|
+
for (const root of roots) root.traverse((object) => {
|
|
6342
|
+
if (object.geometry) geometries.add(object.geometry);
|
|
6343
|
+
if (object.skeleton) skeletons.add(object.skeleton);
|
|
6344
|
+
for (const material of object.material ? Array.isArray(object.material) ? object.material : [object.material] : []) {
|
|
6345
|
+
materials2.add(material);
|
|
6346
|
+
for (const value of Object.values(material)) if (value?.isTexture) textures.add(value);
|
|
6347
|
+
}
|
|
6348
|
+
});
|
|
6349
|
+
const images = /* @__PURE__ */ new Set();
|
|
6350
|
+
for (const texture of textures) {
|
|
6351
|
+
for (const image of Array.isArray(texture.image) ? texture.image : [texture.image]) if (image) images.add(image);
|
|
6352
|
+
texture.dispose();
|
|
6353
|
+
}
|
|
6354
|
+
for (const geometry of geometries) geometry.dispose();
|
|
6355
|
+
for (const material of materials2) material.dispose();
|
|
6356
|
+
for (const skeleton of skeletons) skeleton.dispose();
|
|
6357
|
+
for (const image of images) image.close?.();
|
|
6358
|
+
}
|
|
6359
|
+
function display(value, skybox) {
|
|
6360
|
+
const texture = "isTexture" in value ? value : null;
|
|
6361
|
+
if (texture) {
|
|
6362
|
+
texture.colorSpace = SRGBColorSpace;
|
|
6363
|
+
if (skybox.kind === "skybox") texture.mapping = EquirectangularReflectionMapping;
|
|
6364
|
+
}
|
|
6365
|
+
let disposed = false;
|
|
6366
|
+
return {
|
|
6367
|
+
value,
|
|
6368
|
+
texture,
|
|
6369
|
+
intensity: skybox.intensity,
|
|
6370
|
+
rotation: skybox.kind === "skybox" ? skybox.rotation : 0,
|
|
6371
|
+
dispose() {
|
|
6372
|
+
if (disposed) return;
|
|
6373
|
+
disposed = true;
|
|
6374
|
+
texture?.dispose();
|
|
6375
|
+
}
|
|
6376
|
+
};
|
|
6377
|
+
}
|
|
6378
|
+
function generatedSky(skybox) {
|
|
6379
|
+
const canvas = document.createElement("canvas");
|
|
6380
|
+
canvas.width = 1024;
|
|
6381
|
+
canvas.height = 512;
|
|
6382
|
+
const context = canvas.getContext("2d");
|
|
6383
|
+
if (!context) throw new Error("The browser could not create the scenery skybox.");
|
|
6384
|
+
const gradient = context.createLinearGradient(0, 0, 0, 512);
|
|
6385
|
+
gradient.addColorStop(0, "#10172c");
|
|
6386
|
+
gradient.addColorStop(0.48, skybox.color);
|
|
6387
|
+
gradient.addColorStop(0.52, "#b6aaa0");
|
|
6388
|
+
gradient.addColorStop(1, "#151c25");
|
|
6389
|
+
context.fillStyle = gradient;
|
|
6390
|
+
context.fillRect(0, 0, 1024, 512);
|
|
6391
|
+
return display(new CanvasTexture(canvas), skybox);
|
|
6392
|
+
}
|
|
6393
|
+
async function imageDisplay(asset, skybox) {
|
|
6394
|
+
if (!["image/png", "image/jpeg", "image/webp"].includes(asset.contentType)) throw new Error("Scenery backgrounds require a PNG, JPEG or WebP image.");
|
|
6395
|
+
const url = URL.createObjectURL(new Blob([asset.bytes], { type: asset.contentType }));
|
|
6396
|
+
let pending;
|
|
6397
|
+
try {
|
|
6398
|
+
const texture = await new Promise((resolve, reject) => {
|
|
6399
|
+
pending = new TextureLoader().load(url, resolve, void 0, reject);
|
|
6400
|
+
});
|
|
6401
|
+
return display(texture, skybox);
|
|
6402
|
+
} catch (error) {
|
|
6403
|
+
pending?.dispose();
|
|
6404
|
+
throw new Error(`Scenery background image could not decode: ${messageOf(error)}`);
|
|
6405
|
+
} finally {
|
|
6406
|
+
URL.revokeObjectURL(url);
|
|
6407
|
+
}
|
|
6408
|
+
}
|
|
6409
|
+
async function parseModel(asset) {
|
|
6410
|
+
if (asset.contentType !== "model/gltf-binary" || asset.bytes.byteLength < 20) throw new Error("Scenery models require a self-contained GLB 2.0 file.");
|
|
6411
|
+
const header = new DataView(asset.bytes);
|
|
6412
|
+
if (header.getUint32(0, true) !== 1179937895 || header.getUint32(4, true) !== 2 || header.getUint32(8, true) !== asset.bytes.byteLength) throw new Error("Scenery models require a valid GLB 2.0 container.");
|
|
6413
|
+
let offset = 12, document2;
|
|
6414
|
+
while (offset < asset.bytes.byteLength) {
|
|
6415
|
+
if (offset + 8 > asset.bytes.byteLength) throw new Error("The scenery GLB has a truncated chunk header.");
|
|
6416
|
+
const size = header.getUint32(offset, true), kind = header.getUint32(offset + 4, true);
|
|
6417
|
+
if (size % 4 || size > asset.bytes.byteLength - offset - 8) throw new Error("The scenery GLB has an invalid chunk length.");
|
|
6418
|
+
if (offset === 12 && kind !== 1313821514) throw new Error("The scenery GLB must begin with its JSON chunk.");
|
|
6419
|
+
if (kind === 1313821514) {
|
|
6420
|
+
if (document2) throw new Error("The scenery GLB contains duplicate JSON chunks.");
|
|
6421
|
+
try {
|
|
6422
|
+
document2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(asset.bytes, offset + 8, size)));
|
|
6423
|
+
} catch (error) {
|
|
6424
|
+
throw new Error(`The scenery GLB JSON is invalid: ${messageOf(error)}`);
|
|
6425
|
+
}
|
|
6426
|
+
if (!document2 || typeof document2 !== "object" || Array.isArray(document2)) throw new Error("The scenery GLB JSON must be an object.");
|
|
6427
|
+
}
|
|
6428
|
+
offset += 8 + size;
|
|
6429
|
+
}
|
|
6430
|
+
for (const resources of [document2?.buffers, document2?.images]) {
|
|
6431
|
+
if (resources !== void 0 && !Array.isArray(resources)) throw new Error("The scenery GLB buffers and images must be arrays.");
|
|
6432
|
+
for (const resource of resources ?? []) {
|
|
6433
|
+
if (resource?.uri !== void 0 && (typeof resource.uri !== "string" || !resource.uri.startsWith("data:"))) {
|
|
6434
|
+
throw new Error("Scenery GLB files must embed their geometry and textures; external URLs are not allowed.");
|
|
6435
|
+
}
|
|
6436
|
+
}
|
|
6437
|
+
}
|
|
6438
|
+
const manager = new LoadingManager();
|
|
6439
|
+
manager.setURLModifier((url) => {
|
|
6440
|
+
if (url.startsWith("data:") || url.startsWith("blob:")) return url;
|
|
6441
|
+
throw new Error("Scenery GLB files must embed their geometry and textures; external URLs are not allowed.");
|
|
6442
|
+
});
|
|
6443
|
+
const gltf = await new GLTFLoader(manager).parseAsync(asset.bytes, "");
|
|
6444
|
+
if (!gltf.scene?.isObject3D) throw new Error("The scenery GLB has no scene to display.");
|
|
6445
|
+
return [gltf.scene, ...gltf.scenes.filter((root) => root !== gltf.scene)];
|
|
6446
|
+
}
|
|
6447
|
+
function createSceneSceneryRenderer(options) {
|
|
6448
|
+
const { scene, rendering, loadAsset } = options;
|
|
6449
|
+
if (!scene?.isScene || typeof loadAsset !== "function") throw new Error("Scenery requires a Three scene and an asset loader.");
|
|
6450
|
+
let disposed = false, disposal = null;
|
|
6451
|
+
let backgroundKey = null, backgroundGeneration = 0;
|
|
6452
|
+
let backgroundLoad = null, currentDisplay = null;
|
|
6453
|
+
const models = /* @__PURE__ */ new Map(), tasks = /* @__PURE__ */ new Set();
|
|
6454
|
+
const report = (message) => {
|
|
6455
|
+
try {
|
|
6456
|
+
options.onError?.(message);
|
|
6457
|
+
} catch (error) {
|
|
6458
|
+
console.error("Scenery error subscriber failed.", error);
|
|
6459
|
+
}
|
|
6460
|
+
};
|
|
6461
|
+
const track = (task) => {
|
|
6462
|
+
tasks.add(task);
|
|
6463
|
+
void task.then(() => tasks.delete(task), (error) => {
|
|
6464
|
+
tasks.delete(task);
|
|
6465
|
+
report(messageOf(error));
|
|
6466
|
+
});
|
|
6467
|
+
return task;
|
|
6468
|
+
};
|
|
6469
|
+
const release = (callback) => {
|
|
6470
|
+
try {
|
|
6471
|
+
return rendering ? rendering.releaseSceneResources(callback) : (callback(), Promise.resolve());
|
|
6472
|
+
} catch (error) {
|
|
6473
|
+
return Promise.reject(error);
|
|
6474
|
+
}
|
|
6475
|
+
};
|
|
6476
|
+
const background = acquireSceneBackground(scene, () => {
|
|
6477
|
+
void dispose().catch((error) => report(messageOf(error)));
|
|
6478
|
+
});
|
|
6479
|
+
const retire = (model) => {
|
|
6480
|
+
if (model.retired) return Promise.resolve();
|
|
6481
|
+
model.retired = true;
|
|
6482
|
+
model.controller.abort();
|
|
6483
|
+
return release(() => {
|
|
6484
|
+
model.wrapper.removeFromParent();
|
|
6485
|
+
model.attached = false;
|
|
6486
|
+
disposeRoots(model.roots);
|
|
6487
|
+
model.roots = [];
|
|
6488
|
+
});
|
|
6489
|
+
};
|
|
6490
|
+
const mount = (instance) => {
|
|
6491
|
+
const wrapper = new Group();
|
|
6492
|
+
wrapper.name = `Scenery ${instance.instanceId}`;
|
|
6493
|
+
wrapper.position.set(instance.position.x, instance.position.y, instance.position.z);
|
|
6494
|
+
const model = { assetId: instance.assetId, wrapper, controller: new AbortController(), roots: [], attached: false, retired: false };
|
|
6495
|
+
models.set(instance.instanceId, model);
|
|
6496
|
+
track((async () => {
|
|
6497
|
+
let roots = [];
|
|
6498
|
+
try {
|
|
6499
|
+
const asset = await loadAsset(model.assetId, model.controller.signal);
|
|
6500
|
+
if (model.controller.signal.aborted) return;
|
|
6501
|
+
roots = await parseModel(asset);
|
|
6502
|
+
if (model.controller.signal.aborted || disposed) {
|
|
6503
|
+
disposeRoots(roots);
|
|
6504
|
+
roots = [];
|
|
6505
|
+
return;
|
|
6506
|
+
}
|
|
6507
|
+
await release(() => {
|
|
6508
|
+
if (model.controller.signal.aborted || disposed) {
|
|
6509
|
+
disposeRoots(roots);
|
|
6510
|
+
roots = [];
|
|
6511
|
+
return;
|
|
6512
|
+
}
|
|
6513
|
+
model.roots = roots;
|
|
6514
|
+
roots = [];
|
|
6515
|
+
wrapper.add(model.roots[0]);
|
|
6516
|
+
scene.add(wrapper);
|
|
6517
|
+
model.attached = true;
|
|
6518
|
+
});
|
|
6519
|
+
} catch (error) {
|
|
6520
|
+
if (roots.length) disposeRoots(roots);
|
|
6521
|
+
if (!model.controller.signal.aborted && !disposed) {
|
|
6522
|
+
if (models.get(instance.instanceId) === model) models.delete(instance.instanceId);
|
|
6523
|
+
await retire(model);
|
|
6524
|
+
report(`Scenery placement ${instance.instanceId} could not load: ${messageOf(error)}`);
|
|
6525
|
+
}
|
|
6526
|
+
}
|
|
6527
|
+
})());
|
|
6528
|
+
};
|
|
6529
|
+
const updateBackground = (skybox) => {
|
|
6530
|
+
const key = JSON.stringify(skybox);
|
|
6531
|
+
if (backgroundKey === key) return;
|
|
6532
|
+
backgroundKey = key;
|
|
6533
|
+
const generation = ++backgroundGeneration;
|
|
6534
|
+
backgroundLoad?.abort();
|
|
6535
|
+
const controller = new AbortController();
|
|
6536
|
+
backgroundLoad = controller;
|
|
6537
|
+
track((async () => {
|
|
6538
|
+
let next = null;
|
|
6539
|
+
try {
|
|
6540
|
+
if (skybox) {
|
|
6541
|
+
if (skybox.kind === "color") next = display(new Color(skybox.color), skybox);
|
|
6542
|
+
else if (!skybox.assetId) next = generatedSky(skybox);
|
|
6543
|
+
else {
|
|
6544
|
+
const asset = await loadAsset(skybox.assetId, controller.signal);
|
|
6545
|
+
if (controller.signal.aborted) return;
|
|
6546
|
+
next = await imageDisplay(asset, skybox);
|
|
6547
|
+
}
|
|
6548
|
+
}
|
|
6549
|
+
await release(() => {
|
|
6550
|
+
if (controller.signal.aborted || generation !== backgroundGeneration || disposed) {
|
|
6551
|
+
next?.dispose();
|
|
6552
|
+
next = null;
|
|
6553
|
+
return;
|
|
6554
|
+
}
|
|
6555
|
+
if (next) background.set(next.value, next.intensity, next.rotation);
|
|
6556
|
+
else background.clear();
|
|
6557
|
+
const previous = currentDisplay;
|
|
6558
|
+
currentDisplay = next;
|
|
6559
|
+
next = null;
|
|
6560
|
+
previous?.dispose();
|
|
6561
|
+
});
|
|
6562
|
+
} catch (error) {
|
|
6563
|
+
next?.dispose();
|
|
6564
|
+
if (!controller.signal.aborted && generation === backgroundGeneration && !disposed) {
|
|
6565
|
+
backgroundKey = null;
|
|
6566
|
+
report(`Scenery background could not load: ${messageOf(error)}`);
|
|
6567
|
+
}
|
|
6568
|
+
}
|
|
6569
|
+
})());
|
|
6570
|
+
};
|
|
6571
|
+
function dispose() {
|
|
6572
|
+
if (disposal) return disposal;
|
|
6573
|
+
disposed = true;
|
|
6574
|
+
backgroundGeneration++;
|
|
6575
|
+
backgroundLoad?.abort();
|
|
6576
|
+
background.dispose();
|
|
6577
|
+
const releases = [...models.values()].map(retire);
|
|
6578
|
+
models.clear();
|
|
6579
|
+
releases.push(release(() => {
|
|
6580
|
+
currentDisplay?.dispose();
|
|
6581
|
+
currentDisplay = null;
|
|
6582
|
+
}));
|
|
6583
|
+
disposal = (async () => {
|
|
6584
|
+
const results = await Promise.allSettled([...tasks, ...releases]);
|
|
6585
|
+
const failed = results.find((result) => result.status === "rejected");
|
|
6586
|
+
if (failed) throw failed.reason;
|
|
6587
|
+
})();
|
|
6588
|
+
return disposal;
|
|
6589
|
+
}
|
|
6590
|
+
return {
|
|
6591
|
+
update(value) {
|
|
6592
|
+
if (disposed) throw new Error("The scenery renderer has been disposed.");
|
|
6593
|
+
const layout = copyLayout(value);
|
|
6594
|
+
const next = new Map(layout.instances.map((instance) => [instance.instanceId, instance]));
|
|
6595
|
+
for (const [id, model] of models) if (next.get(id)?.assetId !== model.assetId) {
|
|
6596
|
+
models.delete(id);
|
|
6597
|
+
track(retire(model));
|
|
6598
|
+
}
|
|
6599
|
+
for (const instance of layout.instances) {
|
|
6600
|
+
const current = models.get(instance.instanceId);
|
|
6601
|
+
if (current) current.wrapper.position.set(instance.position.x, instance.position.y, instance.position.z);
|
|
6602
|
+
else mount(instance);
|
|
6603
|
+
}
|
|
6604
|
+
updateBackground(layout.skybox);
|
|
6605
|
+
},
|
|
6606
|
+
dispose
|
|
6607
|
+
};
|
|
6608
|
+
}
|
|
5716
6609
|
var COLORS = {
|
|
5717
6610
|
mapped: 4889258,
|
|
5718
6611
|
unmapped: 5925498,
|
|
@@ -5839,8 +6732,8 @@ function createOctahedralBoneGeometry() {
|
|
|
5839
6732
|
-w,
|
|
5840
6733
|
MID_POSITION
|
|
5841
6734
|
]);
|
|
5842
|
-
const geometry = new
|
|
5843
|
-
geometry.setAttribute("position", new
|
|
6735
|
+
const geometry = new THREE11.BufferGeometry();
|
|
6736
|
+
geometry.setAttribute("position", new THREE11.BufferAttribute(positions, 3));
|
|
5844
6737
|
geometry.computeVertexNormals();
|
|
5845
6738
|
return geometry;
|
|
5846
6739
|
}
|
|
@@ -5857,24 +6750,24 @@ function collectBones(root) {
|
|
|
5857
6750
|
});
|
|
5858
6751
|
return Array.from(bones);
|
|
5859
6752
|
}
|
|
5860
|
-
function
|
|
6753
|
+
function isBone3(obj) {
|
|
5861
6754
|
return !!obj && obj.isBone;
|
|
5862
6755
|
}
|
|
5863
6756
|
function countBoneSubtree(bone) {
|
|
5864
6757
|
let count = 1;
|
|
5865
6758
|
for (const child of bone.children) {
|
|
5866
|
-
if (
|
|
6759
|
+
if (isBone3(child)) {
|
|
5867
6760
|
count += countBoneSubtree(child);
|
|
5868
6761
|
}
|
|
5869
6762
|
}
|
|
5870
6763
|
return count;
|
|
5871
6764
|
}
|
|
5872
6765
|
function resolveTopLevelHelperRoot(root) {
|
|
5873
|
-
const topLevelBones = collectBones(root).filter((bone) => !
|
|
6766
|
+
const topLevelBones = collectBones(root).filter((bone) => !isBone3(bone.parent));
|
|
5874
6767
|
if (topLevelBones.length !== 1) return null;
|
|
5875
6768
|
let helperRoot = topLevelBones[0];
|
|
5876
6769
|
while (ROOT_HELPER_NAME_PATTERN.test(helperRoot.name)) {
|
|
5877
|
-
const boneChildren = helperRoot.children.filter(
|
|
6770
|
+
const boneChildren = helperRoot.children.filter(isBone3);
|
|
5878
6771
|
if (boneChildren.length !== 1) break;
|
|
5879
6772
|
const onlyChild = boneChildren[0];
|
|
5880
6773
|
if (!ROOT_HELPER_NAME_PATTERN.test(onlyChild.name)) break;
|
|
@@ -5885,7 +6778,7 @@ function resolveTopLevelHelperRoot(root) {
|
|
|
5885
6778
|
function resolveBodyDisplayRoot(root) {
|
|
5886
6779
|
const helperRoot = resolveTopLevelHelperRoot(root);
|
|
5887
6780
|
if (!helperRoot) return null;
|
|
5888
|
-
const boneChildren = helperRoot.children.filter(
|
|
6781
|
+
const boneChildren = helperRoot.children.filter(isBone3);
|
|
5889
6782
|
if (boneChildren.length === 0) return null;
|
|
5890
6783
|
if (boneChildren.length === 1) return boneChildren[0];
|
|
5891
6784
|
const subtreeSizes = boneChildren.map((child) => ({ child, size: countBoneSubtree(child) })).sort((left, right) => right.size - left.size);
|
|
@@ -5897,7 +6790,7 @@ function resolveBodyDisplayRoot(root) {
|
|
|
5897
6790
|
return null;
|
|
5898
6791
|
}
|
|
5899
6792
|
function collectBoneChildren(bone) {
|
|
5900
|
-
return bone.children.filter(
|
|
6793
|
+
return bone.children.filter(isBone3);
|
|
5901
6794
|
}
|
|
5902
6795
|
function hasZeroLengthConnection(parentBone, childBone) {
|
|
5903
6796
|
parentBone.getWorldPosition(_parentPos);
|
|
@@ -5906,7 +6799,7 @@ function hasZeroLengthConnection(parentBone, childBone) {
|
|
|
5906
6799
|
}
|
|
5907
6800
|
function hasRootHelperAncestor(bone) {
|
|
5908
6801
|
let current = bone.parent;
|
|
5909
|
-
while (
|
|
6802
|
+
while (isBone3(current)) {
|
|
5910
6803
|
if (ROOT_HELPER_NAME_PATTERN.test(current.name)) return true;
|
|
5911
6804
|
current = current.parent;
|
|
5912
6805
|
}
|
|
@@ -5916,7 +6809,7 @@ function shouldSkipRootHelperConnection(parentBone, boneChildren) {
|
|
|
5916
6809
|
if (boneChildren.length !== 1) return false;
|
|
5917
6810
|
const childBone = boneChildren[0];
|
|
5918
6811
|
if (ROOT_HELPER_NAME_PATTERN.test(parentBone.name)) {
|
|
5919
|
-
if (!
|
|
6812
|
+
if (!isBone3(parentBone.parent)) return true;
|
|
5920
6813
|
return collectBoneChildren(childBone).some((grandChild) => hasZeroLengthConnection(childBone, grandChild));
|
|
5921
6814
|
}
|
|
5922
6815
|
return hasRootHelperAncestor(parentBone) && HIP_NAME_PATTERN.test(parentBone.name) && PELVIS_NAME_PATTERN.test(childBone.name) && hasZeroLengthConnection(parentBone, childBone);
|
|
@@ -5929,20 +6822,20 @@ function setRayFromPointer(clientX, clientY, camera, domElement) {
|
|
|
5929
6822
|
_raycaster.setFromCamera(_mouse, camera);
|
|
5930
6823
|
return true;
|
|
5931
6824
|
}
|
|
5932
|
-
var _parentPos = new
|
|
5933
|
-
var _childPos = new
|
|
5934
|
-
var _direction = new
|
|
5935
|
-
var _zAxis = new
|
|
5936
|
-
var _quaternion = new
|
|
5937
|
-
var _bonePos = new
|
|
5938
|
-
var _boneQuat = new
|
|
5939
|
-
var _boneEuler = new
|
|
5940
|
-
var _mouse = new
|
|
5941
|
-
var _raycaster = new
|
|
5942
|
-
var _cameraSpacePosition = new
|
|
5943
|
-
var _planeNormal = new
|
|
5944
|
-
var _dragPlane = new
|
|
5945
|
-
var _planeHit = new
|
|
6825
|
+
var _parentPos = new THREE11.Vector3();
|
|
6826
|
+
var _childPos = new THREE11.Vector3();
|
|
6827
|
+
var _direction = new THREE11.Vector3();
|
|
6828
|
+
var _zAxis = new THREE11.Vector3(0, 0, 1);
|
|
6829
|
+
var _quaternion = new THREE11.Quaternion();
|
|
6830
|
+
var _bonePos = new THREE11.Vector3();
|
|
6831
|
+
var _boneQuat = new THREE11.Quaternion();
|
|
6832
|
+
var _boneEuler = new THREE11.Euler();
|
|
6833
|
+
var _mouse = new THREE11.Vector2();
|
|
6834
|
+
var _raycaster = new THREE11.Raycaster();
|
|
6835
|
+
var _cameraSpacePosition = new THREE11.Vector3();
|
|
6836
|
+
var _planeNormal = new THREE11.Vector3();
|
|
6837
|
+
var _dragPlane = new THREE11.Plane();
|
|
6838
|
+
var _planeHit = new THREE11.Vector3();
|
|
5946
6839
|
function resolveSkeletonOverlayStartBoneName(root) {
|
|
5947
6840
|
return resolveBodyDisplayRoot(root)?.name ?? null;
|
|
5948
6841
|
}
|
|
@@ -5989,9 +6882,9 @@ var SkeletonOverlay = class {
|
|
|
5989
6882
|
this.scene = scene;
|
|
5990
6883
|
this.mappedBoneNames = mappedBoneNames;
|
|
5991
6884
|
this.semanticKeyMap = semanticKeyMap;
|
|
5992
|
-
this.opacityScale =
|
|
6885
|
+
this.opacityScale = THREE11.MathUtils.clamp(options.opacityScale ?? 1, 0, 1);
|
|
5993
6886
|
this.focusSelectedBone = options.focusSelectedBone ?? false;
|
|
5994
|
-
this.group = new
|
|
6887
|
+
this.group = new THREE11.Group();
|
|
5995
6888
|
this.group.name = "SkeletonOverlay";
|
|
5996
6889
|
this.group.renderOrder = 999;
|
|
5997
6890
|
this.group.visible = false;
|
|
@@ -6000,50 +6893,50 @@ var SkeletonOverlay = class {
|
|
|
6000
6893
|
transparent: true,
|
|
6001
6894
|
depthTest: false,
|
|
6002
6895
|
depthWrite: false,
|
|
6003
|
-
side:
|
|
6896
|
+
side: THREE11.DoubleSide
|
|
6004
6897
|
};
|
|
6005
|
-
this.mappedMaterial = new
|
|
6006
|
-
this.unmappedMaterial = new
|
|
6007
|
-
this.highlightMaterial = new
|
|
6008
|
-
this.selectedMaterial = new
|
|
6009
|
-
this.contextMaterial = new
|
|
6010
|
-
this.mutedMaterial = new
|
|
6898
|
+
this.mappedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.mapped, opacity: OPACITY.mapped });
|
|
6899
|
+
this.unmappedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.unmapped, opacity: OPACITY.unmapped });
|
|
6900
|
+
this.highlightMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.highlighted, opacity: OPACITY.highlighted });
|
|
6901
|
+
this.selectedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.selected, opacity: OPACITY.selected });
|
|
6902
|
+
this.contextMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.selected, opacity: OPACITY.context });
|
|
6903
|
+
this.mutedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.unmapped, opacity: OPACITY.muted });
|
|
6011
6904
|
this.mappedMaterial.forceSinglePass = true;
|
|
6012
6905
|
this.unmappedMaterial.forceSinglePass = true;
|
|
6013
6906
|
this.highlightMaterial.forceSinglePass = true;
|
|
6014
6907
|
this.selectedMaterial.forceSinglePass = true;
|
|
6015
6908
|
this.contextMaterial.forceSinglePass = true;
|
|
6016
6909
|
this.mutedMaterial.forceSinglePass = true;
|
|
6017
|
-
this.axisRingGeometry = new
|
|
6018
|
-
this.axisRingHitGeometry = new
|
|
6019
|
-
this.axisCenterGeometry = new
|
|
6020
|
-
this.pitchRingMaterial = new
|
|
6021
|
-
this.yawRingMaterial = new
|
|
6022
|
-
this.rollRingMaterial = new
|
|
6023
|
-
this.axisHitMaterial = new
|
|
6910
|
+
this.axisRingGeometry = new THREE11.TorusGeometry(1, 0.032, 10, 120);
|
|
6911
|
+
this.axisRingHitGeometry = new THREE11.TorusGeometry(1, AXIS_GIZMO_HIT_TUBE, 8, 64);
|
|
6912
|
+
this.axisCenterGeometry = new THREE11.SphereGeometry(0.082, 12, 8);
|
|
6913
|
+
this.pitchRingMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.pitch, opacity: AXIS_GIZMO_IDLE_OPACITY });
|
|
6914
|
+
this.yawRingMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.yaw, opacity: AXIS_GIZMO_IDLE_OPACITY });
|
|
6915
|
+
this.rollRingMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.roll, opacity: AXIS_GIZMO_IDLE_OPACITY });
|
|
6916
|
+
this.axisHitMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, opacity: 0 });
|
|
6024
6917
|
this.axisHitMaterial.colorWrite = false;
|
|
6025
|
-
this.axisCenterMaterial = new
|
|
6026
|
-
this.axisGizmoGroup = new
|
|
6918
|
+
this.axisCenterMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: 16119802, opacity: 0.92 });
|
|
6919
|
+
this.axisGizmoGroup = new THREE11.Group();
|
|
6027
6920
|
this.axisGizmoGroup.name = "SelectedBoneAxisGizmo";
|
|
6028
6921
|
this.axisGizmoGroup.visible = false;
|
|
6029
6922
|
this.axisGizmoGroup.renderOrder = 1e3;
|
|
6030
|
-
const pitchRing = new
|
|
6923
|
+
const pitchRing = new THREE11.Mesh(this.axisRingGeometry, this.pitchRingMaterial);
|
|
6031
6924
|
pitchRing.renderOrder = 1e3;
|
|
6032
6925
|
pitchRing.userData.axis = "pitch";
|
|
6033
|
-
const pitchHit = new
|
|
6926
|
+
const pitchHit = new THREE11.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
|
|
6034
6927
|
pitchHit.userData.axis = "pitch";
|
|
6035
|
-
const yawRing = new
|
|
6928
|
+
const yawRing = new THREE11.Mesh(this.axisRingGeometry, this.yawRingMaterial);
|
|
6036
6929
|
yawRing.renderOrder = 1e3;
|
|
6037
6930
|
yawRing.userData.axis = "yaw";
|
|
6038
|
-
const yawHit = new
|
|
6931
|
+
const yawHit = new THREE11.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
|
|
6039
6932
|
yawHit.userData.axis = "yaw";
|
|
6040
|
-
const rollRing = new
|
|
6933
|
+
const rollRing = new THREE11.Mesh(this.axisRingGeometry, this.rollRingMaterial);
|
|
6041
6934
|
rollRing.renderOrder = 1e3;
|
|
6042
6935
|
rollRing.userData.axis = "roll";
|
|
6043
|
-
const rollHit = new
|
|
6936
|
+
const rollHit = new THREE11.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
|
|
6044
6937
|
rollHit.rotation.copy(rollRing.rotation);
|
|
6045
6938
|
rollHit.userData.axis = "roll";
|
|
6046
|
-
const center = new
|
|
6939
|
+
const center = new THREE11.Mesh(this.axisCenterGeometry, this.axisCenterMaterial);
|
|
6047
6940
|
center.renderOrder = 1001;
|
|
6048
6941
|
this.axisGizmoMeshes = {
|
|
6049
6942
|
pitch: pitchRing,
|
|
@@ -6083,11 +6976,11 @@ var SkeletonOverlay = class {
|
|
|
6083
6976
|
this.boneTree = allBones.map((bone) => {
|
|
6084
6977
|
const isMapped = this.mappedBoneNames.has(bone.name);
|
|
6085
6978
|
const semanticKey = this.semanticKeyMap.get(bone.name) ?? null;
|
|
6086
|
-
const boneChildren = bone.children.filter((c) =>
|
|
6087
|
-
const parentName = bone.parent &&
|
|
6979
|
+
const boneChildren = bone.children.filter((c) => isBone3(c) && includedBones.has(c)).map((c) => c.name);
|
|
6980
|
+
const parentName = bone.parent && isBone3(bone.parent) && includedBones.has(bone.parent) ? bone.parent.name : null;
|
|
6088
6981
|
let depth = 0;
|
|
6089
6982
|
let current = bone.parent;
|
|
6090
|
-
while (current &&
|
|
6983
|
+
while (current && isBone3(current) && includedBones.has(current)) {
|
|
6091
6984
|
depth++;
|
|
6092
6985
|
current = current.parent;
|
|
6093
6986
|
}
|
|
@@ -6103,7 +6996,7 @@ var SkeletonOverlay = class {
|
|
|
6103
6996
|
});
|
|
6104
6997
|
for (const bone of allBones) {
|
|
6105
6998
|
const boneChildren = bone.children.filter(
|
|
6106
|
-
(c) =>
|
|
6999
|
+
(c) => isBone3(c) && includedBones.has(c)
|
|
6107
7000
|
);
|
|
6108
7001
|
if (boneChildren.length > 0) {
|
|
6109
7002
|
if (shouldSkipRootHelperConnection(bone, boneChildren)) {
|
|
@@ -6117,15 +7010,15 @@ var SkeletonOverlay = class {
|
|
|
6117
7010
|
}
|
|
6118
7011
|
}
|
|
6119
7012
|
this.group.add(this.axisGizmoGroup);
|
|
6120
|
-
const sentinelGeo = new
|
|
6121
|
-
const sentinelMat = new
|
|
7013
|
+
const sentinelGeo = new THREE11.SphereGeometry(1e-6, 4, 2);
|
|
7014
|
+
const sentinelMat = new THREE11.MeshBasicMaterial({
|
|
6122
7015
|
transparent: true,
|
|
6123
7016
|
opacity: 0,
|
|
6124
7017
|
depthWrite: false,
|
|
6125
7018
|
depthTest: false
|
|
6126
7019
|
});
|
|
6127
7020
|
sentinelMat.colorWrite = false;
|
|
6128
|
-
const sentinel = new
|
|
7021
|
+
const sentinel = new THREE11.Mesh(sentinelGeo, sentinelMat);
|
|
6129
7022
|
sentinel.visible = true;
|
|
6130
7023
|
sentinel.frustumCulled = false;
|
|
6131
7024
|
sentinel.onBeforeRender = (renderer, _scene, camera) => this.update(camera, renderer.domElement);
|
|
@@ -6137,7 +7030,7 @@ var SkeletonOverlay = class {
|
|
|
6137
7030
|
createBoneMesh(parentBone, childBone) {
|
|
6138
7031
|
const isMapped = this.mappedBoneNames.has(parentBone.name);
|
|
6139
7032
|
const material = isMapped ? this.mappedMaterial : this.unmappedMaterial;
|
|
6140
|
-
const mesh = new
|
|
7033
|
+
const mesh = new THREE11.Mesh(this.sharedGeometry, material);
|
|
6141
7034
|
mesh.name = `bone_vis_${parentBone.name}${childBone ? `_${childBone.name}` : "_leaf"}`;
|
|
6142
7035
|
mesh.frustumCulled = false;
|
|
6143
7036
|
mesh.renderOrder = 999;
|
|
@@ -6217,20 +7110,20 @@ var SkeletonOverlay = class {
|
|
|
6217
7110
|
bone.getWorldPosition(_parentPos);
|
|
6218
7111
|
let referenceLength = 0;
|
|
6219
7112
|
for (const child of bone.children) {
|
|
6220
|
-
if (!
|
|
7113
|
+
if (!isBone3(child)) continue;
|
|
6221
7114
|
child.getWorldPosition(_childPos);
|
|
6222
7115
|
const nextDistance = _parentPos.distanceTo(_childPos);
|
|
6223
7116
|
if (nextDistance <= 1e-4) continue;
|
|
6224
7117
|
referenceLength = referenceLength === 0 ? nextDistance : Math.min(referenceLength, nextDistance);
|
|
6225
7118
|
}
|
|
6226
|
-
if (referenceLength <= 1e-4 &&
|
|
7119
|
+
if (referenceLength <= 1e-4 && isBone3(bone.parent)) {
|
|
6227
7120
|
bone.parent.getWorldPosition(_childPos);
|
|
6228
7121
|
referenceLength = _parentPos.distanceTo(_childPos);
|
|
6229
7122
|
}
|
|
6230
7123
|
if (referenceLength <= 1e-4) {
|
|
6231
7124
|
referenceLength = LEAF_BONE_LENGTH;
|
|
6232
7125
|
}
|
|
6233
|
-
return
|
|
7126
|
+
return THREE11.MathUtils.clamp(
|
|
6234
7127
|
referenceLength * AXIS_GIZMO_RADIUS_FACTOR,
|
|
6235
7128
|
AXIS_GIZMO_MIN_RADIUS,
|
|
6236
7129
|
AXIS_GIZMO_MAX_RADIUS
|
|
@@ -6246,7 +7139,7 @@ var SkeletonOverlay = class {
|
|
|
6246
7139
|
_cameraSpacePosition.copy(origin).applyMatrix4(perspectiveCamera.matrixWorldInverse);
|
|
6247
7140
|
const depth = -_cameraSpacePosition.z;
|
|
6248
7141
|
if (!Number.isFinite(depth) || depth <= perspectiveCamera.near) return null;
|
|
6249
|
-
const visibleWorldHeight = 2 * depth * Math.tan(
|
|
7142
|
+
const visibleWorldHeight = 2 * depth * Math.tan(THREE11.MathUtils.degToRad(perspectiveCamera.getEffectiveFOV()) / 2);
|
|
6250
7143
|
return visibleWorldHeight * (AXIS_GIZMO_SCREEN_RADIUS_PX / viewportHeight);
|
|
6251
7144
|
}
|
|
6252
7145
|
if (camera.isOrthographicCamera) {
|
|
@@ -6377,9 +7270,9 @@ var SkeletonOverlay = class {
|
|
|
6377
7270
|
return {
|
|
6378
7271
|
position: [_bonePos.x, _bonePos.y, _bonePos.z],
|
|
6379
7272
|
rotation: [
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
7273
|
+
THREE11.MathUtils.radToDeg(_boneEuler.x),
|
|
7274
|
+
THREE11.MathUtils.radToDeg(_boneEuler.y),
|
|
7275
|
+
THREE11.MathUtils.radToDeg(_boneEuler.z)
|
|
6383
7276
|
]
|
|
6384
7277
|
};
|
|
6385
7278
|
}
|
|
@@ -6581,7 +7474,7 @@ function getDeltaMagnitudeAtIndex(mesh, morphAttribute, index) {
|
|
|
6581
7474
|
return Math.hypot(dx, dy, dz);
|
|
6582
7475
|
}
|
|
6583
7476
|
function resolveMorphPreviewMeshes(root, morphName, meshNames) {
|
|
6584
|
-
const nameFilter = meshNames
|
|
7477
|
+
const nameFilter = meshNames ? new Set(meshNames) : null;
|
|
6585
7478
|
const meshes = [];
|
|
6586
7479
|
root.traverse((obj) => {
|
|
6587
7480
|
const mesh = obj;
|
|
@@ -6630,7 +7523,7 @@ function writePointColor(colors, offset, intensity2) {
|
|
|
6630
7523
|
colors[offset + 1] = COLOR_LOW.g + (COLOR_HIGH.g - COLOR_LOW.g) * intensity2;
|
|
6631
7524
|
colors[offset + 2] = COLOR_LOW.b + (COLOR_HIGH.b - COLOR_LOW.b) * intensity2;
|
|
6632
7525
|
}
|
|
6633
|
-
function buildMorphVertexPreviewRenderData(meshes, morphName, cache, options = {}) {
|
|
7526
|
+
function buildMorphVertexPreviewRenderData(meshes, morphName, cache, options = {}, previous = null) {
|
|
6634
7527
|
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
6635
7528
|
const maxPoints = options.maxPoints ?? DEFAULT_MAX_POINTS;
|
|
6636
7529
|
const sampledMeshes = [];
|
|
@@ -6647,9 +7540,9 @@ function buildMorphVertexPreviewRenderData(meshes, morphName, cache, options = {
|
|
|
6647
7540
|
pointCount += sample.indices.length;
|
|
6648
7541
|
}
|
|
6649
7542
|
if (pointCount === 0) return null;
|
|
6650
|
-
const positions = new Float32Array(pointCount * 3);
|
|
6651
|
-
const colors = new Float32Array(pointCount * 3);
|
|
6652
|
-
const vertex = new
|
|
7543
|
+
const positions = previous?.positions.length === pointCount * 3 ? previous.positions : new Float32Array(pointCount * 3);
|
|
7544
|
+
const colors = previous?.colors.length === pointCount * 3 ? previous.colors : new Float32Array(pointCount * 3);
|
|
7545
|
+
const vertex = new THREE11.Vector3();
|
|
6653
7546
|
let cursor = 0;
|
|
6654
7547
|
for (const { mesh, sample } of sampledMeshes) {
|
|
6655
7548
|
mesh.updateWorldMatrix(true, false);
|
|
@@ -6705,6 +7598,7 @@ var MorphVertexPreview = class {
|
|
|
6705
7598
|
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
6706
7599
|
__publicField(this, "points", null);
|
|
6707
7600
|
__publicField(this, "active", null);
|
|
7601
|
+
__publicField(this, "renderData", null);
|
|
6708
7602
|
__publicField(this, "disposed", false);
|
|
6709
7603
|
__publicField(this, "unbindRender", null);
|
|
6710
7604
|
}
|
|
@@ -6714,12 +7608,18 @@ var MorphVertexPreview = class {
|
|
|
6714
7608
|
this.hide();
|
|
6715
7609
|
return;
|
|
6716
7610
|
}
|
|
7611
|
+
if (this.active?.morphName !== morphName || this.active?.meshNames?.length !== meshNames?.length || meshNames?.some((name, index) => name !== this.active?.meshNames?.[index])) {
|
|
7612
|
+
this.cache.clear();
|
|
7613
|
+
this.renderData = null;
|
|
7614
|
+
}
|
|
6717
7615
|
this.active = { morphName, meshNames: meshNames?.slice() };
|
|
6718
7616
|
this.unbindRender ?? (this.unbindRender = bindPreviewRender(this.scene, () => this.update()));
|
|
6719
7617
|
this.update();
|
|
6720
7618
|
}
|
|
6721
7619
|
hide() {
|
|
6722
7620
|
this.active = null;
|
|
7621
|
+
this.cache.clear();
|
|
7622
|
+
this.renderData = null;
|
|
6723
7623
|
this.unbindRender?.();
|
|
6724
7624
|
this.unbindRender = null;
|
|
6725
7625
|
if (this.points) this.points.visible = false;
|
|
@@ -6736,7 +7636,7 @@ var MorphVertexPreview = class {
|
|
|
6736
7636
|
this.points = null;
|
|
6737
7637
|
}
|
|
6738
7638
|
createPoints() {
|
|
6739
|
-
const points = new
|
|
7639
|
+
const points = new THREE11.Points(new THREE11.BufferGeometry(), new THREE11.PointsMaterial({
|
|
6740
7640
|
size: 0.012,
|
|
6741
7641
|
sizeAttenuation: true,
|
|
6742
7642
|
transparent: true,
|
|
@@ -6757,26 +7657,31 @@ var MorphVertexPreview = class {
|
|
|
6757
7657
|
if (this.disposed || !this.active) return;
|
|
6758
7658
|
const { morphName, meshNames } = this.active;
|
|
6759
7659
|
const meshes = resolveMorphPreviewMeshes(this.scene, morphName, meshNames);
|
|
6760
|
-
const data = buildMorphVertexPreviewRenderData(meshes, morphName, this.cache, this.options);
|
|
7660
|
+
const data = buildMorphVertexPreviewRenderData(meshes, morphName, this.cache, this.options, this.renderData);
|
|
7661
|
+
this.renderData = data;
|
|
6761
7662
|
if (!data) {
|
|
6762
7663
|
if (this.points) this.points.visible = false;
|
|
6763
7664
|
return;
|
|
6764
7665
|
}
|
|
6765
7666
|
const points = this.points ?? this.createPoints();
|
|
6766
|
-
this.
|
|
6767
|
-
this.updateAttribute(points.geometry, "color", data.colors, data.pointCount);
|
|
7667
|
+
this.updateGeometry(points.geometry, data);
|
|
6768
7668
|
points.geometry.computeBoundingSphere();
|
|
6769
7669
|
points.material.size = data.pointCount > 6e3 ? 9e-3 : 0.012;
|
|
6770
7670
|
points.visible = true;
|
|
6771
7671
|
}
|
|
6772
|
-
|
|
6773
|
-
const
|
|
6774
|
-
|
|
6775
|
-
|
|
7672
|
+
updateGeometry(geometry, data) {
|
|
7673
|
+
const position = geometry.getAttribute("position");
|
|
7674
|
+
const color = geometry.getAttribute("color");
|
|
7675
|
+
if (!position || !color || position.count !== data.pointCount || color.count !== data.pointCount) {
|
|
7676
|
+
if (position || color) geometry.dispose();
|
|
7677
|
+
geometry.setAttribute("position", new THREE11.BufferAttribute(data.positions, 3));
|
|
7678
|
+
geometry.setAttribute("color", new THREE11.BufferAttribute(data.colors, 3));
|
|
6776
7679
|
return;
|
|
6777
7680
|
}
|
|
6778
|
-
|
|
6779
|
-
|
|
7681
|
+
if (position.array !== data.positions) position.array.set(data.positions);
|
|
7682
|
+
if (color.array !== data.colors) color.array.set(data.colors);
|
|
7683
|
+
position.needsUpdate = true;
|
|
7684
|
+
color.needsUpdate = true;
|
|
6780
7685
|
}
|
|
6781
7686
|
};
|
|
6782
7687
|
|
|
@@ -6830,7 +7735,7 @@ var transform = (obj) => ({
|
|
|
6830
7735
|
rotation: { x: obj.quaternion.x, y: obj.quaternion.y, z: obj.quaternion.z, w: obj.quaternion.w },
|
|
6831
7736
|
scale: { x: obj.scale.x, y: obj.scale.y, z: obj.scale.z }
|
|
6832
7737
|
});
|
|
6833
|
-
var
|
|
7738
|
+
var snapshot2 = (obj, reference) => {
|
|
6834
7739
|
if (!reference) {
|
|
6835
7740
|
return {
|
|
6836
7741
|
obj,
|
|
@@ -6880,6 +7785,7 @@ var ThreeModelInspector = class {
|
|
|
6880
7785
|
const allMeshes = [];
|
|
6881
7786
|
const boneObjects = [];
|
|
6882
7787
|
model.traverse((obj) => {
|
|
7788
|
+
if (highlightOverlays.has(obj)) return;
|
|
6883
7789
|
objects.push(obj);
|
|
6884
7790
|
if (obj.isMesh) allMeshes.push(obj);
|
|
6885
7791
|
if (obj.isBone || obj.type === "Bone") boneObjects.push(obj);
|
|
@@ -6915,7 +7821,7 @@ var ThreeModelInspector = class {
|
|
|
6915
7821
|
}
|
|
6916
7822
|
const bones = {};
|
|
6917
7823
|
for (const bone of boneObjects) {
|
|
6918
|
-
if (bone.name) bones[bone.name] =
|
|
7824
|
+
if (bone.name) bones[bone.name] = snapshot2(bone, references?.get(bone));
|
|
6919
7825
|
}
|
|
6920
7826
|
const profile = options.profile;
|
|
6921
7827
|
if (profile?.boneNodes) {
|
|
@@ -6924,7 +7830,7 @@ var ThreeModelInspector = class {
|
|
|
6924
7830
|
const suffix = profile.boneSuffix ?? "";
|
|
6925
7831
|
const name = `${base.startsWith(prefix) ? "" : prefix}${base}${base.endsWith(suffix) ? "" : suffix}`;
|
|
6926
7832
|
const object = model.getObjectByName(name) ?? model.getObjectByName(base);
|
|
6927
|
-
if (object) bones[key] = bones[object.name] ??
|
|
7833
|
+
if (object) bones[key] = bones[object.name] ?? snapshot2(object, references?.get(object));
|
|
6928
7834
|
}
|
|
6929
7835
|
}
|
|
6930
7836
|
return {
|
|
@@ -7032,6 +7938,16 @@ function serializeAnimationClips(model, clips, inspection) {
|
|
|
7032
7938
|
const meshIds = new Map(Array.from(inspection.meshBindings, ([id, mesh]) => [mesh, id]));
|
|
7033
7939
|
const boneIds = new Map(Array.from(inspection.boneBindings, ([id, bone]) => [bone, id]));
|
|
7034
7940
|
const objectIds = new Map(Array.from(inspection.objectBindings, ([id, object]) => [object, id]));
|
|
7941
|
+
const morphIds = /* @__PURE__ */ new Map();
|
|
7942
|
+
for (const [id, { mesh, index }] of inspection.morphBindings) {
|
|
7943
|
+
let indices = morphIds.get(mesh);
|
|
7944
|
+
if (!indices) {
|
|
7945
|
+
indices = /* @__PURE__ */ new Map();
|
|
7946
|
+
morphIds.set(mesh, indices);
|
|
7947
|
+
}
|
|
7948
|
+
if (!indices.has(index)) indices.set(index, id);
|
|
7949
|
+
}
|
|
7950
|
+
const destinations = /* @__PURE__ */ new Map();
|
|
7035
7951
|
return clips.map((clip) => {
|
|
7036
7952
|
const tracks = [];
|
|
7037
7953
|
const channelKinds = /* @__PURE__ */ new Map();
|
|
@@ -7043,7 +7959,11 @@ function serializeAnimationClips(model, clips, inspection) {
|
|
|
7043
7959
|
return id;
|
|
7044
7960
|
};
|
|
7045
7961
|
withComponentTracks(clip).tracks.forEach((track, index) => {
|
|
7046
|
-
|
|
7962
|
+
let found = destinations.get(track.name);
|
|
7963
|
+
if (found === void 0) {
|
|
7964
|
+
found = findTrackTarget(model, track);
|
|
7965
|
+
destinations.set(track.name, found);
|
|
7966
|
+
}
|
|
7047
7967
|
if (!found?.object) return;
|
|
7048
7968
|
const { parsed, object } = found;
|
|
7049
7969
|
let target = null;
|
|
@@ -7052,8 +7972,9 @@ function serializeAnimationClips(model, clips, inspection) {
|
|
|
7052
7972
|
const mesh = object;
|
|
7053
7973
|
const requested = parsed.propertyIndex;
|
|
7054
7974
|
const indexValue = typeof requested === "number" ? requested : Number.isFinite(Number(requested)) ? Number(requested) : mesh.morphTargetDictionary?.[String(requested)];
|
|
7055
|
-
|
|
7056
|
-
|
|
7975
|
+
if (indexValue === void 0) return;
|
|
7976
|
+
const morphTargetId = morphIds.get(mesh)?.get(indexValue);
|
|
7977
|
+
if (morphTargetId !== void 0) target = { kind: "morphTarget", meshId: meshIds.get(mesh), morphTargetId };
|
|
7057
7978
|
} else if (property === "visible" && object.isMesh) {
|
|
7058
7979
|
target = { kind: "meshVisibility", meshId: meshIds.get(object) };
|
|
7059
7980
|
} else {
|
|
@@ -7067,7 +7988,7 @@ function serializeAnimationClips(model, clips, inspection) {
|
|
|
7067
7988
|
const kind = target.kind === "morphTarget" ? "face" : target.kind === "boneTransform" ? "body" : "scene";
|
|
7068
7989
|
const cubic = !!track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline;
|
|
7069
7990
|
const size = track.getValueSize() / (cubic ? 3 : 1);
|
|
7070
|
-
if (!
|
|
7991
|
+
if (!track.values.every(Number.isFinite)) {
|
|
7071
7992
|
throw new Error(`Animation track ${JSON.stringify(track.name)} contains non-finite samples.`);
|
|
7072
7993
|
}
|
|
7073
7994
|
const values = cubic ? Array.from(track.times).flatMap((_, key) => Array.from(track.values.slice((key * 3 + 1) * size, (key * 3 + 2) * size))) : Array.from(track.values);
|
|
@@ -7091,15 +8012,17 @@ function serializeAnimationClips(model, clips, inspection) {
|
|
|
7091
8012
|
});
|
|
7092
8013
|
}
|
|
7093
8014
|
var materials = (value) => value ? Array.isArray(value) ? value : [value] : [];
|
|
8015
|
+
var highlightOverlays = /* @__PURE__ */ new WeakSet();
|
|
7094
8016
|
var ThreeFrameApplier = class {
|
|
7095
8017
|
constructor() {
|
|
7096
8018
|
__publicField(this, "meshes", /* @__PURE__ */ new Map());
|
|
7097
8019
|
__publicField(this, "morphs", /* @__PURE__ */ new Map());
|
|
7098
8020
|
__publicField(this, "bones", /* @__PURE__ */ new Map());
|
|
7099
8021
|
__publicField(this, "objects", /* @__PURE__ */ new Map());
|
|
7100
|
-
__publicField(this, "
|
|
8022
|
+
__publicField(this, "highlighted", /* @__PURE__ */ new Map());
|
|
7101
8023
|
}
|
|
7102
8024
|
setBindings(inspection) {
|
|
8025
|
+
this.clearMeshHighlights();
|
|
7103
8026
|
this.meshes = new Map(inspection.meshBindings);
|
|
7104
8027
|
this.morphs = new Map(inspection.morphBindings);
|
|
7105
8028
|
this.bones = new Map(inspection.boneBindings);
|
|
@@ -7145,7 +8068,7 @@ var ThreeFrameApplier = class {
|
|
|
7145
8068
|
const config = configs[object.name];
|
|
7146
8069
|
if (!config) return;
|
|
7147
8070
|
if (typeof config.visible === "boolean") object.visible = config.visible;
|
|
7148
|
-
if (config.material) this.
|
|
8071
|
+
if (config.material) this.applyHighlightedMeshMaterial(object, config.material);
|
|
7149
8072
|
});
|
|
7150
8073
|
}
|
|
7151
8074
|
getMeshMaterialConfig(root, name) {
|
|
@@ -7153,38 +8076,97 @@ var ThreeFrameApplier = class {
|
|
|
7153
8076
|
this.visitMesh(root, name, (mesh) => {
|
|
7154
8077
|
const material = materials(mesh.material)[0];
|
|
7155
8078
|
if (!material) return;
|
|
7156
|
-
const blending = Object.
|
|
8079
|
+
const blending = Object.keys(THREE_BLENDING_MODES).find((key) => THREE_BLENDING_MODES[key] === material.blending) ?? "Normal";
|
|
7157
8080
|
result = { renderOrder: mesh.renderOrder, transparent: material.transparent, opacity: material.opacity, depthWrite: material.depthWrite, depthTest: material.depthTest, blending };
|
|
7158
8081
|
});
|
|
7159
8082
|
return result;
|
|
7160
8083
|
}
|
|
7161
8084
|
setMeshMaterialConfig(root, name, config) {
|
|
7162
|
-
this.visitMesh(root, name, (mesh) => this.
|
|
8085
|
+
this.visitMesh(root, name, (mesh) => this.applyHighlightedMeshMaterial(mesh, config));
|
|
8086
|
+
}
|
|
8087
|
+
applyHighlightedMeshMaterial(mesh, config) {
|
|
8088
|
+
this.applyMaterial(mesh, config);
|
|
8089
|
+
const original = this.highlighted.get(mesh)?.original;
|
|
8090
|
+
if (!original) return;
|
|
8091
|
+
const highlighted = mesh.material;
|
|
8092
|
+
try {
|
|
8093
|
+
mesh.material = original;
|
|
8094
|
+
this.applyMaterial(mesh, config);
|
|
8095
|
+
} finally {
|
|
8096
|
+
mesh.material = highlighted;
|
|
8097
|
+
}
|
|
7163
8098
|
}
|
|
7164
8099
|
setMeshVisible(root, name, visible) {
|
|
7165
8100
|
this.visitMesh(root, name, (mesh) => {
|
|
7166
8101
|
mesh.visible = visible;
|
|
7167
8102
|
});
|
|
7168
8103
|
}
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
8104
|
+
/** Apply a complete Rust snapshot. Only native resources and ID bindings live here. */
|
|
8105
|
+
applyMeshHighlightFrame(frame) {
|
|
8106
|
+
if (frame.version !== 1) throw new Error(`Unsupported mesh highlight frame version: ${frame.version}`);
|
|
8107
|
+
const selected = new Set(frame.highlights.map((write) => this.meshes.get(write.meshId)));
|
|
8108
|
+
for (const mesh of this.highlighted.keys()) {
|
|
8109
|
+
if (!selected.has(mesh)) this.clearMeshHighlight(mesh);
|
|
8110
|
+
}
|
|
8111
|
+
for (const write of frame.highlights) {
|
|
8112
|
+
if (write.edgeMode !== "triangles") throw new Error(`Unsupported highlight edge mode: ${write.edgeMode}`);
|
|
8113
|
+
const mesh = this.meshes.get(write.meshId);
|
|
8114
|
+
if (!mesh) continue;
|
|
8115
|
+
let state = this.highlighted.get(mesh);
|
|
8116
|
+
if (!state) {
|
|
8117
|
+
const original = mesh.material;
|
|
8118
|
+
const copies = materials(original).map((material) => {
|
|
8119
|
+
const copy2 = material.clone();
|
|
8120
|
+
copy2.polygonOffset = true;
|
|
8121
|
+
copy2.polygonOffsetFactor = 1;
|
|
8122
|
+
copy2.polygonOffsetUnits = 1;
|
|
8123
|
+
return copy2;
|
|
8124
|
+
});
|
|
8125
|
+
mesh.material = Array.isArray(original) ? copies : copies[0];
|
|
8126
|
+
const overlay = mesh.clone(false);
|
|
8127
|
+
overlay.name = "";
|
|
8128
|
+
overlay.userData = {};
|
|
8129
|
+
overlay.position.set(0, 0, 0);
|
|
8130
|
+
overlay.quaternion.identity();
|
|
8131
|
+
overlay.scale.set(1, 1, 1);
|
|
8132
|
+
overlay.matrix.identity();
|
|
8133
|
+
overlay.matrixAutoUpdate = true;
|
|
8134
|
+
overlay.visible = true;
|
|
8135
|
+
overlay.castShadow = overlay.receiveShadow = false;
|
|
8136
|
+
overlay.raycast = () => {
|
|
8137
|
+
};
|
|
8138
|
+
overlay.material = new MeshBasicMaterial({ wireframe: write.edgeMode === "triangles", transparent: true, depthWrite: false, toneMapped: false });
|
|
8139
|
+
overlay.onBeforeRender = () => {
|
|
8140
|
+
overlay.geometry = mesh.geometry;
|
|
8141
|
+
overlay.morphTargetInfluences = mesh.morphTargetInfluences;
|
|
8142
|
+
};
|
|
8143
|
+
highlightOverlays.add(overlay);
|
|
8144
|
+
mesh.add(overlay);
|
|
8145
|
+
state = { original, overlay };
|
|
8146
|
+
this.highlighted.set(mesh, state);
|
|
8147
|
+
}
|
|
8148
|
+
for (const material of materials(mesh.material)) {
|
|
7173
8149
|
if (!material.emissive) continue;
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
if (!this.originalEmissive.has(key)) this.originalEmissive.set(key, { color: material.emissive.getHex(), intensity: material.emissiveIntensity ?? 0 });
|
|
7177
|
-
material.emissive.setHex(color);
|
|
7178
|
-
material.emissiveIntensity = intensity2;
|
|
7179
|
-
} else {
|
|
7180
|
-
const original = this.originalEmissive.get(key);
|
|
7181
|
-
if (original) {
|
|
7182
|
-
material.emissive.setHex(original.color);
|
|
7183
|
-
material.emissiveIntensity = original.intensity;
|
|
7184
|
-
}
|
|
7185
|
-
}
|
|
8150
|
+
material.emissive.setRGB(...write.fillColor);
|
|
8151
|
+
material.emissiveIntensity = write.fillIntensity;
|
|
7186
8152
|
}
|
|
7187
|
-
|
|
8153
|
+
const edges = state.overlay.material;
|
|
8154
|
+
edges.color.setRGB(...write.edgeColor);
|
|
8155
|
+
edges.opacity = write.edgeOpacity;
|
|
8156
|
+
edges.side = materials(mesh.material)[0]?.side ?? edges.side;
|
|
8157
|
+
state.overlay.renderOrder = mesh.renderOrder + 1;
|
|
8158
|
+
}
|
|
8159
|
+
}
|
|
8160
|
+
clearMeshHighlights() {
|
|
8161
|
+
for (const mesh of this.highlighted.keys()) this.clearMeshHighlight(mesh);
|
|
8162
|
+
}
|
|
8163
|
+
clearMeshHighlight(mesh) {
|
|
8164
|
+
const state = this.highlighted.get(mesh);
|
|
8165
|
+
state.overlay.removeFromParent();
|
|
8166
|
+
state.overlay.material.dispose();
|
|
8167
|
+
for (const material of materials(mesh.material)) material.dispose();
|
|
8168
|
+
mesh.material = state.original;
|
|
8169
|
+
this.highlighted.delete(mesh);
|
|
7188
8170
|
}
|
|
7189
8171
|
applyHairAppearance(meshes, appearance) {
|
|
7190
8172
|
for (const mesh of meshes) {
|
|
@@ -7321,13 +8303,19 @@ var ThreeFrameApplier = class {
|
|
|
7321
8303
|
applyMaterial(mesh, config) {
|
|
7322
8304
|
if (typeof config.renderOrder === "number") mesh.renderOrder = config.renderOrder;
|
|
7323
8305
|
for (const material of materials(mesh.material)) {
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
8306
|
+
let changed = false;
|
|
8307
|
+
const assign = (key, value) => {
|
|
8308
|
+
if (material[key] === value) return;
|
|
8309
|
+
material[key] = value;
|
|
8310
|
+
changed = true;
|
|
8311
|
+
};
|
|
8312
|
+
if (typeof config.opacity === "number") assign("opacity", config.opacity);
|
|
8313
|
+
if (typeof config.transparent === "boolean") assign("transparent", config.transparent);
|
|
8314
|
+
else if (typeof config.opacity === "number" && config.opacity < 1) assign("transparent", true);
|
|
8315
|
+
if (typeof config.depthWrite === "boolean") assign("depthWrite", config.depthWrite);
|
|
8316
|
+
if (typeof config.depthTest === "boolean") assign("depthTest", config.depthTest);
|
|
8317
|
+
if (config.blending) assign("blending", THREE_BLENDING_MODES[config.blending]);
|
|
8318
|
+
if (changed) material.needsUpdate = true;
|
|
7331
8319
|
}
|
|
7332
8320
|
}
|
|
7333
8321
|
visitMesh(root, name, visit) {
|
|
@@ -7339,11 +8327,11 @@ var ThreeFrameApplier = class {
|
|
|
7339
8327
|
var collectMorphMeshes = (root) => {
|
|
7340
8328
|
const meshes = [];
|
|
7341
8329
|
root.traverse((object) => {
|
|
7342
|
-
if (object.isMesh && morphEntries(object).length > 0) meshes.push(object);
|
|
8330
|
+
if (object.isMesh && !highlightOverlays.has(object) && morphEntries(object).length > 0) meshes.push(object);
|
|
7343
8331
|
});
|
|
7344
8332
|
return meshes;
|
|
7345
8333
|
};
|
|
7346
8334
|
|
|
7347
|
-
export { BLENDING_MODES, CHARACTER_SCENE_TYPES, CHARACTER_SCENE_TYPE_IDS, ThreeAnnotationController as CameraController, CameraDOMControls, CameraDOMControls as Controls, DEFAULT_CHARACTER_LIGHTING_PRESETS, DEFAULT_CHARACTER_LIGHTING_PRESET_ID, DEFAULT_CHARACTER_LIGHTING_PRESET_IDS, DEFAULT_CHARACTER_LIGHTING_SETTINGS, DEFAULT_CHARACTER_SCENE_TYPE_ID, DEFAULT_MARKER_AUTO_HIDE_MS, DEFAULT_PREVIEW_AUTO_CLEAR_MS, Embody, HtmlAnnotationMarkers, ThreeAnnotationMarkers as Markers3D, HtmlAnnotationMarkers as MarkersHTML, MorphVertexPreview, HtmlAnnotationMarkers as OverlayAnnotationMarkers, RUNTIME_ANNOTATION_REGION_PREFIX, RustAnnotationCameraCore, SkeletonOverlay, THREE_BLENDING_MODES, ThreeAnnotationController, ThreeAnnotationMarkers, ThreeAnnotations, ThreeFrameApplier, ThreeModelInspector, ThreeNativePlaybackConnector, analyzeModel, applyAUBoneBindingUpdate, applyBilateralAxisBindingUpdate, applyBoneAxisBindingUpdate, applyCharacterModelTransform, bindModelReferencePose, boneResolutionProfile, buildBoneAuOptions, buildMappedBoneInfo, buildMorphVertexPreviewRenderData, buildMorphVertexPreviewSample, buildRegionDisplayOptions, captureModelReferencePose, classifyAuAsJointControl, collectMorphMeshes, collectSkeletonBones, computeHumanoidSkeletonTemplateRestBounds, createAnimationClipFromClipIR, createBilateralBoneAxisAu, createBoneAxisAu, createCharacterHost, createDefaultCharacterLighting, createDefaultCharacterScene, createDefaultCharacterSceneAsync, createDefaultCharacterSceneRuntime, createMarkerLifecycleRevealOptions, createMarkerVisibilityLifecycle, createRuntimeAnnotationPreviewLifecycle, createRustAnnotationCameraCore, createRustEmbodyHost, createRustEmbodyHostUnbound, createScenePlaybackFrame, createShadowPlane, createValidationSkeletonFromHumanoidTemplate, detectAnnotationLaterality, disposeCharacterModel, ensureBilateralBoneNodeKeys, ensureBoneNodeKey, extendCharacterConfigWithPreset, extendModelReferencePose, extractHumanoidSkeletonTemplateFromModel, extractModelData, extractProfileOverrides, filterCameraViewRegions, findModelRoot, findNodeKeyForBone, formatAxisDirectionLabel, formatAxisLabel, fuzzyNameMatch, getAUBoneBindingState, getAxisFromChannel, getBilateralAxisBindingState, getBoneAxisBindingState, getDefaultAnnotationLaterality, getHumanoidSkeletonTemplate, getHumanoidSkeletonTemplateBoneNames, getHumanoidSpecification, getMeshNamesForAUProfile, getModelLocalOrbitAngle, getPreset, getRuntimeAUMeshSideOffset, getRuntimeAUMorphAnchorPoint, getRuntimeAnnotationSide, getSemanticHorizontalSign, getSemanticHorizontalSignForSide, getWorldDirectionForCameraAngle, hasLeftRightMorphs, inferChiralBoneNamePair, inferEyeControlFamily, inferEyeControlScope, inferRuntimeAnnotationPreviewSide, isJointControlAuInfo, isMaxDegreesOnlyAxisBindingUpdate, isMixedAU, isRuntimeAnnotationRegionName, loadCharacterModel, mergeCharacterRegionsByName, normalizeDefaultCharacterLightingSettings, normalizeHairColorAppearance, parseCharacterModel, passesMarkerCameraAngleGate, pickContinuumAnnotationAuId, renderScenePlayback, resolveAnnotationCharacterConfig, resolveBoneAxisChannel, resolveBoneNameForNodeKey, resolveBoneNames, resolveContinuumDisplayLabel, resolveFaceCenter, resolveHumanoidCharacterization, resolveMarkerAnchorRegion, resolveMorphPreviewMeshes, resolveRegionCameraAngle, resolveRegionVisibilityCameraAngle, resolveSkeletonOverlayStartBoneName, resolveVisemeProfile, serializeAnimationClips, setAUMorphTargets, setHumanoidRoleBinding, shouldProjectMarkerAnchorToSurface, shouldUseFaceCenterForMarkerAnchor, stripConfiguredBoneAffixes, supportsRustEmbodyPreset, toWorldDirection, validateHumanoidCharacterization, validateMappings };
|
|
8335
|
+
export { BLENDING_MODES, CHARACTER_SCENE_TYPES, CHARACTER_SCENE_TYPE_IDS, ThreeAnnotationController as CameraController, CameraDOMControls, CameraDOMControls as Controls, DEFAULT_CHARACTER_LIGHTING_PRESETS, DEFAULT_CHARACTER_LIGHTING_PRESET_ID, DEFAULT_CHARACTER_LIGHTING_PRESET_IDS, DEFAULT_CHARACTER_LIGHTING_SETTINGS, DEFAULT_CHARACTER_SCENE_TYPE_ID, DEFAULT_MARKER_AUTO_HIDE_MS, DEFAULT_PREVIEW_AUTO_CLEAR_MS, Embody, HtmlAnnotationMarkers, ThreeAnnotationMarkers as Markers3D, HtmlAnnotationMarkers as MarkersHTML, MorphVertexPreview, HtmlAnnotationMarkers as OverlayAnnotationMarkers, RUNTIME_ANNOTATION_REGION_PREFIX, RustAnnotationCameraCore, SkeletonOverlay, THREE_BLENDING_MODES, ThreeAnnotationController, ThreeAnnotationMarkers, ThreeAnnotations, ThreeFrameApplier, ThreeModelInspector, ThreeNativePlaybackConnector, analyzeModel, applyAUBoneBindingUpdate, applyBilateralAxisBindingUpdate, applyBoneAxisBindingUpdate, applyCharacterModelTransform, bindModelReferencePose, boneResolutionProfile, buildBoneAuOptions, buildMappedBoneInfo, buildMorphVertexPreviewRenderData, buildMorphVertexPreviewSample, buildRegionDisplayOptions, captureModelReferencePose, classifyAuAsJointControl, collectMorphMeshes, collectSkeletonBones, computeHumanoidSkeletonTemplateRestBounds, convertMixamoFbxToVrmAnimations, convertMixamoToVrmAnimation, createAnimationClipFromClipIR, createAnimationClipFromVrmAnimation, createBilateralBoneAxisAu, createBoneAxisAu, createCharacterHost, createDefaultCharacterLighting, createDefaultCharacterScene, createDefaultCharacterSceneAsync, createDefaultCharacterSceneRuntime, createMarkerLifecycleRevealOptions, createMarkerVisibilityLifecycle, createRuntimeAnnotationPreviewLifecycle, createRustAnnotationCameraCore, createRustEmbodyHost, createRustEmbodyHostUnbound, createScenePlaybackFrame, createSceneSceneryRenderer, createShadowPlane, createValidationSkeletonFromHumanoidTemplate, detectAnnotationLaterality, disposeCharacterModel, ensureBilateralBoneNodeKeys, ensureBoneNodeKey, extendCharacterConfigWithPreset, extendModelReferencePose, extractHumanoidSkeletonTemplateFromModel, extractModelData, extractProfileOverrides, filterCameraViewRegions, findModelRoot, findNodeKeyForBone, formatAxisDirectionLabel, formatAxisLabel, fuzzyNameMatch, getAUBoneBindingState, getAUBoneBindingStates, getAxisFromChannel, getBilateralAxisBindingState, getBoneAxisBindingState, getDefaultAnnotationLaterality, getHumanoidSkeletonTemplate, getHumanoidSkeletonTemplateBoneNames, getHumanoidSpecification, getMeshNamesForAUProfile, getModelLocalOrbitAngle, getPreset, getRuntimeAUMeshSideOffset, getRuntimeAUMorphAnchorPoint, getRuntimeAnnotationSide, getSemanticHorizontalSign, getSemanticHorizontalSignForSide, getWorldDirectionForCameraAngle, hasLeftRightMorphs, inferChiralBoneNamePair, inferEyeControlFamily, inferEyeControlScope, inferRuntimeAnnotationPreviewSide, inspectVrmAnimation, isJointControlAuInfo, isMaxDegreesOnlyAxisBindingUpdate, isMixedAU, isRuntimeAnnotationRegionName, loadCharacterModel, mergeCharacterRegionsByName, normalizeDefaultCharacterLightingSettings, normalizeHairColorAppearance, parseCharacterModel, passesMarkerCameraAngleGate, pickContinuumAnnotationAuId, renderScenePlayback, resolveAnnotationCharacterConfig, resolveBoneAxisChannel, resolveBoneNameForNodeKey, resolveBoneNames, resolveContinuumDisplayLabel, resolveFaceCenter, resolveHumanoidCharacterization, resolveMarkerAnchorRegion, resolveMorphMeshSelections, resolveMorphPreviewMeshes, resolveRegionCameraAngle, resolveRegionVisibilityCameraAngle, resolveSkeletonOverlayStartBoneName, resolveVisemeProfile, serializeAnimationClips, setAUMorphTargets, setHumanoidRoleBinding, shouldProjectMarkerAnchorToSurface, shouldUseFaceCenterForMarkerAnchor, stripConfiguredBoneAffixes, supportsRustEmbodyPreset, toWorldDirection, validateHumanoidCharacterization, validateMappings };
|
|
7348
8336
|
//# sourceMappingURL=three.js.map
|
|
7349
8337
|
//# sourceMappingURL=three.js.map
|