@lovelace_lol/embody 0.0.15 → 0.0.17
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 +407 -149
- package/dist/index.js +1426 -381
- 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 +18 -18
- package/dist/three/authoringTypes.d.ts +38 -4
- package/dist/three/character.d.ts +58 -14
- package/dist/three/index.d.ts +14 -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/sceneVideoCapture.d.ts +12 -0
- package/dist/three/scenery.d.ts +46 -0
- package/dist/three/vrm-animation.d.ts +70 -0
- package/dist/three.js +1426 -381
- package/dist/three.js.map +1 -1
- package/dist/wasm/README.md +407 -149
- package/dist/wasm/embody_wasm.d.ts +241 -95
- 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 +113 -88
- 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,110 +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 resolveMorphMeshSelections = (profile, categories, availableNames) => request2("profile.resolveMorphMeshSelections", { profile, categories, availableNames });
|
|
2270
|
-
var getHumanoidSpecification = () => request2("humanoid.getSpecification");
|
|
2271
|
-
var setAUMorphTargets = (profile, auId, side, targets) => request2("profile.setAUMorphTargets", { profile, auId, side, targets });
|
|
2272
|
-
var setHumanoidRoleBinding = (profile, role, boneName) => request2("profile.setHumanoidRoleBinding", { profile, role, boneName });
|
|
2273
|
-
var resolveHumanoidCharacterization = (profile) => request2("profile.resolveHumanoidCharacterization", { profile });
|
|
2274
|
-
var hasLeftRightMorphs = (profile, auId) => request2("profile.hasLeftRightMorphs", { profile, auId });
|
|
2275
|
-
var isMixedAU = (profile, auId) => request2("profile.isMixedAU", { profile, auId });
|
|
2276
|
-
var resolveVisemeProfile = (profile) => request2("profile.resolveVisemes", { profile });
|
|
2277
|
-
var isMaxDegreesOnlyAxisBindingUpdate = (update) => request2("bone.isMaxDegreesOnlyUpdate", { update });
|
|
2278
|
-
var findNodeKeyForBone = (boneName, profile) => profile ? request2("bone.findNodeKey", { profile, boneName }) : null;
|
|
2279
|
-
var ensureBoneNodeKey = (profile, boneName) => request2("bone.ensureNodeKey", { profile, boneName });
|
|
2280
|
-
var ensureBilateralBoneNodeKeys = (profile, leftBoneName, rightBoneName) => request2("bone.ensureBilateralNodeKeys", { profile, leftBoneName, rightBoneName });
|
|
2281
|
-
var resolveBoneNameForNodeKey = (profile, nodeKey) => request2("bone.resolveBoneName", { profile, nodeKey });
|
|
2282
|
-
var stripConfiguredBoneAffixes = (profile, boneName) => request2("bone.stripAffixes", { profile: profile ?? {}, boneName });
|
|
2283
|
-
var inferChiralBoneNamePair = (boneNames, selectedBoneName) => request2("bone.inferChiralPair", { boneNames, selectedBoneName });
|
|
2284
|
-
var formatAxisLabel = (axis) => request2("bone.formatAxisLabel", { axis });
|
|
2285
|
-
var formatAxisDirectionLabel = (axis, direction) => request2("bone.formatDirectionLabel", { axis, direction });
|
|
2286
|
-
var getAxisFromChannel = (channel) => channel ? request2("bone.getAxisFromChannel", { channel }) : null;
|
|
2287
|
-
var getBoneAxisBindingState = (profile, boneName, axis) => request2("bone.getAxisState", { profile, boneName, axis });
|
|
2288
|
-
var resolveBoneAxisChannel = (profile, boneName, axis) => request2("bone.resolveAxisChannel", { profile, boneName, axis });
|
|
2289
|
-
var getBilateralAxisBindingState = (profile, boneName, axis) => request2("bone.getBilateralAxisState", { profile, boneName, axis });
|
|
2290
|
-
var getAUBoneBindingState = (profile, auId) => request2("bone.getAUBindingState", { profile, auId });
|
|
2291
|
-
var getAUBoneBindingStates = (profile, auIds) => request2("bone.getAUBindingStates", {
|
|
2292
|
-
profile: {
|
|
2293
|
-
auToBones: profile.auToBones,
|
|
2294
|
-
boneNodes: profile.boneNodes,
|
|
2295
|
-
bonePrefix: profile.bonePrefix,
|
|
2296
|
-
boneSuffix: profile.boneSuffix,
|
|
2297
|
-
suffixPattern: profile.suffixPattern,
|
|
2298
|
-
compositeRotations: profile.compositeRotations,
|
|
2299
|
-
continuumPairs: profile.continuumPairs,
|
|
2300
|
-
humanoidCharacterization: profile.humanoidCharacterization
|
|
2301
|
-
},
|
|
2302
|
-
auIds
|
|
2303
|
-
});
|
|
2304
|
-
var inferEyeControlScope = (profile, binding) => request2("bone.inferEyeScope", { profile, binding });
|
|
2305
|
-
var inferEyeControlFamily = (binding) => request2("bone.inferEyeFamily", { binding });
|
|
2306
|
-
var applyBoneAxisBindingUpdate = (profile, boneName, axis, update) => request2("bone.applyAxisUpdate", { profile, boneName, axis, update });
|
|
2307
|
-
var applyBilateralAxisBindingUpdate = (profile, boneName, axis, scope, update) => request2("bone.applyBilateralAxisUpdate", { profile, boneName, axis, scope, update });
|
|
2308
|
-
var applyAUBoneBindingUpdate = (profile, auId, update) => request2("bone.applyAUBindingUpdate", { profile, auId, update });
|
|
2309
|
-
var createBoneAxisAu = (profile, boneName, axis, direction, options = {}) => request2("bone.createAxisAU", { profile, boneName, axis, direction, name: options.name });
|
|
2310
|
-
var createBilateralBoneAxisAu = (profile, boneName, axis, scope, direction, options = {}) => request2("bone.createBilateralAxisAU", { profile, boneName, axis, scope, direction, name: options.name });
|
|
2311
|
-
var buildBoneAuOptions = (profile) => request2("bone.buildAUOptions", { profile });
|
|
2312
|
-
var isJointControlAuInfo = (info) => request2("bone.isJointControlInfo", { info });
|
|
2313
|
-
var classifyAuAsJointControl = (profile, auId, options = {}) => request2("bone.classifyJointControl", { profile, auId, boneName: options.boneName, nodeKey: options.nodeKey });
|
|
2314
|
-
var resolveContinuumDisplayLabel = (profile, negativeAuId, positiveAuId) => request2("bone.resolveContinuumLabel", { profile, negativeAuId, positiveAuId }) ?? `AU ${negativeAuId} <-> AU ${positiveAuId}`;
|
|
2315
|
-
function inspectModel(model, meshes, profile) {
|
|
2316
|
-
return new ThreeModelInspector().inspectModel(model, { meshes, profile });
|
|
2317
|
-
}
|
|
2318
|
-
function extractModelData(model, meshes, animations = []) {
|
|
2319
|
-
const inspection = inspectModel(model, meshes);
|
|
2320
|
-
return JSON.parse(requireInitializedEmbodyCore().extract_model_data_json(json(inspection.descriptor), json(serializeAnimationClips(model, animations, inspection))));
|
|
2321
|
-
}
|
|
2322
|
-
async function analyzeModel(options) {
|
|
2323
|
-
const source = options.source;
|
|
2324
|
-
const model = source.type === "gltf" ? source.gltf.scene ?? source.gltf.scenes[0] : source.model;
|
|
2325
|
-
const animations = source.type === "gltf" ? source.gltf.animations : source.animations ?? [];
|
|
2326
|
-
const inspection = inspectModel(model, source.type === "runtime" ? source.meshes : void 0);
|
|
2327
|
-
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) })));
|
|
2328
|
-
}
|
|
2329
|
-
function validateMappings(meshes, skeleton, profile, options = {}) {
|
|
2330
|
-
let nextMorphId = 1;
|
|
2331
|
-
const morphTargets = [];
|
|
2332
|
-
const descriptorMeshes = (meshes ?? []).map((value, index) => {
|
|
2333
|
-
const mesh = value;
|
|
2334
|
-
const id = index + 1;
|
|
2335
|
-
const morphTargetIds = Object.entries(mesh.morphTargetDictionary ?? mesh.geometry?.morphTargetDictionary ?? {}).map(([name, hostIndex]) => {
|
|
2336
|
-
const morphId = nextMorphId++;
|
|
2337
|
-
morphTargets.push({ id: morphId, meshId: id, name, hostIndex });
|
|
2338
|
-
return morphId;
|
|
2339
|
-
});
|
|
2340
|
-
return { id, name: mesh.name ?? "", morphTargetIds, visible: true };
|
|
2341
|
-
});
|
|
2342
|
-
const bones = (skeleton?.bones ?? []).map((bone, index) => ({ id: index + 1, name: typeof bone === "string" ? bone : bone.name ?? "" }));
|
|
2343
|
-
return JSON.parse(requireInitializedEmbodyCore().validate_profile_model(json(profile), json({ meshes: descriptorMeshes, morphTargets, bones, objects: [] }), json(options)));
|
|
2344
|
-
}
|
|
2345
|
-
function validateHumanoidCharacterization(model, profile) {
|
|
2346
|
-
return JSON.parse(requireInitializedEmbodyCore().validate_humanoid_characterization(json(profile), json(inspectModel(model, void 0, profile).descriptor)));
|
|
2347
|
-
}
|
|
2348
|
-
function normalizeHairColorAppearance(value, fallback = DEFAULT_HAIR_COLOR_APPEARANCE) {
|
|
2349
|
-
return JSON.parse(requireInitializedEmbodyCore().normalize_hair_color_appearance_json(json(value), json(fallback)));
|
|
2350
|
-
}
|
|
2351
|
-
var getHumanoidSkeletonTemplate = (id) => HUMANOID_SKELETON_TEMPLATES.find((template) => template.id === id) ?? null;
|
|
2352
|
-
function getHumanoidSkeletonTemplateBoneNames(template = CC4_HUMANOID_SKELETON_TEMPLATE) {
|
|
2353
|
-
return JSON.parse(requireInitializedEmbodyCore().humanoid_skeleton_template_bone_names(json(template)));
|
|
2354
|
-
}
|
|
2355
|
-
function createValidationSkeletonFromHumanoidTemplate(template = CC4_HUMANOID_SKELETON_TEMPLATE) {
|
|
2356
|
-
return JSON.parse(requireInitializedEmbodyCore().create_validation_skeleton_json(json(template)));
|
|
2357
|
-
}
|
|
2358
|
-
function computeHumanoidSkeletonTemplateRestBounds(template) {
|
|
2359
|
-
return Array.from(requireInitializedEmbodyCore().compute_humanoid_skeleton_template_rest_bounds(json(template)));
|
|
2360
|
-
}
|
|
2361
|
-
function extractHumanoidSkeletonTemplateFromModel(model, options) {
|
|
2362
|
-
return JSON.parse(requireInitializedEmbodyCore().extract_humanoid_skeleton_template_json(json(inspectModel(model).descriptor), json(options)));
|
|
2363
|
-
}
|
|
2364
2366
|
var isBone = (object) => !!(object.isBone || object.type === "Bone");
|
|
2365
2367
|
var finite = (values, description) => {
|
|
2366
2368
|
if (!values.every(Number.isFinite)) {
|
|
@@ -2485,6 +2487,286 @@ function bindModelReferencePose(model, referencePose) {
|
|
|
2485
2487
|
if (index !== referencePose.nodes.length) throw new Error("Incompatible reference pose hierarchy: extra captured nodes");
|
|
2486
2488
|
return bindings2;
|
|
2487
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
|
+
}
|
|
2488
2770
|
function withComponentTracks(clip) {
|
|
2489
2771
|
let expanded = false;
|
|
2490
2772
|
const tracks = clip.tracks.flatMap((track) => {
|
|
@@ -2580,6 +2862,23 @@ var ThreeNativePlaybackConnector = class {
|
|
|
2580
2862
|
__publicField(this, "disposed", false);
|
|
2581
2863
|
__publicField(this, "onError");
|
|
2582
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
|
+
});
|
|
2583
2882
|
__publicField(this, "finished", (event) => {
|
|
2584
2883
|
const handle = this.actionHandles.get(event.action);
|
|
2585
2884
|
if (!handle || this.disposed) return;
|
|
@@ -2602,8 +2901,14 @@ var ThreeNativePlaybackConnector = class {
|
|
|
2602
2901
|
this.binding = { ...binding, mixer: binding.mixer ?? new AnimationMixer(binding.model) };
|
|
2603
2902
|
this.onError = binding.onError;
|
|
2604
2903
|
this.onRelease = binding.onRelease;
|
|
2904
|
+
this.afterSample = binding.afterSample;
|
|
2605
2905
|
this.binding.mixer.addEventListener("finished", this.finished);
|
|
2606
|
-
this.rendering = bindPlaybackRendering(
|
|
2906
|
+
this.rendering = bindPlaybackRendering(
|
|
2907
|
+
binding.model,
|
|
2908
|
+
this.binding.mixer,
|
|
2909
|
+
this.afterFrame,
|
|
2910
|
+
this.sampleResponse
|
|
2911
|
+
);
|
|
2607
2912
|
}
|
|
2608
2913
|
/** Prepare bindings first; old actions are released before the new binding is exposed. */
|
|
2609
2914
|
rebind(modelKey, profileKey, binding = this.binding) {
|
|
@@ -2614,7 +2919,12 @@ var ThreeNativePlaybackConnector = class {
|
|
|
2614
2919
|
this.rendering.dispose();
|
|
2615
2920
|
this.binding = { ...binding, mixer: binding.mixer ?? new AnimationMixer(binding.model) };
|
|
2616
2921
|
this.binding.mixer.addEventListener("finished", this.finished);
|
|
2617
|
-
this.rendering = bindPlaybackRendering(
|
|
2922
|
+
this.rendering = bindPlaybackRendering(
|
|
2923
|
+
binding.model,
|
|
2924
|
+
this.binding.mixer,
|
|
2925
|
+
this.afterFrame,
|
|
2926
|
+
this.sampleResponse
|
|
2927
|
+
);
|
|
2618
2928
|
this.rendering.setEnabled(this.renderingEnabled);
|
|
2619
2929
|
});
|
|
2620
2930
|
}
|
|
@@ -2948,7 +3258,15 @@ var intensity = (options, fallback = 1) => {
|
|
|
2948
3258
|
const value = options.intensityScale ?? options.intensity ?? options.weight;
|
|
2949
3259
|
return finite2(value) ? Math.max(0, Math.min(2, value)) : fallback;
|
|
2950
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
|
+
};
|
|
2951
3267
|
var presetId = (value) => value?.trim().toLowerCase() || "cc4";
|
|
3268
|
+
var HAIR_RESPONSE = "__embody_hair_response";
|
|
3269
|
+
var HAIR_IMPULSE_CLIP = "__embody_hair_impulse";
|
|
2952
3270
|
function supportsRustEmbodyPreset(value) {
|
|
2953
3271
|
return requireInitializedEmbodyCore().has_preset(presetId(value));
|
|
2954
3272
|
}
|
|
@@ -2972,10 +3290,12 @@ var Embody = class _Embody {
|
|
|
2972
3290
|
__publicField(this, "profileMode");
|
|
2973
3291
|
__publicField(this, "preset");
|
|
2974
3292
|
__publicField(this, "sources", /* @__PURE__ */ new Map());
|
|
3293
|
+
__publicField(this, "vrmAnimations", /* @__PURE__ */ new Map());
|
|
2975
3294
|
__publicField(this, "playbacks", /* @__PURE__ */ new Map());
|
|
2976
3295
|
__publicField(this, "current", /* @__PURE__ */ new Map());
|
|
2977
3296
|
__publicField(this, "playDepth", 0);
|
|
2978
3297
|
__publicField(this, "pendingEvents", []);
|
|
3298
|
+
__publicField(this, "pendingReleases", []);
|
|
2979
3299
|
__publicField(this, "bindingGeneration", 0);
|
|
2980
3300
|
__publicField(this, "disposed", false);
|
|
2981
3301
|
__publicField(this, "rendering", true);
|
|
@@ -2983,6 +3303,7 @@ var Embody = class _Embody {
|
|
|
2983
3303
|
__publicField(this, "nextLiveTarget", 0);
|
|
2984
3304
|
__publicField(this, "liveTargets", /* @__PURE__ */ new Map());
|
|
2985
3305
|
__publicField(this, "hairObjects", /* @__PURE__ */ new Map());
|
|
3306
|
+
__publicField(this, "hairHeadObservation");
|
|
2986
3307
|
__publicField(this, "hairConfig", {});
|
|
2987
3308
|
__publicField(this, "hairAppearance", {
|
|
2988
3309
|
hairColor: copy(DEFAULT_HAIR_COLOR_APPEARANCE),
|
|
@@ -3032,6 +3353,7 @@ var Embody = class _Embody {
|
|
|
3032
3353
|
this.assertAlive();
|
|
3033
3354
|
const model = options.model;
|
|
3034
3355
|
if (!model?.isObject3D) throw new Error("onReady requires a Three Object3D model.");
|
|
3356
|
+
this.applier.clearMeshHighlights();
|
|
3035
3357
|
const reference = this.references.get(model) ?? options.referencePose ?? (!this.boundModel ? this.config.referencePose : void 0) ?? captureModelReferencePose(model);
|
|
3036
3358
|
const inspection = this.inspector.inspectModel(model, { meshes: options.meshes, profile: this.profile, referencePose: reference });
|
|
3037
3359
|
this.bind(model, reference, inspection, this.profileSource, this.profile);
|
|
@@ -3071,7 +3393,10 @@ var Embody = class _Embody {
|
|
|
3071
3393
|
}
|
|
3072
3394
|
this.core.free();
|
|
3073
3395
|
this.core = core;
|
|
3074
|
-
if (replacingModel)
|
|
3396
|
+
if (replacingModel) {
|
|
3397
|
+
this.sources.clear();
|
|
3398
|
+
this.vrmAnimations.clear();
|
|
3399
|
+
}
|
|
3075
3400
|
this.boundModel = model;
|
|
3076
3401
|
this.inspection = inspection;
|
|
3077
3402
|
this.referenceBindings = referenceBindings;
|
|
@@ -3092,10 +3417,17 @@ var Embody = class _Embody {
|
|
|
3092
3417
|
this.connector = new ThreeNativePlaybackConnector(session, {
|
|
3093
3418
|
model,
|
|
3094
3419
|
inspection,
|
|
3420
|
+
onFrame: (deltaSeconds) => {
|
|
3421
|
+
if (this.disposed) return;
|
|
3422
|
+
this.applier.applyMeshHighlightFrame(parse(this.core.advance_mesh_highlight(deltaSeconds)));
|
|
3423
|
+
},
|
|
3095
3424
|
onRelease: (id) => {
|
|
3096
3425
|
const playback = this.playbacks.get(id);
|
|
3097
|
-
if (playback?.terminal)
|
|
3098
|
-
|
|
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)
|
|
3099
3431
|
});
|
|
3100
3432
|
} catch (error) {
|
|
3101
3433
|
session.free();
|
|
@@ -3106,17 +3438,20 @@ var Embody = class _Embody {
|
|
|
3106
3438
|
this.connector.setTimeScale(this.timeScale);
|
|
3107
3439
|
this.connector.subscribe((event) => this.onPlaybackEvent(event));
|
|
3108
3440
|
this.hairConfig = parse(this.core.get_hair_config_json());
|
|
3441
|
+
this.bindHairHead(model, inspection);
|
|
3109
3442
|
this.registerHairObjects(inspection.allMeshes.filter((mesh) => ["hair", "eyebrow"].includes(this.meshCategory(mesh))));
|
|
3110
3443
|
if (preserveLiveState) this.commitLive();
|
|
3111
3444
|
}
|
|
3112
3445
|
bindModel(meshes) {
|
|
3113
3446
|
const model = this.requireModel();
|
|
3447
|
+
this.applier.clearMeshHighlights();
|
|
3114
3448
|
const reference = this.references.get(model);
|
|
3115
3449
|
const inspection = this.inspector.inspectModel(model, { meshes, profile: this.profile, referencePose: reference });
|
|
3116
3450
|
this.bind(model, reference, inspection, this.profileSource, this.profile);
|
|
3117
3451
|
}
|
|
3118
3452
|
extendReferencePose() {
|
|
3119
3453
|
const model = this.requireModel();
|
|
3454
|
+
this.applier.clearMeshHighlights();
|
|
3120
3455
|
const reference = extendModelReferencePose(model, this.references.get(model));
|
|
3121
3456
|
const inspection = this.inspector.inspectModel(model, { profile: this.profile, referencePose: reference });
|
|
3122
3457
|
this.bind(model, reference, inspection, this.profileSource, this.profile);
|
|
@@ -3131,6 +3466,7 @@ var Embody = class _Embody {
|
|
|
3131
3466
|
this.profile = profile;
|
|
3132
3467
|
return;
|
|
3133
3468
|
}
|
|
3469
|
+
this.applier.clearMeshHighlights();
|
|
3134
3470
|
const reference = this.references.get(this.boundModel);
|
|
3135
3471
|
const inspection = this.inspector.inspectModel(this.boundModel, { profile, referencePose: reference });
|
|
3136
3472
|
this.bind(this.boundModel, reference, inspection, source, profile, false);
|
|
@@ -3155,12 +3491,16 @@ var Embody = class _Embody {
|
|
|
3155
3491
|
const playback = this.playbacks.get(event.handle);
|
|
3156
3492
|
if (!playback) return;
|
|
3157
3493
|
playback.terminal = true;
|
|
3158
|
-
if (!playback.connector.getAction(playback.id))
|
|
3159
|
-
this.playbacks.delete(playback.id);
|
|
3160
|
-
if (this.current.get(playback.source.name) === playback) this.current.delete(playback.source.name);
|
|
3161
|
-
}
|
|
3494
|
+
if (!playback.connector.getAction(playback.id)) this.forget(playback);
|
|
3162
3495
|
playback.notify({ ...event, actionId: event.handle, clipName: playback.source.name });
|
|
3163
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
|
+
}
|
|
3164
3504
|
compile(source, options) {
|
|
3165
3505
|
const buildOptions = { ..."options" in source ? source.options : {}, ...options, intensityScale: intensity(options) };
|
|
3166
3506
|
switch (source.kind) {
|
|
@@ -3230,7 +3570,10 @@ var Embody = class _Embody {
|
|
|
3230
3570
|
const candidates = outgoing?.getClip().tracks;
|
|
3231
3571
|
const oldTrack = candidates?.[index]?.name === track.name ? candidates[index] : candidates?.find((candidate) => candidate.name === track.name);
|
|
3232
3572
|
if (oldTrack && outgoing) {
|
|
3233
|
-
|
|
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);
|
|
3234
3577
|
if (outgoing.blendMode === AdditiveAnimationBlendMode && rest) {
|
|
3235
3578
|
previous = track.ValueTypeName === "quaternion" ? new Quaternion().fromArray(rest).multiply(new Quaternion().fromArray(previous)).toArray() : previous.map((value, axis) => value + rest[axis]);
|
|
3236
3579
|
}
|
|
@@ -3366,7 +3709,7 @@ var Embody = class _Embody {
|
|
|
3366
3709
|
validateEasing(options) {
|
|
3367
3710
|
if (options.easing !== void 0 && !["linear", "easeIn", "easeOut", "easeInOut", "easeInOutCubic"].includes(options.easing)) throw new Error(`Unknown playback easing: ${options.easing}`);
|
|
3368
3711
|
}
|
|
3369
|
-
handle(source, options, autoPlay) {
|
|
3712
|
+
handle(source, options, autoPlay, owner = "caller") {
|
|
3370
3713
|
options = { ..."options" in source ? source.options : {}, ...options };
|
|
3371
3714
|
const listeners = /* @__PURE__ */ new Set();
|
|
3372
3715
|
const generation = this.bindingGeneration;
|
|
@@ -3394,8 +3737,10 @@ var Embody = class _Embody {
|
|
|
3394
3737
|
const connector = this.requireConnector();
|
|
3395
3738
|
const inheritedStarts = /* @__PURE__ */ new Map();
|
|
3396
3739
|
const clip = this.materialize(source, options, inheritedStarts);
|
|
3397
|
-
const hairMotion = this.authoredHeadMotion(clip);
|
|
3398
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;
|
|
3399
3744
|
const settings = this.nativeOptions(options, clip);
|
|
3400
3745
|
if (previous && connector.getAction(previous.id)) settings.replace = previous.id;
|
|
3401
3746
|
if (current) newPromise();
|
|
@@ -3407,12 +3752,30 @@ var Embody = class _Embody {
|
|
|
3407
3752
|
id = connector.playNativeClip(clip, settings);
|
|
3408
3753
|
} catch (error) {
|
|
3409
3754
|
this.playDepth -= 1;
|
|
3410
|
-
|
|
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();
|
|
3411
3773
|
throw error;
|
|
3412
3774
|
}
|
|
3413
3775
|
const playback = {
|
|
3414
3776
|
id,
|
|
3415
3777
|
source,
|
|
3778
|
+
owner,
|
|
3416
3779
|
options: { ...options, intensityScale: intensity(options) },
|
|
3417
3780
|
connector,
|
|
3418
3781
|
terminal: false,
|
|
@@ -3454,7 +3817,7 @@ var Embody = class _Embody {
|
|
|
3454
3817
|
if (this.connector !== connector || !connector.getAction(id)) return result;
|
|
3455
3818
|
ensure();
|
|
3456
3819
|
const seconds = finite2(playback.options.fadeOutSec) ? playback.options.fadeOutSec : 0;
|
|
3457
|
-
if (seconds > 0
|
|
3820
|
+
if (seconds > 0) connector.fadeOut(id, seconds);
|
|
3458
3821
|
else control({ type: "stop" });
|
|
3459
3822
|
return result;
|
|
3460
3823
|
};
|
|
@@ -3488,11 +3851,7 @@ var Embody = class _Embody {
|
|
|
3488
3851
|
if (finite2(fade) && fade > 0) connector.fadeIn(id, fade);
|
|
3489
3852
|
} finally {
|
|
3490
3853
|
this.playDepth -= 1;
|
|
3491
|
-
|
|
3492
|
-
}
|
|
3493
|
-
if (hairMotion && connector.getAction(id)) {
|
|
3494
|
-
this.playHairImpulse(hairMotion.horizontal, hairMotion.vertical);
|
|
3495
|
-
this.setHairGravity(hairMotion.pitch, clip.duration);
|
|
3854
|
+
this.flushPendingPlaybackEvents();
|
|
3496
3855
|
}
|
|
3497
3856
|
return result;
|
|
3498
3857
|
};
|
|
@@ -3530,7 +3889,10 @@ var Embody = class _Embody {
|
|
|
3530
3889
|
}
|
|
3531
3890
|
forget(playback) {
|
|
3532
3891
|
this.playbacks.delete(playback.id);
|
|
3533
|
-
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
|
+
}
|
|
3534
3896
|
}
|
|
3535
3897
|
updatePlayback(playback, patch) {
|
|
3536
3898
|
this.validateEasing(patch);
|
|
@@ -3546,6 +3908,7 @@ var Embody = class _Embody {
|
|
|
3546
3908
|
if (["weight", "intensity", "intensityScale", "blendMode", "easing", "balance", "balanceMap", "mixWeights"].some((key) => key in patch)) {
|
|
3547
3909
|
const clip = this.materialize(playback.source, options, playback.inheritedStarts);
|
|
3548
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");
|
|
3549
3912
|
if ("blendMode" in patch) connector.control(playback.id, { type: "setBlendMode", blendMode: clip.blendMode === AdditiveAnimationBlendMode ? "additive" : "normal" });
|
|
3550
3913
|
}
|
|
3551
3914
|
if ("mixerWeight" in patch) connector.control(playback.id, { type: "setWeight", weight: patch.mixerWeight });
|
|
@@ -3571,6 +3934,38 @@ var Embody = class _Embody {
|
|
|
3571
3934
|
playback.options = options;
|
|
3572
3935
|
if (!connector.getAction(playback.id)) this.forget(playback);
|
|
3573
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
|
+
}
|
|
3574
3969
|
registerAnimationClips(clips) {
|
|
3575
3970
|
const model = this.requireModel();
|
|
3576
3971
|
for (const clip of clips) {
|
|
@@ -3582,12 +3977,13 @@ var Embody = class _Embody {
|
|
|
3582
3977
|
}
|
|
3583
3978
|
loadAnimationClips(clips) {
|
|
3584
3979
|
this.stopAllAnimations();
|
|
3980
|
+
this.vrmAnimations.clear();
|
|
3585
3981
|
for (const source of [...this.sources.values()]) if (source.kind === "native") {
|
|
3586
3982
|
this.sources.delete(source.name);
|
|
3587
3983
|
this.core.remove_animation_clip(source.name);
|
|
3588
3984
|
}
|
|
3589
3985
|
this.registerAnimationClips(clips ?? []);
|
|
3590
|
-
this.
|
|
3986
|
+
this.sampleHairResponse(0);
|
|
3591
3987
|
}
|
|
3592
3988
|
removeAnimationClip(name) {
|
|
3593
3989
|
this.stopAnimation(name);
|
|
@@ -3602,12 +3998,12 @@ var Embody = class _Embody {
|
|
|
3602
3998
|
if (!source) throw new Error(`Unknown animation clip: ${name}`);
|
|
3603
3999
|
return this.handle(source, options, true);
|
|
3604
4000
|
}
|
|
3605
|
-
build(source, options, play) {
|
|
4001
|
+
build(source, options, play, owner = "caller") {
|
|
3606
4002
|
this.requireModel();
|
|
3607
4003
|
this.validateEasing(options);
|
|
3608
4004
|
this.compile(source, options);
|
|
3609
4005
|
this.sources.set(source.name, source);
|
|
3610
|
-
return this.handle(source, options, play);
|
|
4006
|
+
return this.handle(source, options, play, owner);
|
|
3611
4007
|
}
|
|
3612
4008
|
buildClip(name, curves, options = {}) {
|
|
3613
4009
|
if (options.semanticAnimation) throw new Error("Decode legacy named animation tracks to numeric AU curves before building a clip.");
|
|
@@ -3640,7 +4036,7 @@ var Embody = class _Embody {
|
|
|
3640
4036
|
this.core.cleanup_snippet(name);
|
|
3641
4037
|
}
|
|
3642
4038
|
crossfadeTo(name, seconds, options = {}) {
|
|
3643
|
-
const outgoing = [...this.current.values()].filter((playback) =>
|
|
4039
|
+
const outgoing = [...this.current.values()].filter((playback) => playback.owner === "caller");
|
|
3644
4040
|
const handle = this.playAnimation(name, { ...options, fadeInSec: seconds });
|
|
3645
4041
|
for (const playback of outgoing) if (playback.connector.getAction(playback.id)) playback.connector.fadeOut(playback.id, seconds);
|
|
3646
4042
|
return handle;
|
|
@@ -3651,28 +4047,35 @@ var Embody = class _Embody {
|
|
|
3651
4047
|
if (playback.connector.getAction(playback.id)) playback.connector.control(playback.id, { type: "stop" });
|
|
3652
4048
|
this.forget(playback);
|
|
3653
4049
|
}
|
|
4050
|
+
/** Stop every caller playback. Live setter targets and hair actions keep running. */
|
|
3654
4051
|
stopAllAnimations() {
|
|
3655
4052
|
for (const playback of [...this.playbacks.values()]) {
|
|
3656
|
-
if (playback.
|
|
4053
|
+
if (playback.owner !== "caller") continue;
|
|
3657
4054
|
if (playback.connector.getAction(playback.id)) playback.connector.control(playback.id, { type: "stop" });
|
|
3658
4055
|
this.forget(playback);
|
|
3659
4056
|
}
|
|
3660
4057
|
}
|
|
3661
4058
|
pauseAnimation(name) {
|
|
4059
|
+
if (name === HAIR_RESPONSE) this.core.set_hair_response_paused(true);
|
|
3662
4060
|
const p = this.current.get(name);
|
|
3663
4061
|
if (p) p.connector.control(p.id, { type: "pause" });
|
|
3664
4062
|
}
|
|
3665
4063
|
resumeAnimation(name) {
|
|
4064
|
+
if (name === HAIR_RESPONSE) {
|
|
4065
|
+
this.core.set_hair_response_paused(false);
|
|
4066
|
+
this.sampleHairResponse(0);
|
|
4067
|
+
}
|
|
3666
4068
|
const p = this.current.get(name);
|
|
3667
4069
|
if (!p) return;
|
|
3668
4070
|
if (p.terminal) p.replay({ ...p.options, time: p.connector.getAction(p.id)?.time ?? 0 });
|
|
3669
4071
|
else p.connector.control(p.id, { type: "resume" });
|
|
3670
4072
|
}
|
|
4073
|
+
// Hair actions follow the hair configuration and Embody.stop()/start(), not caller pause commands.
|
|
3671
4074
|
pauseAllAnimations() {
|
|
3672
|
-
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" });
|
|
3673
4076
|
}
|
|
3674
4077
|
resumeAllAnimations() {
|
|
3675
|
-
for (const name of [...this.current
|
|
4078
|
+
for (const [name, p] of [...this.current]) if (p.owner !== "hair") this.resumeAnimation(name);
|
|
3676
4079
|
}
|
|
3677
4080
|
updateClipParams(name, options) {
|
|
3678
4081
|
const p = this.current.get(name);
|
|
@@ -3773,7 +4176,8 @@ var Embody = class _Embody {
|
|
|
3773
4176
|
const handle = this.handle(
|
|
3774
4177
|
{ name: releaseClip.name, kind: "compiled", clip: releaseClip },
|
|
3775
4178
|
{ loopMode: "once", finalPose: "release", mixerWeight: action.getEffectiveWeight() },
|
|
3776
|
-
true
|
|
4179
|
+
true,
|
|
4180
|
+
"live"
|
|
3777
4181
|
);
|
|
3778
4182
|
connector.fadeOut(handle.actionId, duration);
|
|
3779
4183
|
const record = { track: released, handle, releasing: true };
|
|
@@ -3791,11 +4195,17 @@ var Embody = class _Embody {
|
|
|
3791
4195
|
const outgoing = previous?.handle.actionId && connector.getAction(previous.handle.actionId);
|
|
3792
4196
|
let nativeTrack = track;
|
|
3793
4197
|
if (duration > 0 && outgoing) {
|
|
3794
|
-
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
|
+
);
|
|
3795
4205
|
nativeTrack = { ...track, values: [...start, ...endpoint(track)], inheritStart: false };
|
|
3796
4206
|
}
|
|
3797
4207
|
const targetClip = { ...clip, name, tracks: [nativeTrack] };
|
|
3798
|
-
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");
|
|
3799
4209
|
this.liveTargets.set(key, { track, handle, releasing: false });
|
|
3800
4210
|
handles.push(handle);
|
|
3801
4211
|
}
|
|
@@ -3840,6 +4250,49 @@ var Embody = class _Embody {
|
|
|
3840
4250
|
getContinuum(negative, positive) {
|
|
3841
4251
|
return this.core.get_continuum(negative, positive);
|
|
3842
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
|
+
}
|
|
3843
4296
|
transitionAU(id, value, durationMs = 200, balance = this.core.get_au_balance(id)) {
|
|
3844
4297
|
return this.transition(this.mutateLive(() => this.core.set_au_signed(id, value, balance), durationMs));
|
|
3845
4298
|
}
|
|
@@ -3939,7 +4392,9 @@ var Embody = class _Embody {
|
|
|
3939
4392
|
this.meshProfile(name).visible = visible;
|
|
3940
4393
|
}
|
|
3941
4394
|
highlightMesh(name, color, value) {
|
|
3942
|
-
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()));
|
|
3943
4398
|
}
|
|
3944
4399
|
getMeshMaterialConfig(name) {
|
|
3945
4400
|
return this.applier.getMeshMaterialConfig(this.requireModel(), name);
|
|
@@ -3957,7 +4412,9 @@ var Embody = class _Embody {
|
|
|
3957
4412
|
const material = mesh && (Array.isArray(mesh.material) ? mesh.material[0] : mesh.material);
|
|
3958
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 };
|
|
3959
4414
|
}
|
|
3960
|
-
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);
|
|
3961
4418
|
return [...this.hairObjects.values()].map((mesh) => ({ name: mesh.name, isMesh: true, isEyebrow: this.meshCategory(mesh) === "eyebrow" }));
|
|
3962
4419
|
}
|
|
3963
4420
|
getRegisteredHairObjects() {
|
|
@@ -3997,12 +4454,69 @@ var Embody = class _Embody {
|
|
|
3997
4454
|
setHairOutline(value) {
|
|
3998
4455
|
Object.assign(this.hairAppearance, { showOutline: value.show }, value.color ? { outlineColor: value.color } : {}, value.opacity !== void 0 ? { outlineOpacity: value.opacity } : {});
|
|
3999
4456
|
}
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
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();
|
|
4006
4520
|
}
|
|
4007
4521
|
}
|
|
4008
4522
|
setHairPhysicsEnabled(value) {
|
|
@@ -4012,8 +4526,12 @@ var Embody = class _Embody {
|
|
|
4012
4526
|
return !!this.hairConfig.enabled;
|
|
4013
4527
|
}
|
|
4014
4528
|
setHairPhysicsConfig(value) {
|
|
4529
|
+
this.assertAlive();
|
|
4015
4530
|
this.hairConfig = parse(this.core.set_hair_config_json(json2(value)));
|
|
4016
|
-
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);
|
|
4017
4535
|
}
|
|
4018
4536
|
getHairPhysicsConfig() {
|
|
4019
4537
|
return copy(this.hairConfig);
|
|
@@ -4029,41 +4547,12 @@ var Embody = class _Embody {
|
|
|
4029
4547
|
if (state.position) mesh.position.set(state.position.x, state.position.y, state.position.z);
|
|
4030
4548
|
if (state.color) this.applier.applyHairAppearance([mesh], state.color);
|
|
4031
4549
|
}
|
|
4032
|
-
authoredHeadMotion(clip) {
|
|
4033
|
-
if (!this.hairConfig.enabled || clip.name.startsWith("__embody_hair") || !this.hairObjects.size) return void 0;
|
|
4034
|
-
const tracks = [];
|
|
4035
|
-
for (const track of clip.tracks) {
|
|
4036
|
-
if (track.ValueTypeName !== "quaternion") continue;
|
|
4037
|
-
const parsed = PropertyBinding.parseTrackName(track.name);
|
|
4038
|
-
let object = PropertyBinding.findNode(this.requireModel(), parsed.nodeName);
|
|
4039
|
-
if (parsed.objectName === "bones" && object) {
|
|
4040
|
-
const skeleton = object.skeleton;
|
|
4041
|
-
object = skeleton?.bones.find((bone) => bone.name === String(parsed.objectIndex)) ?? skeleton?.bones[Number(parsed.objectIndex)];
|
|
4042
|
-
}
|
|
4043
|
-
if (!object) continue;
|
|
4044
|
-
const cubic = track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true;
|
|
4045
|
-
const stride = track.getValueSize();
|
|
4046
|
-
const reference = this.restValues(track);
|
|
4047
|
-
if (!reference) continue;
|
|
4048
|
-
const samples = [];
|
|
4049
|
-
for (let index = 0; index < track.times.length; index += 1) {
|
|
4050
|
-
const offset = index * stride + (cubic ? 4 : 0);
|
|
4051
|
-
samples.push(Array.from(track.values.subarray(offset, offset + 4)));
|
|
4052
|
-
}
|
|
4053
|
-
tracks.push({ boneName: object.name, current: object.quaternion.toArray(), reference, samples });
|
|
4054
|
-
}
|
|
4055
|
-
return parse(this.core.resolve_hair_motion_json(json2(tracks), clip.blendMode === AdditiveAnimationBlendMode)) ?? void 0;
|
|
4056
|
-
}
|
|
4057
|
-
setHairGravity(pitch, durationSeconds) {
|
|
4058
|
-
const clip = parse(this.core.build_hair_gravity_clip("__embody_hair_gravity", pitch, durationSeconds, json2({ meshNames: [...this.hairObjects.keys()], source: "hair" })));
|
|
4059
|
-
if (!clip.tracks.length) return;
|
|
4060
|
-
this.handle({ name: clip.name, kind: "compiled", clip }, { loopMode: "once", finalPose: "hold", blendMode: "additive" }, true);
|
|
4061
|
-
}
|
|
4062
4550
|
playHairImpulse(horizontal, vertical) {
|
|
4063
4551
|
if (!this.hairConfig.enabled || !this.hairObjects.size) return void 0;
|
|
4064
4552
|
const curves = parse(this.wasm.build_hair_impulse_curves(json2(this.hairConfig), this.hairConfig.impulseClipDuration, horizontal, vertical));
|
|
4065
4553
|
if (!Object.keys(curves).length) return void 0;
|
|
4066
|
-
|
|
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");
|
|
4067
4556
|
}
|
|
4068
4557
|
start() {
|
|
4069
4558
|
this.assertAlive();
|
|
@@ -4084,11 +4573,14 @@ var Embody = class _Embody {
|
|
|
4084
4573
|
dispose() {
|
|
4085
4574
|
if (this.disposed) return;
|
|
4086
4575
|
this.connector?.dispose();
|
|
4576
|
+
this.applier.clearMeshHighlights();
|
|
4087
4577
|
this.core.free();
|
|
4088
4578
|
this.playbacks.clear();
|
|
4089
4579
|
this.current.clear();
|
|
4090
4580
|
this.sources.clear();
|
|
4581
|
+
this.vrmAnimations.clear();
|
|
4091
4582
|
this.hairObjects.clear();
|
|
4583
|
+
this.hairHeadObservation = void 0;
|
|
4092
4584
|
this.meshes.length = 0;
|
|
4093
4585
|
this.meshByName.clear();
|
|
4094
4586
|
this.bones = {};
|
|
@@ -4130,7 +4622,7 @@ function createSceneInkBackgroundCss(palette = DEFAULT_PALETTE) {
|
|
|
4130
4622
|
createSceneInkBackgroundCss();
|
|
4131
4623
|
|
|
4132
4624
|
// three/sceneInkBackground.ts
|
|
4133
|
-
var toCssColor = (tone) => `#${new
|
|
4625
|
+
var toCssColor = (tone) => `#${new THREE11.Color(tone).getHexString()}`;
|
|
4134
4626
|
function createSceneInkBackground(onBackgroundChange) {
|
|
4135
4627
|
let currentBackground = "";
|
|
4136
4628
|
let disposed = false;
|
|
@@ -4270,7 +4762,7 @@ function sampleTexture(texture) {
|
|
|
4270
4762
|
if (alpha < 0.18) {
|
|
4271
4763
|
continue;
|
|
4272
4764
|
}
|
|
4273
|
-
const color = new
|
|
4765
|
+
const color = new THREE11.Color(
|
|
4274
4766
|
data[index] / 255,
|
|
4275
4767
|
data[index + 1] / 255,
|
|
4276
4768
|
data[index + 2] / 255
|
|
@@ -4315,7 +4807,7 @@ function buildPalette(samples) {
|
|
|
4315
4807
|
const hsl = { h: 0, s: 0, l: 0 };
|
|
4316
4808
|
sample.color.getHSL(hsl);
|
|
4317
4809
|
const bucketId = Math.floor(hsl.h * 18);
|
|
4318
|
-
const bucket = buckets.get(bucketId) ?? { color: new
|
|
4810
|
+
const bucket = buckets.get(bucketId) ?? { color: new THREE11.Color(0, 0, 0), score: 0 };
|
|
4319
4811
|
bucket.color.r += sample.color.r * sample.score;
|
|
4320
4812
|
bucket.color.g += sample.color.g * sample.score;
|
|
4321
4813
|
bucket.color.b += sample.color.b * sample.score;
|
|
@@ -4348,18 +4840,18 @@ function buildPalette(samples) {
|
|
|
4348
4840
|
function massageBackgroundTone(color) {
|
|
4349
4841
|
const hsl = { h: 0, s: 0, l: 0 };
|
|
4350
4842
|
color.getHSL(hsl);
|
|
4351
|
-
const saturation =
|
|
4352
|
-
const lightness =
|
|
4353
|
-
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);
|
|
4354
4846
|
}
|
|
4355
4847
|
function createCompanionTone(source, index) {
|
|
4356
4848
|
const hsl = { h: 0, s: 0, l: 0 };
|
|
4357
4849
|
source.getHSL(hsl);
|
|
4358
4850
|
const hueOffset = index === 1 ? 0.075 : -0.065;
|
|
4359
|
-
return new
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
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)
|
|
4363
4855
|
);
|
|
4364
4856
|
}
|
|
4365
4857
|
function colorDistance(a, b) {
|
|
@@ -4398,6 +4890,11 @@ var prepare = (model, castShadows = true) => {
|
|
|
4398
4890
|
});
|
|
4399
4891
|
return meshes;
|
|
4400
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
|
+
};
|
|
4401
4898
|
var progress = (listener) => {
|
|
4402
4899
|
let last = -1;
|
|
4403
4900
|
return (event) => {
|
|
@@ -4430,8 +4927,8 @@ function disposeCharacterModel(scene, model) {
|
|
|
4430
4927
|
});
|
|
4431
4928
|
}
|
|
4432
4929
|
function loadCharacterModel(url, options = {}) {
|
|
4433
|
-
const done = (model, animations, gltf) => (
|
|
4434
|
-
if (/\.fbx(
|
|
4930
|
+
const done = (model, animations, gltf) => prepareCharacterModelResult(model, animations, gltf, options);
|
|
4931
|
+
if ((options.format ?? (/\.fbx(?:[?#]|$)/i.test(url) ? "fbx" : "gltf")) === "fbx") {
|
|
4435
4932
|
fbxLoader ?? (fbxLoader = new FBXLoader());
|
|
4436
4933
|
return new Promise((resolve, reject) => fbxLoader.load(url, (model) => resolve(done(model, model.animations ?? [], null)), progress(options.onProgress), reject));
|
|
4437
4934
|
}
|
|
@@ -4439,8 +4936,15 @@ function loadCharacterModel(url, options = {}) {
|
|
|
4439
4936
|
return new Promise((resolve, reject) => loader.load(url, (gltf) => resolve(done(gltf.scene, gltf.animations ?? [], gltf)), progress(options.onProgress), reject));
|
|
4440
4937
|
}
|
|
4441
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
|
+
}
|
|
4442
4946
|
const loader = getGltfLoader(options.dracoDecoderPath);
|
|
4443
|
-
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));
|
|
4444
4948
|
}
|
|
4445
4949
|
|
|
4446
4950
|
// three/modelMorphLayout.ts
|
|
@@ -4590,6 +5094,168 @@ function assertRenderableCharacterMorphAttributes(model) {
|
|
|
4590
5094
|
}
|
|
4591
5095
|
});
|
|
4592
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/sceneVideoCapture.ts
|
|
5154
|
+
var activeCaptures = /* @__PURE__ */ new WeakMap();
|
|
5155
|
+
function endCharacterSceneVideoCaptures(rendering) {
|
|
5156
|
+
for (const end of [...activeCaptures.get(rendering) ?? []]) end();
|
|
5157
|
+
}
|
|
5158
|
+
function acquireCharacterSceneVideoCapture(rendering, options = {}) {
|
|
5159
|
+
const fps = options.fps ?? 30;
|
|
5160
|
+
if (!Number.isFinite(fps) || fps < 1 || fps > 60) throw new Error("Video capture frame rate must be between 1 and 60 fps.");
|
|
5161
|
+
if (typeof MutationObserver === "undefined") throw new Error("Canvas size observation is unavailable for video capture.");
|
|
5162
|
+
const lease = rendering.acquireRenderer();
|
|
5163
|
+
const canvas = lease.renderer.domElement;
|
|
5164
|
+
let tracks = [];
|
|
5165
|
+
let unsubscribe = null;
|
|
5166
|
+
let observer = null;
|
|
5167
|
+
let released = false;
|
|
5168
|
+
let leaseReleased = false;
|
|
5169
|
+
const trackListeners = [];
|
|
5170
|
+
const stoppedTracks = /* @__PURE__ */ new Set();
|
|
5171
|
+
const captures = activeCaptures.get(rendering) ?? /* @__PURE__ */ new Set();
|
|
5172
|
+
activeCaptures.set(rendering, captures);
|
|
5173
|
+
const terminate = () => finish(true);
|
|
5174
|
+
captures.add(terminate);
|
|
5175
|
+
function cleanup() {
|
|
5176
|
+
captures.delete(terminate);
|
|
5177
|
+
if (!captures.size && activeCaptures.get(rendering) === captures) activeCaptures.delete(rendering);
|
|
5178
|
+
const observing = observer;
|
|
5179
|
+
observer = null;
|
|
5180
|
+
try {
|
|
5181
|
+
observing?.disconnect();
|
|
5182
|
+
} catch {
|
|
5183
|
+
}
|
|
5184
|
+
const subscribed = unsubscribe;
|
|
5185
|
+
unsubscribe = null;
|
|
5186
|
+
try {
|
|
5187
|
+
subscribed?.();
|
|
5188
|
+
} catch {
|
|
5189
|
+
}
|
|
5190
|
+
for (const remove of trackListeners.splice(0)) {
|
|
5191
|
+
try {
|
|
5192
|
+
remove();
|
|
5193
|
+
} catch {
|
|
5194
|
+
}
|
|
5195
|
+
}
|
|
5196
|
+
for (const track of tracks) {
|
|
5197
|
+
if (stoppedTracks.has(track)) continue;
|
|
5198
|
+
stoppedTracks.add(track);
|
|
5199
|
+
try {
|
|
5200
|
+
track.stop();
|
|
5201
|
+
} catch {
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
if (!leaseReleased) {
|
|
5205
|
+
leaseReleased = true;
|
|
5206
|
+
lease.release();
|
|
5207
|
+
}
|
|
5208
|
+
}
|
|
5209
|
+
function finish(notify) {
|
|
5210
|
+
if (released) {
|
|
5211
|
+
cleanup();
|
|
5212
|
+
return;
|
|
5213
|
+
}
|
|
5214
|
+
released = true;
|
|
5215
|
+
const ended = notify ? tracks.filter((track) => track.readyState !== "ended") : [];
|
|
5216
|
+
cleanup();
|
|
5217
|
+
for (const track of ended) {
|
|
5218
|
+
try {
|
|
5219
|
+
track.dispatchEvent(new Event("ended"));
|
|
5220
|
+
} catch {
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
5223
|
+
}
|
|
5224
|
+
function check(snapshot3) {
|
|
5225
|
+
if (snapshot3.renderer !== lease.renderer || !["ready", "error"].includes(snapshot3.status)) finish(true);
|
|
5226
|
+
}
|
|
5227
|
+
try {
|
|
5228
|
+
if (typeof canvas.captureStream !== "function") throw new Error("This renderer canvas cannot provide a video stream.");
|
|
5229
|
+
const stream = canvas.captureStream(fps);
|
|
5230
|
+
tracks = stream.getTracks();
|
|
5231
|
+
if (released) throw new Error("Video capture ended during canvas acquisition.");
|
|
5232
|
+
if (!stream.getVideoTracks().some((track) => track.readyState === "live")) throw new Error("The renderer canvas did not provide a live video track.");
|
|
5233
|
+
for (const track of tracks) {
|
|
5234
|
+
const ended = () => finish(true);
|
|
5235
|
+
trackListeners.push(() => track.removeEventListener("ended", ended));
|
|
5236
|
+
track.addEventListener("ended", ended);
|
|
5237
|
+
if (released) throw new Error("Video capture ended during acquisition.");
|
|
5238
|
+
}
|
|
5239
|
+
const width = canvas.width, height = canvas.height;
|
|
5240
|
+
unsubscribe = rendering.subscribe(check);
|
|
5241
|
+
if (released) throw new Error("The renderer changed during video capture acquisition.");
|
|
5242
|
+
check(rendering.getSnapshot());
|
|
5243
|
+
if (released) throw new Error("The renderer is unavailable for video capture.");
|
|
5244
|
+
observer = new MutationObserver(() => {
|
|
5245
|
+
if (canvas.width !== width || canvas.height !== height) finish(true);
|
|
5246
|
+
});
|
|
5247
|
+
if (released) throw new Error("Video capture ended during size observer creation.");
|
|
5248
|
+
observer.observe(canvas, { attributes: true, attributeFilter: ["width", "height"] });
|
|
5249
|
+
if (canvas.width !== width || canvas.height !== height) finish(true);
|
|
5250
|
+
if (released) throw new Error("The renderer canvas changed size during video capture acquisition.");
|
|
5251
|
+
return { stream, release: () => finish(false) };
|
|
5252
|
+
} catch (error) {
|
|
5253
|
+
finish(false);
|
|
5254
|
+
throw error;
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
|
|
5258
|
+
// three/scene.ts
|
|
4593
5259
|
var DEFAULT_CHARACTER_LIGHTING_PRESETS = {
|
|
4594
5260
|
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 } },
|
|
4595
5261
|
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 } },
|
|
@@ -4834,7 +5500,8 @@ function constructDefaultCharacterScene(container, options, renderer, disposeRen
|
|
|
4834
5500
|
Object.assign(renderer.domElement.style, { display: "block", width: "100%", height: "100%" });
|
|
4835
5501
|
const scene = new Scene();
|
|
4836
5502
|
const background = options.background === void 0 ? sceneType.background : options.background;
|
|
4837
|
-
scene
|
|
5503
|
+
setSceneBaseBackground(scene, background == null ? null : new Color(background));
|
|
5504
|
+
lifetime.add(() => disposeSceneBackground(scene));
|
|
4838
5505
|
const camera = new PerspectiveCamera(options.cameraFov ?? 45, width / height, 0.1, 1e3);
|
|
4839
5506
|
const preset = DEFAULT_CHARACTER_LIGHTING_PRESETS[options.lightingPreset ?? sceneType.lightingPreset]?.settings ?? DEFAULT_CHARACTER_LIGHTING_SETTINGS;
|
|
4840
5507
|
const lighting = createLighting(scene, { ...preset, ...options.lighting });
|
|
@@ -4855,7 +5522,7 @@ function constructDefaultCharacterScene(container, options, renderer, disposeRen
|
|
|
4855
5522
|
const type = CHARACTER_SCENE_TYPES[value.type ?? DEFAULT_CHARACTER_SCENE_TYPE_ID] ?? CHARACTER_SCENE_TYPES[DEFAULT_CHARACTER_SCENE_TYPE_ID];
|
|
4856
5523
|
currentSceneType = type.id;
|
|
4857
5524
|
const background2 = value.background === void 0 ? type.background : value.background;
|
|
4858
|
-
scene
|
|
5525
|
+
setSceneBaseBackground(scene, background2 == null ? null : new Color(background2));
|
|
4859
5526
|
camera.fov = value.cameraFov ?? 45;
|
|
4860
5527
|
camera.updateProjectionMatrix();
|
|
4861
5528
|
const preset2 = DEFAULT_CHARACTER_LIGHTING_PRESETS[value.lightingPreset ?? type.lightingPreset] ?? DEFAULT_CHARACTER_LIGHTING_PRESETS[DEFAULT_CHARACTER_LIGHTING_PRESET_ID];
|
|
@@ -4990,17 +5657,17 @@ function prepareCameraCoordinates(scene, camera, backend) {
|
|
|
4990
5657
|
}
|
|
4991
5658
|
});
|
|
4992
5659
|
const previous = Array.from(cameras, (current) => [current, current.coordinateSystem]);
|
|
4993
|
-
const
|
|
5660
|
+
const apply2 = (current, coordinates2) => {
|
|
4994
5661
|
current.coordinateSystem = coordinates2;
|
|
4995
5662
|
current.updateProjectionMatrix();
|
|
4996
5663
|
};
|
|
4997
5664
|
const coordinates = backend === "webgpu" ? WebGPUCoordinateSystem : WebGLCoordinateSystem;
|
|
4998
|
-
cameras.forEach((current) =>
|
|
5665
|
+
cameras.forEach((current) => apply2(current, coordinates));
|
|
4999
5666
|
shadows.forEach((shadow) => {
|
|
5000
5667
|
shadow.needsUpdate = true;
|
|
5001
5668
|
});
|
|
5002
5669
|
return () => {
|
|
5003
|
-
previous.forEach(([current, original]) =>
|
|
5670
|
+
previous.forEach(([current, original]) => apply2(current, original));
|
|
5004
5671
|
shadows.forEach((shadow) => {
|
|
5005
5672
|
shadow.needsUpdate = true;
|
|
5006
5673
|
});
|
|
@@ -5014,7 +5681,8 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5014
5681
|
let currentSceneType = sceneType.id;
|
|
5015
5682
|
const scene = new Scene();
|
|
5016
5683
|
const background = options.background === void 0 ? sceneType.background : options.background;
|
|
5017
|
-
scene
|
|
5684
|
+
setSceneBaseBackground(scene, background == null ? null : new Color(background));
|
|
5685
|
+
lifetime.add(() => disposeSceneBackground(scene));
|
|
5018
5686
|
const camera = new PerspectiveCamera(options.cameraFov ?? 45, 1, 0.1, 1e3);
|
|
5019
5687
|
const preset = DEFAULT_CHARACTER_LIGHTING_PRESETS[options.lightingPreset ?? sceneType.lightingPreset]?.settings ?? DEFAULT_CHARACTER_LIGHTING_SETTINGS;
|
|
5020
5688
|
const lighting = createMutableCharacterLighting(scene, { ...preset, ...options.lighting });
|
|
@@ -5054,10 +5722,10 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5054
5722
|
return { ...common, backend: null, renderer: null };
|
|
5055
5723
|
};
|
|
5056
5724
|
const notify = () => {
|
|
5057
|
-
const
|
|
5725
|
+
const snapshot3 = getSnapshot();
|
|
5058
5726
|
for (const listener of listeners) {
|
|
5059
5727
|
try {
|
|
5060
|
-
listener(
|
|
5728
|
+
listener(snapshot3);
|
|
5061
5729
|
} catch (failure) {
|
|
5062
5730
|
console.error("Character rendering subscriber failed.", failure);
|
|
5063
5731
|
}
|
|
@@ -5184,9 +5852,9 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5184
5852
|
rollback(() => oldRenderer?.dispose());
|
|
5185
5853
|
status = "ready";
|
|
5186
5854
|
error = null;
|
|
5187
|
-
const
|
|
5855
|
+
const snapshot3 = getSnapshot();
|
|
5188
5856
|
notify();
|
|
5189
|
-
return
|
|
5857
|
+
return snapshot3;
|
|
5190
5858
|
} catch (failure) {
|
|
5191
5859
|
if (suspended) {
|
|
5192
5860
|
try {
|
|
@@ -5265,6 +5933,17 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5265
5933
|
}
|
|
5266
5934
|
} };
|
|
5267
5935
|
},
|
|
5936
|
+
acquireRendererAsync: async () => {
|
|
5937
|
+
for (; ; ) {
|
|
5938
|
+
if (disposed) throw renderingAborted();
|
|
5939
|
+
const settling = pending;
|
|
5940
|
+
if (settling) {
|
|
5941
|
+
await settling.catch(() => void 0);
|
|
5942
|
+
continue;
|
|
5943
|
+
}
|
|
5944
|
+
return rendering.acquireRenderer();
|
|
5945
|
+
}
|
|
5946
|
+
},
|
|
5268
5947
|
releaseSceneResources: (release) => {
|
|
5269
5948
|
if (disposal) return disposal.then(release, release);
|
|
5270
5949
|
const previous = pending;
|
|
@@ -5287,6 +5966,7 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5287
5966
|
pendingResourceReleases--;
|
|
5288
5967
|
});
|
|
5289
5968
|
pending = operation;
|
|
5969
|
+
endCharacterSceneVideoCaptures(rendering);
|
|
5290
5970
|
void operation.then(() => {
|
|
5291
5971
|
if (pending === operation) pending = null;
|
|
5292
5972
|
}, () => {
|
|
@@ -5348,7 +6028,7 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5348
6028
|
const type = CHARACTER_SCENE_TYPES[value.type ?? DEFAULT_CHARACTER_SCENE_TYPE_ID] ?? CHARACTER_SCENE_TYPES[DEFAULT_CHARACTER_SCENE_TYPE_ID];
|
|
5349
6029
|
currentSceneType = type.id;
|
|
5350
6030
|
const background2 = value.background === void 0 ? type.background : value.background;
|
|
5351
|
-
scene
|
|
6031
|
+
setSceneBaseBackground(scene, background2 == null ? null : new Color(background2));
|
|
5352
6032
|
camera.fov = value.cameraFov ?? 45;
|
|
5353
6033
|
camera.updateProjectionMatrix();
|
|
5354
6034
|
const preset2 = DEFAULT_CHARACTER_LIGHTING_PRESETS[value.lightingPreset ?? type.lightingPreset] ?? DEFAULT_CHARACTER_LIGHTING_PRESETS[DEFAULT_CHARACTER_LIGHTING_PRESET_ID];
|
|
@@ -5391,9 +6071,9 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5391
6071
|
async function createDefaultCharacterSceneAsync(container, options = {}) {
|
|
5392
6072
|
const runtime = createDefaultCharacterSceneRuntime(container, options);
|
|
5393
6073
|
try {
|
|
5394
|
-
const
|
|
5395
|
-
if (!
|
|
5396
|
-
const handle =
|
|
6074
|
+
const snapshot3 = await runtime.ready;
|
|
6075
|
+
if (!snapshot3.renderer) throw new Error("Character scene renderer was not initialized.");
|
|
6076
|
+
const handle = snapshot3.backend === "webgl" ? { ...runtime, renderer: snapshot3.renderer, backend: "webgl" } : { ...runtime, renderer: snapshot3.renderer, backend: "webgpu" };
|
|
5397
6077
|
Object.defineProperties(handle, {
|
|
5398
6078
|
renderer: { get: () => runtime.renderer },
|
|
5399
6079
|
backend: { get: () => runtime.backend },
|
|
@@ -5480,7 +6160,14 @@ async function createCharacterHost(options) {
|
|
|
5480
6160
|
if (!rendering.getSnapshot().renderer) throw error;
|
|
5481
6161
|
}
|
|
5482
6162
|
check(signal);
|
|
5483
|
-
|
|
6163
|
+
const lease = await rendering.acquireRendererAsync();
|
|
6164
|
+
try {
|
|
6165
|
+
check(signal);
|
|
6166
|
+
return lease;
|
|
6167
|
+
} catch (error) {
|
|
6168
|
+
lease.release();
|
|
6169
|
+
throw error;
|
|
6170
|
+
}
|
|
5484
6171
|
};
|
|
5485
6172
|
const makeEngine = (value, character) => {
|
|
5486
6173
|
let profile = character.profile ?? options.embody?.profile;
|
|
@@ -5729,6 +6416,302 @@ async function createCharacterHost(options) {
|
|
|
5729
6416
|
throw error;
|
|
5730
6417
|
}
|
|
5731
6418
|
}
|
|
6419
|
+
var finite4 = (value) => typeof value === "number" && Number.isFinite(value);
|
|
6420
|
+
var identity2 = (value) => typeof value === "string" && value.trim().length > 0;
|
|
6421
|
+
var messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
6422
|
+
function copyLayout(layout) {
|
|
6423
|
+
if (layout === null) return { skybox: null, instances: [] };
|
|
6424
|
+
if (!layout || !Array.isArray(layout.instances)) throw new Error("Scenery requires an instances array.");
|
|
6425
|
+
const ids = /* @__PURE__ */ new Set();
|
|
6426
|
+
const instances = layout.instances.map((instance) => {
|
|
6427
|
+
if (!instance || !identity2(instance.instanceId) || !identity2(instance.assetId)) throw new Error("Each scenery placement requires an instanceId and assetId.");
|
|
6428
|
+
if (ids.has(instance.instanceId)) throw new Error(`Duplicate scenery instanceId: ${instance.instanceId}`);
|
|
6429
|
+
ids.add(instance.instanceId);
|
|
6430
|
+
const position = instance.position;
|
|
6431
|
+
if (!position || !finite4(position.x) || !finite4(position.y) || !finite4(position.z)) throw new Error(`Scenery placement ${instance.instanceId} requires finite X/Y/Z coordinates.`);
|
|
6432
|
+
return { instanceId: instance.instanceId, assetId: instance.assetId, position: { x: position.x, y: position.y, z: position.z } };
|
|
6433
|
+
});
|
|
6434
|
+
const skybox = layout.skybox;
|
|
6435
|
+
if (skybox !== null) {
|
|
6436
|
+
if (!skybox || !["skybox", "backdrop", "color"].includes(skybox.kind)) throw new Error("Scenery background kind must be skybox, backdrop or color.");
|
|
6437
|
+
if (skybox.assetId !== null && !identity2(skybox.assetId)) throw new Error("The scenery background assetId must be a nonempty string or null.");
|
|
6438
|
+
if (skybox.kind === "color" && skybox.assetId !== null) throw new Error("A solid scenery color cannot use an image asset.");
|
|
6439
|
+
if (typeof skybox.color !== "string" || !/^#[\da-f]{6}$/i.test(skybox.color)) throw new Error("Scenery background color must use six-digit #rrggbb.");
|
|
6440
|
+
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.");
|
|
6441
|
+
}
|
|
6442
|
+
return { instances, skybox: skybox === null ? null : { kind: skybox.kind, assetId: skybox.assetId, color: skybox.color, intensity: skybox.intensity, rotation: skybox.rotation } };
|
|
6443
|
+
}
|
|
6444
|
+
function disposeRoots(roots) {
|
|
6445
|
+
const geometries = /* @__PURE__ */ new Set(), materials2 = /* @__PURE__ */ new Set(), textures = /* @__PURE__ */ new Set();
|
|
6446
|
+
const skeletons = /* @__PURE__ */ new Set();
|
|
6447
|
+
for (const root of roots) root.traverse((object) => {
|
|
6448
|
+
if (object.geometry) geometries.add(object.geometry);
|
|
6449
|
+
if (object.skeleton) skeletons.add(object.skeleton);
|
|
6450
|
+
for (const material of object.material ? Array.isArray(object.material) ? object.material : [object.material] : []) {
|
|
6451
|
+
materials2.add(material);
|
|
6452
|
+
for (const value of Object.values(material)) if (value?.isTexture) textures.add(value);
|
|
6453
|
+
}
|
|
6454
|
+
});
|
|
6455
|
+
const images = /* @__PURE__ */ new Set();
|
|
6456
|
+
for (const texture of textures) {
|
|
6457
|
+
for (const image of Array.isArray(texture.image) ? texture.image : [texture.image]) if (image) images.add(image);
|
|
6458
|
+
texture.dispose();
|
|
6459
|
+
}
|
|
6460
|
+
for (const geometry of geometries) geometry.dispose();
|
|
6461
|
+
for (const material of materials2) material.dispose();
|
|
6462
|
+
for (const skeleton of skeletons) skeleton.dispose();
|
|
6463
|
+
for (const image of images) image.close?.();
|
|
6464
|
+
}
|
|
6465
|
+
function display(value, skybox) {
|
|
6466
|
+
const texture = "isTexture" in value ? value : null;
|
|
6467
|
+
if (texture) {
|
|
6468
|
+
texture.colorSpace = SRGBColorSpace;
|
|
6469
|
+
if (skybox.kind === "skybox") texture.mapping = EquirectangularReflectionMapping;
|
|
6470
|
+
}
|
|
6471
|
+
let disposed = false;
|
|
6472
|
+
return {
|
|
6473
|
+
value,
|
|
6474
|
+
texture,
|
|
6475
|
+
intensity: skybox.intensity,
|
|
6476
|
+
rotation: skybox.kind === "skybox" ? skybox.rotation : 0,
|
|
6477
|
+
dispose() {
|
|
6478
|
+
if (disposed) return;
|
|
6479
|
+
disposed = true;
|
|
6480
|
+
texture?.dispose();
|
|
6481
|
+
}
|
|
6482
|
+
};
|
|
6483
|
+
}
|
|
6484
|
+
function generatedSky(skybox) {
|
|
6485
|
+
const canvas = document.createElement("canvas");
|
|
6486
|
+
canvas.width = 1024;
|
|
6487
|
+
canvas.height = 512;
|
|
6488
|
+
const context = canvas.getContext("2d");
|
|
6489
|
+
if (!context) throw new Error("The browser could not create the scenery skybox.");
|
|
6490
|
+
const gradient = context.createLinearGradient(0, 0, 0, 512);
|
|
6491
|
+
gradient.addColorStop(0, "#10172c");
|
|
6492
|
+
gradient.addColorStop(0.48, skybox.color);
|
|
6493
|
+
gradient.addColorStop(0.52, "#b6aaa0");
|
|
6494
|
+
gradient.addColorStop(1, "#151c25");
|
|
6495
|
+
context.fillStyle = gradient;
|
|
6496
|
+
context.fillRect(0, 0, 1024, 512);
|
|
6497
|
+
return display(new CanvasTexture(canvas), skybox);
|
|
6498
|
+
}
|
|
6499
|
+
async function imageDisplay(asset, skybox) {
|
|
6500
|
+
if (!["image/png", "image/jpeg", "image/webp"].includes(asset.contentType)) throw new Error("Scenery backgrounds require a PNG, JPEG or WebP image.");
|
|
6501
|
+
const url = URL.createObjectURL(new Blob([asset.bytes], { type: asset.contentType }));
|
|
6502
|
+
let pending;
|
|
6503
|
+
try {
|
|
6504
|
+
const texture = await new Promise((resolve, reject) => {
|
|
6505
|
+
pending = new TextureLoader().load(url, resolve, void 0, reject);
|
|
6506
|
+
});
|
|
6507
|
+
return display(texture, skybox);
|
|
6508
|
+
} catch (error) {
|
|
6509
|
+
pending?.dispose();
|
|
6510
|
+
throw new Error(`Scenery background image could not decode: ${messageOf(error)}`);
|
|
6511
|
+
} finally {
|
|
6512
|
+
URL.revokeObjectURL(url);
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6515
|
+
async function parseModel(asset) {
|
|
6516
|
+
if (asset.contentType !== "model/gltf-binary" || asset.bytes.byteLength < 20) throw new Error("Scenery models require a self-contained GLB 2.0 file.");
|
|
6517
|
+
const header = new DataView(asset.bytes);
|
|
6518
|
+
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.");
|
|
6519
|
+
let offset = 12, document2;
|
|
6520
|
+
while (offset < asset.bytes.byteLength) {
|
|
6521
|
+
if (offset + 8 > asset.bytes.byteLength) throw new Error("The scenery GLB has a truncated chunk header.");
|
|
6522
|
+
const size = header.getUint32(offset, true), kind = header.getUint32(offset + 4, true);
|
|
6523
|
+
if (size % 4 || size > asset.bytes.byteLength - offset - 8) throw new Error("The scenery GLB has an invalid chunk length.");
|
|
6524
|
+
if (offset === 12 && kind !== 1313821514) throw new Error("The scenery GLB must begin with its JSON chunk.");
|
|
6525
|
+
if (kind === 1313821514) {
|
|
6526
|
+
if (document2) throw new Error("The scenery GLB contains duplicate JSON chunks.");
|
|
6527
|
+
try {
|
|
6528
|
+
document2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(asset.bytes, offset + 8, size)));
|
|
6529
|
+
} catch (error) {
|
|
6530
|
+
throw new Error(`The scenery GLB JSON is invalid: ${messageOf(error)}`);
|
|
6531
|
+
}
|
|
6532
|
+
if (!document2 || typeof document2 !== "object" || Array.isArray(document2)) throw new Error("The scenery GLB JSON must be an object.");
|
|
6533
|
+
}
|
|
6534
|
+
offset += 8 + size;
|
|
6535
|
+
}
|
|
6536
|
+
for (const resources of [document2?.buffers, document2?.images]) {
|
|
6537
|
+
if (resources !== void 0 && !Array.isArray(resources)) throw new Error("The scenery GLB buffers and images must be arrays.");
|
|
6538
|
+
for (const resource of resources ?? []) {
|
|
6539
|
+
if (resource?.uri !== void 0 && (typeof resource.uri !== "string" || !resource.uri.startsWith("data:"))) {
|
|
6540
|
+
throw new Error("Scenery GLB files must embed their geometry and textures; external URLs are not allowed.");
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
}
|
|
6544
|
+
const manager = new LoadingManager();
|
|
6545
|
+
manager.setURLModifier((url) => {
|
|
6546
|
+
if (url.startsWith("data:") || url.startsWith("blob:")) return url;
|
|
6547
|
+
throw new Error("Scenery GLB files must embed their geometry and textures; external URLs are not allowed.");
|
|
6548
|
+
});
|
|
6549
|
+
const gltf = await new GLTFLoader(manager).parseAsync(asset.bytes, "");
|
|
6550
|
+
if (!gltf.scene?.isObject3D) throw new Error("The scenery GLB has no scene to display.");
|
|
6551
|
+
return [gltf.scene, ...gltf.scenes.filter((root) => root !== gltf.scene)];
|
|
6552
|
+
}
|
|
6553
|
+
function createSceneSceneryRenderer(options) {
|
|
6554
|
+
const { scene, rendering, loadAsset } = options;
|
|
6555
|
+
if (!scene?.isScene || typeof loadAsset !== "function") throw new Error("Scenery requires a Three scene and an asset loader.");
|
|
6556
|
+
let disposed = false, disposal = null;
|
|
6557
|
+
let backgroundKey = null, backgroundGeneration = 0;
|
|
6558
|
+
let backgroundLoad = null, currentDisplay = null;
|
|
6559
|
+
const models = /* @__PURE__ */ new Map(), tasks = /* @__PURE__ */ new Set();
|
|
6560
|
+
const report = (message) => {
|
|
6561
|
+
try {
|
|
6562
|
+
options.onError?.(message);
|
|
6563
|
+
} catch (error) {
|
|
6564
|
+
console.error("Scenery error subscriber failed.", error);
|
|
6565
|
+
}
|
|
6566
|
+
};
|
|
6567
|
+
const track = (task) => {
|
|
6568
|
+
tasks.add(task);
|
|
6569
|
+
void task.then(() => tasks.delete(task), (error) => {
|
|
6570
|
+
tasks.delete(task);
|
|
6571
|
+
report(messageOf(error));
|
|
6572
|
+
});
|
|
6573
|
+
return task;
|
|
6574
|
+
};
|
|
6575
|
+
const release = (callback) => {
|
|
6576
|
+
try {
|
|
6577
|
+
return rendering ? rendering.releaseSceneResources(callback) : (callback(), Promise.resolve());
|
|
6578
|
+
} catch (error) {
|
|
6579
|
+
return Promise.reject(error);
|
|
6580
|
+
}
|
|
6581
|
+
};
|
|
6582
|
+
const background = acquireSceneBackground(scene, () => {
|
|
6583
|
+
void dispose().catch((error) => report(messageOf(error)));
|
|
6584
|
+
});
|
|
6585
|
+
const retire = (model) => {
|
|
6586
|
+
if (model.retired) return Promise.resolve();
|
|
6587
|
+
model.retired = true;
|
|
6588
|
+
model.controller.abort();
|
|
6589
|
+
return release(() => {
|
|
6590
|
+
model.wrapper.removeFromParent();
|
|
6591
|
+
model.attached = false;
|
|
6592
|
+
disposeRoots(model.roots);
|
|
6593
|
+
model.roots = [];
|
|
6594
|
+
});
|
|
6595
|
+
};
|
|
6596
|
+
const mount = (instance) => {
|
|
6597
|
+
const wrapper = new Group();
|
|
6598
|
+
wrapper.name = `Scenery ${instance.instanceId}`;
|
|
6599
|
+
wrapper.position.set(instance.position.x, instance.position.y, instance.position.z);
|
|
6600
|
+
const model = { assetId: instance.assetId, wrapper, controller: new AbortController(), roots: [], attached: false, retired: false };
|
|
6601
|
+
models.set(instance.instanceId, model);
|
|
6602
|
+
track((async () => {
|
|
6603
|
+
let roots = [];
|
|
6604
|
+
try {
|
|
6605
|
+
const asset = await loadAsset(model.assetId, model.controller.signal);
|
|
6606
|
+
if (model.controller.signal.aborted) return;
|
|
6607
|
+
roots = await parseModel(asset);
|
|
6608
|
+
if (model.controller.signal.aborted || disposed) {
|
|
6609
|
+
disposeRoots(roots);
|
|
6610
|
+
roots = [];
|
|
6611
|
+
return;
|
|
6612
|
+
}
|
|
6613
|
+
await release(() => {
|
|
6614
|
+
if (model.controller.signal.aborted || disposed) {
|
|
6615
|
+
disposeRoots(roots);
|
|
6616
|
+
roots = [];
|
|
6617
|
+
return;
|
|
6618
|
+
}
|
|
6619
|
+
model.roots = roots;
|
|
6620
|
+
roots = [];
|
|
6621
|
+
wrapper.add(model.roots[0]);
|
|
6622
|
+
scene.add(wrapper);
|
|
6623
|
+
model.attached = true;
|
|
6624
|
+
});
|
|
6625
|
+
} catch (error) {
|
|
6626
|
+
if (roots.length) disposeRoots(roots);
|
|
6627
|
+
if (!model.controller.signal.aborted && !disposed) {
|
|
6628
|
+
if (models.get(instance.instanceId) === model) models.delete(instance.instanceId);
|
|
6629
|
+
await retire(model);
|
|
6630
|
+
report(`Scenery placement ${instance.instanceId} could not load: ${messageOf(error)}`);
|
|
6631
|
+
}
|
|
6632
|
+
}
|
|
6633
|
+
})());
|
|
6634
|
+
};
|
|
6635
|
+
const updateBackground = (skybox) => {
|
|
6636
|
+
const key = JSON.stringify(skybox);
|
|
6637
|
+
if (backgroundKey === key) return;
|
|
6638
|
+
backgroundKey = key;
|
|
6639
|
+
const generation = ++backgroundGeneration;
|
|
6640
|
+
backgroundLoad?.abort();
|
|
6641
|
+
const controller = new AbortController();
|
|
6642
|
+
backgroundLoad = controller;
|
|
6643
|
+
track((async () => {
|
|
6644
|
+
let next = null;
|
|
6645
|
+
try {
|
|
6646
|
+
if (skybox) {
|
|
6647
|
+
if (skybox.kind === "color") next = display(new Color(skybox.color), skybox);
|
|
6648
|
+
else if (!skybox.assetId) next = generatedSky(skybox);
|
|
6649
|
+
else {
|
|
6650
|
+
const asset = await loadAsset(skybox.assetId, controller.signal);
|
|
6651
|
+
if (controller.signal.aborted) return;
|
|
6652
|
+
next = await imageDisplay(asset, skybox);
|
|
6653
|
+
}
|
|
6654
|
+
}
|
|
6655
|
+
await release(() => {
|
|
6656
|
+
if (controller.signal.aborted || generation !== backgroundGeneration || disposed) {
|
|
6657
|
+
next?.dispose();
|
|
6658
|
+
next = null;
|
|
6659
|
+
return;
|
|
6660
|
+
}
|
|
6661
|
+
if (next) background.set(next.value, next.intensity, next.rotation);
|
|
6662
|
+
else background.clear();
|
|
6663
|
+
const previous = currentDisplay;
|
|
6664
|
+
currentDisplay = next;
|
|
6665
|
+
next = null;
|
|
6666
|
+
previous?.dispose();
|
|
6667
|
+
});
|
|
6668
|
+
} catch (error) {
|
|
6669
|
+
next?.dispose();
|
|
6670
|
+
if (!controller.signal.aborted && generation === backgroundGeneration && !disposed) {
|
|
6671
|
+
backgroundKey = null;
|
|
6672
|
+
report(`Scenery background could not load: ${messageOf(error)}`);
|
|
6673
|
+
}
|
|
6674
|
+
}
|
|
6675
|
+
})());
|
|
6676
|
+
};
|
|
6677
|
+
function dispose() {
|
|
6678
|
+
if (disposal) return disposal;
|
|
6679
|
+
disposed = true;
|
|
6680
|
+
backgroundGeneration++;
|
|
6681
|
+
backgroundLoad?.abort();
|
|
6682
|
+
background.dispose();
|
|
6683
|
+
const releases = [...models.values()].map(retire);
|
|
6684
|
+
models.clear();
|
|
6685
|
+
releases.push(release(() => {
|
|
6686
|
+
currentDisplay?.dispose();
|
|
6687
|
+
currentDisplay = null;
|
|
6688
|
+
}));
|
|
6689
|
+
disposal = (async () => {
|
|
6690
|
+
const results = await Promise.allSettled([...tasks, ...releases]);
|
|
6691
|
+
const failed = results.find((result) => result.status === "rejected");
|
|
6692
|
+
if (failed) throw failed.reason;
|
|
6693
|
+
})();
|
|
6694
|
+
return disposal;
|
|
6695
|
+
}
|
|
6696
|
+
return {
|
|
6697
|
+
update(value) {
|
|
6698
|
+
if (disposed) throw new Error("The scenery renderer has been disposed.");
|
|
6699
|
+
const layout = copyLayout(value);
|
|
6700
|
+
const next = new Map(layout.instances.map((instance) => [instance.instanceId, instance]));
|
|
6701
|
+
for (const [id, model] of models) if (next.get(id)?.assetId !== model.assetId) {
|
|
6702
|
+
models.delete(id);
|
|
6703
|
+
track(retire(model));
|
|
6704
|
+
}
|
|
6705
|
+
for (const instance of layout.instances) {
|
|
6706
|
+
const current = models.get(instance.instanceId);
|
|
6707
|
+
if (current) current.wrapper.position.set(instance.position.x, instance.position.y, instance.position.z);
|
|
6708
|
+
else mount(instance);
|
|
6709
|
+
}
|
|
6710
|
+
updateBackground(layout.skybox);
|
|
6711
|
+
},
|
|
6712
|
+
dispose
|
|
6713
|
+
};
|
|
6714
|
+
}
|
|
5732
6715
|
var COLORS = {
|
|
5733
6716
|
mapped: 4889258,
|
|
5734
6717
|
unmapped: 5925498,
|
|
@@ -5855,8 +6838,8 @@ function createOctahedralBoneGeometry() {
|
|
|
5855
6838
|
-w,
|
|
5856
6839
|
MID_POSITION
|
|
5857
6840
|
]);
|
|
5858
|
-
const geometry = new
|
|
5859
|
-
geometry.setAttribute("position", new
|
|
6841
|
+
const geometry = new THREE11.BufferGeometry();
|
|
6842
|
+
geometry.setAttribute("position", new THREE11.BufferAttribute(positions, 3));
|
|
5860
6843
|
geometry.computeVertexNormals();
|
|
5861
6844
|
return geometry;
|
|
5862
6845
|
}
|
|
@@ -5873,24 +6856,24 @@ function collectBones(root) {
|
|
|
5873
6856
|
});
|
|
5874
6857
|
return Array.from(bones);
|
|
5875
6858
|
}
|
|
5876
|
-
function
|
|
6859
|
+
function isBone3(obj) {
|
|
5877
6860
|
return !!obj && obj.isBone;
|
|
5878
6861
|
}
|
|
5879
6862
|
function countBoneSubtree(bone) {
|
|
5880
6863
|
let count = 1;
|
|
5881
6864
|
for (const child of bone.children) {
|
|
5882
|
-
if (
|
|
6865
|
+
if (isBone3(child)) {
|
|
5883
6866
|
count += countBoneSubtree(child);
|
|
5884
6867
|
}
|
|
5885
6868
|
}
|
|
5886
6869
|
return count;
|
|
5887
6870
|
}
|
|
5888
6871
|
function resolveTopLevelHelperRoot(root) {
|
|
5889
|
-
const topLevelBones = collectBones(root).filter((bone) => !
|
|
6872
|
+
const topLevelBones = collectBones(root).filter((bone) => !isBone3(bone.parent));
|
|
5890
6873
|
if (topLevelBones.length !== 1) return null;
|
|
5891
6874
|
let helperRoot = topLevelBones[0];
|
|
5892
6875
|
while (ROOT_HELPER_NAME_PATTERN.test(helperRoot.name)) {
|
|
5893
|
-
const boneChildren = helperRoot.children.filter(
|
|
6876
|
+
const boneChildren = helperRoot.children.filter(isBone3);
|
|
5894
6877
|
if (boneChildren.length !== 1) break;
|
|
5895
6878
|
const onlyChild = boneChildren[0];
|
|
5896
6879
|
if (!ROOT_HELPER_NAME_PATTERN.test(onlyChild.name)) break;
|
|
@@ -5901,7 +6884,7 @@ function resolveTopLevelHelperRoot(root) {
|
|
|
5901
6884
|
function resolveBodyDisplayRoot(root) {
|
|
5902
6885
|
const helperRoot = resolveTopLevelHelperRoot(root);
|
|
5903
6886
|
if (!helperRoot) return null;
|
|
5904
|
-
const boneChildren = helperRoot.children.filter(
|
|
6887
|
+
const boneChildren = helperRoot.children.filter(isBone3);
|
|
5905
6888
|
if (boneChildren.length === 0) return null;
|
|
5906
6889
|
if (boneChildren.length === 1) return boneChildren[0];
|
|
5907
6890
|
const subtreeSizes = boneChildren.map((child) => ({ child, size: countBoneSubtree(child) })).sort((left, right) => right.size - left.size);
|
|
@@ -5913,7 +6896,7 @@ function resolveBodyDisplayRoot(root) {
|
|
|
5913
6896
|
return null;
|
|
5914
6897
|
}
|
|
5915
6898
|
function collectBoneChildren(bone) {
|
|
5916
|
-
return bone.children.filter(
|
|
6899
|
+
return bone.children.filter(isBone3);
|
|
5917
6900
|
}
|
|
5918
6901
|
function hasZeroLengthConnection(parentBone, childBone) {
|
|
5919
6902
|
parentBone.getWorldPosition(_parentPos);
|
|
@@ -5922,7 +6905,7 @@ function hasZeroLengthConnection(parentBone, childBone) {
|
|
|
5922
6905
|
}
|
|
5923
6906
|
function hasRootHelperAncestor(bone) {
|
|
5924
6907
|
let current = bone.parent;
|
|
5925
|
-
while (
|
|
6908
|
+
while (isBone3(current)) {
|
|
5926
6909
|
if (ROOT_HELPER_NAME_PATTERN.test(current.name)) return true;
|
|
5927
6910
|
current = current.parent;
|
|
5928
6911
|
}
|
|
@@ -5932,7 +6915,7 @@ function shouldSkipRootHelperConnection(parentBone, boneChildren) {
|
|
|
5932
6915
|
if (boneChildren.length !== 1) return false;
|
|
5933
6916
|
const childBone = boneChildren[0];
|
|
5934
6917
|
if (ROOT_HELPER_NAME_PATTERN.test(parentBone.name)) {
|
|
5935
|
-
if (!
|
|
6918
|
+
if (!isBone3(parentBone.parent)) return true;
|
|
5936
6919
|
return collectBoneChildren(childBone).some((grandChild) => hasZeroLengthConnection(childBone, grandChild));
|
|
5937
6920
|
}
|
|
5938
6921
|
return hasRootHelperAncestor(parentBone) && HIP_NAME_PATTERN.test(parentBone.name) && PELVIS_NAME_PATTERN.test(childBone.name) && hasZeroLengthConnection(parentBone, childBone);
|
|
@@ -5945,20 +6928,20 @@ function setRayFromPointer(clientX, clientY, camera, domElement) {
|
|
|
5945
6928
|
_raycaster.setFromCamera(_mouse, camera);
|
|
5946
6929
|
return true;
|
|
5947
6930
|
}
|
|
5948
|
-
var _parentPos = new
|
|
5949
|
-
var _childPos = new
|
|
5950
|
-
var _direction = new
|
|
5951
|
-
var _zAxis = new
|
|
5952
|
-
var _quaternion = new
|
|
5953
|
-
var _bonePos = new
|
|
5954
|
-
var _boneQuat = new
|
|
5955
|
-
var _boneEuler = new
|
|
5956
|
-
var _mouse = new
|
|
5957
|
-
var _raycaster = new
|
|
5958
|
-
var _cameraSpacePosition = new
|
|
5959
|
-
var _planeNormal = new
|
|
5960
|
-
var _dragPlane = new
|
|
5961
|
-
var _planeHit = new
|
|
6931
|
+
var _parentPos = new THREE11.Vector3();
|
|
6932
|
+
var _childPos = new THREE11.Vector3();
|
|
6933
|
+
var _direction = new THREE11.Vector3();
|
|
6934
|
+
var _zAxis = new THREE11.Vector3(0, 0, 1);
|
|
6935
|
+
var _quaternion = new THREE11.Quaternion();
|
|
6936
|
+
var _bonePos = new THREE11.Vector3();
|
|
6937
|
+
var _boneQuat = new THREE11.Quaternion();
|
|
6938
|
+
var _boneEuler = new THREE11.Euler();
|
|
6939
|
+
var _mouse = new THREE11.Vector2();
|
|
6940
|
+
var _raycaster = new THREE11.Raycaster();
|
|
6941
|
+
var _cameraSpacePosition = new THREE11.Vector3();
|
|
6942
|
+
var _planeNormal = new THREE11.Vector3();
|
|
6943
|
+
var _dragPlane = new THREE11.Plane();
|
|
6944
|
+
var _planeHit = new THREE11.Vector3();
|
|
5962
6945
|
function resolveSkeletonOverlayStartBoneName(root) {
|
|
5963
6946
|
return resolveBodyDisplayRoot(root)?.name ?? null;
|
|
5964
6947
|
}
|
|
@@ -6005,9 +6988,9 @@ var SkeletonOverlay = class {
|
|
|
6005
6988
|
this.scene = scene;
|
|
6006
6989
|
this.mappedBoneNames = mappedBoneNames;
|
|
6007
6990
|
this.semanticKeyMap = semanticKeyMap;
|
|
6008
|
-
this.opacityScale =
|
|
6991
|
+
this.opacityScale = THREE11.MathUtils.clamp(options.opacityScale ?? 1, 0, 1);
|
|
6009
6992
|
this.focusSelectedBone = options.focusSelectedBone ?? false;
|
|
6010
|
-
this.group = new
|
|
6993
|
+
this.group = new THREE11.Group();
|
|
6011
6994
|
this.group.name = "SkeletonOverlay";
|
|
6012
6995
|
this.group.renderOrder = 999;
|
|
6013
6996
|
this.group.visible = false;
|
|
@@ -6016,50 +6999,50 @@ var SkeletonOverlay = class {
|
|
|
6016
6999
|
transparent: true,
|
|
6017
7000
|
depthTest: false,
|
|
6018
7001
|
depthWrite: false,
|
|
6019
|
-
side:
|
|
7002
|
+
side: THREE11.DoubleSide
|
|
6020
7003
|
};
|
|
6021
|
-
this.mappedMaterial = new
|
|
6022
|
-
this.unmappedMaterial = new
|
|
6023
|
-
this.highlightMaterial = new
|
|
6024
|
-
this.selectedMaterial = new
|
|
6025
|
-
this.contextMaterial = new
|
|
6026
|
-
this.mutedMaterial = new
|
|
7004
|
+
this.mappedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.mapped, opacity: OPACITY.mapped });
|
|
7005
|
+
this.unmappedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.unmapped, opacity: OPACITY.unmapped });
|
|
7006
|
+
this.highlightMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.highlighted, opacity: OPACITY.highlighted });
|
|
7007
|
+
this.selectedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.selected, opacity: OPACITY.selected });
|
|
7008
|
+
this.contextMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.selected, opacity: OPACITY.context });
|
|
7009
|
+
this.mutedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.unmapped, opacity: OPACITY.muted });
|
|
6027
7010
|
this.mappedMaterial.forceSinglePass = true;
|
|
6028
7011
|
this.unmappedMaterial.forceSinglePass = true;
|
|
6029
7012
|
this.highlightMaterial.forceSinglePass = true;
|
|
6030
7013
|
this.selectedMaterial.forceSinglePass = true;
|
|
6031
7014
|
this.contextMaterial.forceSinglePass = true;
|
|
6032
7015
|
this.mutedMaterial.forceSinglePass = true;
|
|
6033
|
-
this.axisRingGeometry = new
|
|
6034
|
-
this.axisRingHitGeometry = new
|
|
6035
|
-
this.axisCenterGeometry = new
|
|
6036
|
-
this.pitchRingMaterial = new
|
|
6037
|
-
this.yawRingMaterial = new
|
|
6038
|
-
this.rollRingMaterial = new
|
|
6039
|
-
this.axisHitMaterial = new
|
|
7016
|
+
this.axisRingGeometry = new THREE11.TorusGeometry(1, 0.032, 10, 120);
|
|
7017
|
+
this.axisRingHitGeometry = new THREE11.TorusGeometry(1, AXIS_GIZMO_HIT_TUBE, 8, 64);
|
|
7018
|
+
this.axisCenterGeometry = new THREE11.SphereGeometry(0.082, 12, 8);
|
|
7019
|
+
this.pitchRingMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.pitch, opacity: AXIS_GIZMO_IDLE_OPACITY });
|
|
7020
|
+
this.yawRingMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.yaw, opacity: AXIS_GIZMO_IDLE_OPACITY });
|
|
7021
|
+
this.rollRingMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.roll, opacity: AXIS_GIZMO_IDLE_OPACITY });
|
|
7022
|
+
this.axisHitMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, opacity: 0 });
|
|
6040
7023
|
this.axisHitMaterial.colorWrite = false;
|
|
6041
|
-
this.axisCenterMaterial = new
|
|
6042
|
-
this.axisGizmoGroup = new
|
|
7024
|
+
this.axisCenterMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: 16119802, opacity: 0.92 });
|
|
7025
|
+
this.axisGizmoGroup = new THREE11.Group();
|
|
6043
7026
|
this.axisGizmoGroup.name = "SelectedBoneAxisGizmo";
|
|
6044
7027
|
this.axisGizmoGroup.visible = false;
|
|
6045
7028
|
this.axisGizmoGroup.renderOrder = 1e3;
|
|
6046
|
-
const pitchRing = new
|
|
7029
|
+
const pitchRing = new THREE11.Mesh(this.axisRingGeometry, this.pitchRingMaterial);
|
|
6047
7030
|
pitchRing.renderOrder = 1e3;
|
|
6048
7031
|
pitchRing.userData.axis = "pitch";
|
|
6049
|
-
const pitchHit = new
|
|
7032
|
+
const pitchHit = new THREE11.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
|
|
6050
7033
|
pitchHit.userData.axis = "pitch";
|
|
6051
|
-
const yawRing = new
|
|
7034
|
+
const yawRing = new THREE11.Mesh(this.axisRingGeometry, this.yawRingMaterial);
|
|
6052
7035
|
yawRing.renderOrder = 1e3;
|
|
6053
7036
|
yawRing.userData.axis = "yaw";
|
|
6054
|
-
const yawHit = new
|
|
7037
|
+
const yawHit = new THREE11.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
|
|
6055
7038
|
yawHit.userData.axis = "yaw";
|
|
6056
|
-
const rollRing = new
|
|
7039
|
+
const rollRing = new THREE11.Mesh(this.axisRingGeometry, this.rollRingMaterial);
|
|
6057
7040
|
rollRing.renderOrder = 1e3;
|
|
6058
7041
|
rollRing.userData.axis = "roll";
|
|
6059
|
-
const rollHit = new
|
|
7042
|
+
const rollHit = new THREE11.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
|
|
6060
7043
|
rollHit.rotation.copy(rollRing.rotation);
|
|
6061
7044
|
rollHit.userData.axis = "roll";
|
|
6062
|
-
const center = new
|
|
7045
|
+
const center = new THREE11.Mesh(this.axisCenterGeometry, this.axisCenterMaterial);
|
|
6063
7046
|
center.renderOrder = 1001;
|
|
6064
7047
|
this.axisGizmoMeshes = {
|
|
6065
7048
|
pitch: pitchRing,
|
|
@@ -6099,11 +7082,11 @@ var SkeletonOverlay = class {
|
|
|
6099
7082
|
this.boneTree = allBones.map((bone) => {
|
|
6100
7083
|
const isMapped = this.mappedBoneNames.has(bone.name);
|
|
6101
7084
|
const semanticKey = this.semanticKeyMap.get(bone.name) ?? null;
|
|
6102
|
-
const boneChildren = bone.children.filter((c) =>
|
|
6103
|
-
const parentName = bone.parent &&
|
|
7085
|
+
const boneChildren = bone.children.filter((c) => isBone3(c) && includedBones.has(c)).map((c) => c.name);
|
|
7086
|
+
const parentName = bone.parent && isBone3(bone.parent) && includedBones.has(bone.parent) ? bone.parent.name : null;
|
|
6104
7087
|
let depth = 0;
|
|
6105
7088
|
let current = bone.parent;
|
|
6106
|
-
while (current &&
|
|
7089
|
+
while (current && isBone3(current) && includedBones.has(current)) {
|
|
6107
7090
|
depth++;
|
|
6108
7091
|
current = current.parent;
|
|
6109
7092
|
}
|
|
@@ -6119,7 +7102,7 @@ var SkeletonOverlay = class {
|
|
|
6119
7102
|
});
|
|
6120
7103
|
for (const bone of allBones) {
|
|
6121
7104
|
const boneChildren = bone.children.filter(
|
|
6122
|
-
(c) =>
|
|
7105
|
+
(c) => isBone3(c) && includedBones.has(c)
|
|
6123
7106
|
);
|
|
6124
7107
|
if (boneChildren.length > 0) {
|
|
6125
7108
|
if (shouldSkipRootHelperConnection(bone, boneChildren)) {
|
|
@@ -6133,15 +7116,15 @@ var SkeletonOverlay = class {
|
|
|
6133
7116
|
}
|
|
6134
7117
|
}
|
|
6135
7118
|
this.group.add(this.axisGizmoGroup);
|
|
6136
|
-
const sentinelGeo = new
|
|
6137
|
-
const sentinelMat = new
|
|
7119
|
+
const sentinelGeo = new THREE11.SphereGeometry(1e-6, 4, 2);
|
|
7120
|
+
const sentinelMat = new THREE11.MeshBasicMaterial({
|
|
6138
7121
|
transparent: true,
|
|
6139
7122
|
opacity: 0,
|
|
6140
7123
|
depthWrite: false,
|
|
6141
7124
|
depthTest: false
|
|
6142
7125
|
});
|
|
6143
7126
|
sentinelMat.colorWrite = false;
|
|
6144
|
-
const sentinel = new
|
|
7127
|
+
const sentinel = new THREE11.Mesh(sentinelGeo, sentinelMat);
|
|
6145
7128
|
sentinel.visible = true;
|
|
6146
7129
|
sentinel.frustumCulled = false;
|
|
6147
7130
|
sentinel.onBeforeRender = (renderer, _scene, camera) => this.update(camera, renderer.domElement);
|
|
@@ -6153,7 +7136,7 @@ var SkeletonOverlay = class {
|
|
|
6153
7136
|
createBoneMesh(parentBone, childBone) {
|
|
6154
7137
|
const isMapped = this.mappedBoneNames.has(parentBone.name);
|
|
6155
7138
|
const material = isMapped ? this.mappedMaterial : this.unmappedMaterial;
|
|
6156
|
-
const mesh = new
|
|
7139
|
+
const mesh = new THREE11.Mesh(this.sharedGeometry, material);
|
|
6157
7140
|
mesh.name = `bone_vis_${parentBone.name}${childBone ? `_${childBone.name}` : "_leaf"}`;
|
|
6158
7141
|
mesh.frustumCulled = false;
|
|
6159
7142
|
mesh.renderOrder = 999;
|
|
@@ -6233,20 +7216,20 @@ var SkeletonOverlay = class {
|
|
|
6233
7216
|
bone.getWorldPosition(_parentPos);
|
|
6234
7217
|
let referenceLength = 0;
|
|
6235
7218
|
for (const child of bone.children) {
|
|
6236
|
-
if (!
|
|
7219
|
+
if (!isBone3(child)) continue;
|
|
6237
7220
|
child.getWorldPosition(_childPos);
|
|
6238
7221
|
const nextDistance = _parentPos.distanceTo(_childPos);
|
|
6239
7222
|
if (nextDistance <= 1e-4) continue;
|
|
6240
7223
|
referenceLength = referenceLength === 0 ? nextDistance : Math.min(referenceLength, nextDistance);
|
|
6241
7224
|
}
|
|
6242
|
-
if (referenceLength <= 1e-4 &&
|
|
7225
|
+
if (referenceLength <= 1e-4 && isBone3(bone.parent)) {
|
|
6243
7226
|
bone.parent.getWorldPosition(_childPos);
|
|
6244
7227
|
referenceLength = _parentPos.distanceTo(_childPos);
|
|
6245
7228
|
}
|
|
6246
7229
|
if (referenceLength <= 1e-4) {
|
|
6247
7230
|
referenceLength = LEAF_BONE_LENGTH;
|
|
6248
7231
|
}
|
|
6249
|
-
return
|
|
7232
|
+
return THREE11.MathUtils.clamp(
|
|
6250
7233
|
referenceLength * AXIS_GIZMO_RADIUS_FACTOR,
|
|
6251
7234
|
AXIS_GIZMO_MIN_RADIUS,
|
|
6252
7235
|
AXIS_GIZMO_MAX_RADIUS
|
|
@@ -6262,7 +7245,7 @@ var SkeletonOverlay = class {
|
|
|
6262
7245
|
_cameraSpacePosition.copy(origin).applyMatrix4(perspectiveCamera.matrixWorldInverse);
|
|
6263
7246
|
const depth = -_cameraSpacePosition.z;
|
|
6264
7247
|
if (!Number.isFinite(depth) || depth <= perspectiveCamera.near) return null;
|
|
6265
|
-
const visibleWorldHeight = 2 * depth * Math.tan(
|
|
7248
|
+
const visibleWorldHeight = 2 * depth * Math.tan(THREE11.MathUtils.degToRad(perspectiveCamera.getEffectiveFOV()) / 2);
|
|
6266
7249
|
return visibleWorldHeight * (AXIS_GIZMO_SCREEN_RADIUS_PX / viewportHeight);
|
|
6267
7250
|
}
|
|
6268
7251
|
if (camera.isOrthographicCamera) {
|
|
@@ -6393,9 +7376,9 @@ var SkeletonOverlay = class {
|
|
|
6393
7376
|
return {
|
|
6394
7377
|
position: [_bonePos.x, _bonePos.y, _bonePos.z],
|
|
6395
7378
|
rotation: [
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
7379
|
+
THREE11.MathUtils.radToDeg(_boneEuler.x),
|
|
7380
|
+
THREE11.MathUtils.radToDeg(_boneEuler.y),
|
|
7381
|
+
THREE11.MathUtils.radToDeg(_boneEuler.z)
|
|
6399
7382
|
]
|
|
6400
7383
|
};
|
|
6401
7384
|
}
|
|
@@ -6665,7 +7648,7 @@ function buildMorphVertexPreviewRenderData(meshes, morphName, cache, options = {
|
|
|
6665
7648
|
if (pointCount === 0) return null;
|
|
6666
7649
|
const positions = previous?.positions.length === pointCount * 3 ? previous.positions : new Float32Array(pointCount * 3);
|
|
6667
7650
|
const colors = previous?.colors.length === pointCount * 3 ? previous.colors : new Float32Array(pointCount * 3);
|
|
6668
|
-
const vertex = new
|
|
7651
|
+
const vertex = new THREE11.Vector3();
|
|
6669
7652
|
let cursor = 0;
|
|
6670
7653
|
for (const { mesh, sample } of sampledMeshes) {
|
|
6671
7654
|
mesh.updateWorldMatrix(true, false);
|
|
@@ -6759,7 +7742,7 @@ var MorphVertexPreview = class {
|
|
|
6759
7742
|
this.points = null;
|
|
6760
7743
|
}
|
|
6761
7744
|
createPoints() {
|
|
6762
|
-
const points = new
|
|
7745
|
+
const points = new THREE11.Points(new THREE11.BufferGeometry(), new THREE11.PointsMaterial({
|
|
6763
7746
|
size: 0.012,
|
|
6764
7747
|
sizeAttenuation: true,
|
|
6765
7748
|
transparent: true,
|
|
@@ -6797,8 +7780,8 @@ var MorphVertexPreview = class {
|
|
|
6797
7780
|
const color = geometry.getAttribute("color");
|
|
6798
7781
|
if (!position || !color || position.count !== data.pointCount || color.count !== data.pointCount) {
|
|
6799
7782
|
if (position || color) geometry.dispose();
|
|
6800
|
-
geometry.setAttribute("position", new
|
|
6801
|
-
geometry.setAttribute("color", new
|
|
7783
|
+
geometry.setAttribute("position", new THREE11.BufferAttribute(data.positions, 3));
|
|
7784
|
+
geometry.setAttribute("color", new THREE11.BufferAttribute(data.colors, 3));
|
|
6802
7785
|
return;
|
|
6803
7786
|
}
|
|
6804
7787
|
if (position.array !== data.positions) position.array.set(data.positions);
|
|
@@ -6858,7 +7841,7 @@ var transform = (obj) => ({
|
|
|
6858
7841
|
rotation: { x: obj.quaternion.x, y: obj.quaternion.y, z: obj.quaternion.z, w: obj.quaternion.w },
|
|
6859
7842
|
scale: { x: obj.scale.x, y: obj.scale.y, z: obj.scale.z }
|
|
6860
7843
|
});
|
|
6861
|
-
var
|
|
7844
|
+
var snapshot2 = (obj, reference) => {
|
|
6862
7845
|
if (!reference) {
|
|
6863
7846
|
return {
|
|
6864
7847
|
obj,
|
|
@@ -6908,6 +7891,7 @@ var ThreeModelInspector = class {
|
|
|
6908
7891
|
const allMeshes = [];
|
|
6909
7892
|
const boneObjects = [];
|
|
6910
7893
|
model.traverse((obj) => {
|
|
7894
|
+
if (highlightOverlays.has(obj)) return;
|
|
6911
7895
|
objects.push(obj);
|
|
6912
7896
|
if (obj.isMesh) allMeshes.push(obj);
|
|
6913
7897
|
if (obj.isBone || obj.type === "Bone") boneObjects.push(obj);
|
|
@@ -6943,7 +7927,7 @@ var ThreeModelInspector = class {
|
|
|
6943
7927
|
}
|
|
6944
7928
|
const bones = {};
|
|
6945
7929
|
for (const bone of boneObjects) {
|
|
6946
|
-
if (bone.name) bones[bone.name] =
|
|
7930
|
+
if (bone.name) bones[bone.name] = snapshot2(bone, references?.get(bone));
|
|
6947
7931
|
}
|
|
6948
7932
|
const profile = options.profile;
|
|
6949
7933
|
if (profile?.boneNodes) {
|
|
@@ -6952,7 +7936,7 @@ var ThreeModelInspector = class {
|
|
|
6952
7936
|
const suffix = profile.boneSuffix ?? "";
|
|
6953
7937
|
const name = `${base.startsWith(prefix) ? "" : prefix}${base}${base.endsWith(suffix) ? "" : suffix}`;
|
|
6954
7938
|
const object = model.getObjectByName(name) ?? model.getObjectByName(base);
|
|
6955
|
-
if (object) bones[key] = bones[object.name] ??
|
|
7939
|
+
if (object) bones[key] = bones[object.name] ?? snapshot2(object, references?.get(object));
|
|
6956
7940
|
}
|
|
6957
7941
|
}
|
|
6958
7942
|
return {
|
|
@@ -7134,15 +8118,17 @@ function serializeAnimationClips(model, clips, inspection) {
|
|
|
7134
8118
|
});
|
|
7135
8119
|
}
|
|
7136
8120
|
var materials = (value) => value ? Array.isArray(value) ? value : [value] : [];
|
|
8121
|
+
var highlightOverlays = /* @__PURE__ */ new WeakSet();
|
|
7137
8122
|
var ThreeFrameApplier = class {
|
|
7138
8123
|
constructor() {
|
|
7139
8124
|
__publicField(this, "meshes", /* @__PURE__ */ new Map());
|
|
7140
8125
|
__publicField(this, "morphs", /* @__PURE__ */ new Map());
|
|
7141
8126
|
__publicField(this, "bones", /* @__PURE__ */ new Map());
|
|
7142
8127
|
__publicField(this, "objects", /* @__PURE__ */ new Map());
|
|
7143
|
-
__publicField(this, "
|
|
8128
|
+
__publicField(this, "highlighted", /* @__PURE__ */ new Map());
|
|
7144
8129
|
}
|
|
7145
8130
|
setBindings(inspection) {
|
|
8131
|
+
this.clearMeshHighlights();
|
|
7146
8132
|
this.meshes = new Map(inspection.meshBindings);
|
|
7147
8133
|
this.morphs = new Map(inspection.morphBindings);
|
|
7148
8134
|
this.bones = new Map(inspection.boneBindings);
|
|
@@ -7188,7 +8174,7 @@ var ThreeFrameApplier = class {
|
|
|
7188
8174
|
const config = configs[object.name];
|
|
7189
8175
|
if (!config) return;
|
|
7190
8176
|
if (typeof config.visible === "boolean") object.visible = config.visible;
|
|
7191
|
-
if (config.material) this.
|
|
8177
|
+
if (config.material) this.applyHighlightedMeshMaterial(object, config.material);
|
|
7192
8178
|
});
|
|
7193
8179
|
}
|
|
7194
8180
|
getMeshMaterialConfig(root, name) {
|
|
@@ -7196,38 +8182,97 @@ var ThreeFrameApplier = class {
|
|
|
7196
8182
|
this.visitMesh(root, name, (mesh) => {
|
|
7197
8183
|
const material = materials(mesh.material)[0];
|
|
7198
8184
|
if (!material) return;
|
|
7199
|
-
const blending = Object.
|
|
8185
|
+
const blending = Object.keys(THREE_BLENDING_MODES).find((key) => THREE_BLENDING_MODES[key] === material.blending) ?? "Normal";
|
|
7200
8186
|
result = { renderOrder: mesh.renderOrder, transparent: material.transparent, opacity: material.opacity, depthWrite: material.depthWrite, depthTest: material.depthTest, blending };
|
|
7201
8187
|
});
|
|
7202
8188
|
return result;
|
|
7203
8189
|
}
|
|
7204
8190
|
setMeshMaterialConfig(root, name, config) {
|
|
7205
|
-
this.visitMesh(root, name, (mesh) => this.
|
|
8191
|
+
this.visitMesh(root, name, (mesh) => this.applyHighlightedMeshMaterial(mesh, config));
|
|
8192
|
+
}
|
|
8193
|
+
applyHighlightedMeshMaterial(mesh, config) {
|
|
8194
|
+
this.applyMaterial(mesh, config);
|
|
8195
|
+
const original = this.highlighted.get(mesh)?.original;
|
|
8196
|
+
if (!original) return;
|
|
8197
|
+
const highlighted = mesh.material;
|
|
8198
|
+
try {
|
|
8199
|
+
mesh.material = original;
|
|
8200
|
+
this.applyMaterial(mesh, config);
|
|
8201
|
+
} finally {
|
|
8202
|
+
mesh.material = highlighted;
|
|
8203
|
+
}
|
|
7206
8204
|
}
|
|
7207
8205
|
setMeshVisible(root, name, visible) {
|
|
7208
8206
|
this.visitMesh(root, name, (mesh) => {
|
|
7209
8207
|
mesh.visible = visible;
|
|
7210
8208
|
});
|
|
7211
8209
|
}
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
8210
|
+
/** Apply a complete Rust snapshot. Only native resources and ID bindings live here. */
|
|
8211
|
+
applyMeshHighlightFrame(frame) {
|
|
8212
|
+
if (frame.version !== 1) throw new Error(`Unsupported mesh highlight frame version: ${frame.version}`);
|
|
8213
|
+
const selected = new Set(frame.highlights.map((write) => this.meshes.get(write.meshId)));
|
|
8214
|
+
for (const mesh of this.highlighted.keys()) {
|
|
8215
|
+
if (!selected.has(mesh)) this.clearMeshHighlight(mesh);
|
|
8216
|
+
}
|
|
8217
|
+
for (const write of frame.highlights) {
|
|
8218
|
+
if (write.edgeMode !== "triangles") throw new Error(`Unsupported highlight edge mode: ${write.edgeMode}`);
|
|
8219
|
+
const mesh = this.meshes.get(write.meshId);
|
|
8220
|
+
if (!mesh) continue;
|
|
8221
|
+
let state = this.highlighted.get(mesh);
|
|
8222
|
+
if (!state) {
|
|
8223
|
+
const original = mesh.material;
|
|
8224
|
+
const copies = materials(original).map((material) => {
|
|
8225
|
+
const copy2 = material.clone();
|
|
8226
|
+
copy2.polygonOffset = true;
|
|
8227
|
+
copy2.polygonOffsetFactor = 1;
|
|
8228
|
+
copy2.polygonOffsetUnits = 1;
|
|
8229
|
+
return copy2;
|
|
8230
|
+
});
|
|
8231
|
+
mesh.material = Array.isArray(original) ? copies : copies[0];
|
|
8232
|
+
const overlay = mesh.clone(false);
|
|
8233
|
+
overlay.name = "";
|
|
8234
|
+
overlay.userData = {};
|
|
8235
|
+
overlay.position.set(0, 0, 0);
|
|
8236
|
+
overlay.quaternion.identity();
|
|
8237
|
+
overlay.scale.set(1, 1, 1);
|
|
8238
|
+
overlay.matrix.identity();
|
|
8239
|
+
overlay.matrixAutoUpdate = true;
|
|
8240
|
+
overlay.visible = true;
|
|
8241
|
+
overlay.castShadow = overlay.receiveShadow = false;
|
|
8242
|
+
overlay.raycast = () => {
|
|
8243
|
+
};
|
|
8244
|
+
overlay.material = new MeshBasicMaterial({ wireframe: write.edgeMode === "triangles", transparent: true, depthWrite: false, toneMapped: false });
|
|
8245
|
+
overlay.onBeforeRender = () => {
|
|
8246
|
+
overlay.geometry = mesh.geometry;
|
|
8247
|
+
overlay.morphTargetInfluences = mesh.morphTargetInfluences;
|
|
8248
|
+
};
|
|
8249
|
+
highlightOverlays.add(overlay);
|
|
8250
|
+
mesh.add(overlay);
|
|
8251
|
+
state = { original, overlay };
|
|
8252
|
+
this.highlighted.set(mesh, state);
|
|
8253
|
+
}
|
|
8254
|
+
for (const material of materials(mesh.material)) {
|
|
7216
8255
|
if (!material.emissive) continue;
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
if (!this.originalEmissive.has(key)) this.originalEmissive.set(key, { color: material.emissive.getHex(), intensity: material.emissiveIntensity ?? 0 });
|
|
7220
|
-
material.emissive.setHex(color);
|
|
7221
|
-
material.emissiveIntensity = intensity2;
|
|
7222
|
-
} else {
|
|
7223
|
-
const original = this.originalEmissive.get(key);
|
|
7224
|
-
if (original) {
|
|
7225
|
-
material.emissive.setHex(original.color);
|
|
7226
|
-
material.emissiveIntensity = original.intensity;
|
|
7227
|
-
}
|
|
7228
|
-
}
|
|
8256
|
+
material.emissive.setRGB(...write.fillColor);
|
|
8257
|
+
material.emissiveIntensity = write.fillIntensity;
|
|
7229
8258
|
}
|
|
7230
|
-
|
|
8259
|
+
const edges = state.overlay.material;
|
|
8260
|
+
edges.color.setRGB(...write.edgeColor);
|
|
8261
|
+
edges.opacity = write.edgeOpacity;
|
|
8262
|
+
edges.side = materials(mesh.material)[0]?.side ?? edges.side;
|
|
8263
|
+
state.overlay.renderOrder = mesh.renderOrder + 1;
|
|
8264
|
+
}
|
|
8265
|
+
}
|
|
8266
|
+
clearMeshHighlights() {
|
|
8267
|
+
for (const mesh of this.highlighted.keys()) this.clearMeshHighlight(mesh);
|
|
8268
|
+
}
|
|
8269
|
+
clearMeshHighlight(mesh) {
|
|
8270
|
+
const state = this.highlighted.get(mesh);
|
|
8271
|
+
state.overlay.removeFromParent();
|
|
8272
|
+
state.overlay.material.dispose();
|
|
8273
|
+
for (const material of materials(mesh.material)) material.dispose();
|
|
8274
|
+
mesh.material = state.original;
|
|
8275
|
+
this.highlighted.delete(mesh);
|
|
7231
8276
|
}
|
|
7232
8277
|
applyHairAppearance(meshes, appearance) {
|
|
7233
8278
|
for (const mesh of meshes) {
|
|
@@ -7388,11 +8433,11 @@ var ThreeFrameApplier = class {
|
|
|
7388
8433
|
var collectMorphMeshes = (root) => {
|
|
7389
8434
|
const meshes = [];
|
|
7390
8435
|
root.traverse((object) => {
|
|
7391
|
-
if (object.isMesh && morphEntries(object).length > 0) meshes.push(object);
|
|
8436
|
+
if (object.isMesh && !highlightOverlays.has(object) && morphEntries(object).length > 0) meshes.push(object);
|
|
7392
8437
|
});
|
|
7393
8438
|
return meshes;
|
|
7394
8439
|
};
|
|
7395
8440
|
|
|
7396
|
-
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, getAUBoneBindingStates, 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, resolveMorphMeshSelections, resolveMorphPreviewMeshes, resolveRegionCameraAngle, resolveRegionVisibilityCameraAngle, resolveSkeletonOverlayStartBoneName, resolveVisemeProfile, serializeAnimationClips, setAUMorphTargets, setHumanoidRoleBinding, shouldProjectMarkerAnchorToSurface, shouldUseFaceCenterForMarkerAnchor, stripConfiguredBoneAffixes, supportsRustEmbodyPreset, toWorldDirection, validateHumanoidCharacterization, validateMappings };
|
|
8441
|
+
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, acquireCharacterSceneVideoCapture, 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 };
|
|
7397
8442
|
//# sourceMappingURL=three.js.map
|
|
7398
8443
|
//# sourceMappingURL=three.js.map
|