@lovelace_lol/embody 0.0.15 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +407 -149
- package/dist/index.js +1320 -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 +12 -11
- package/dist/three/model.d.ts +2 -0
- package/dist/three/nativePlayback.d.ts +6 -0
- package/dist/three/playbackRendering.d.ts +1 -1
- package/dist/three/scene.d.ts +2 -0
- package/dist/three/sceneBackground.d.ts +11 -0
- package/dist/three/scenery.d.ts +46 -0
- package/dist/three/vrm-animation.d.ts +70 -0
- package/dist/three.js +1320 -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/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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 * from '@lovelace_lol/embody/wasm';
|
|
5
5
|
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';
|
|
6
|
-
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
7
6
|
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
|
|
7
|
+
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
8
8
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
9
9
|
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
|
|
10
10
|
|
|
@@ -33,46 +33,72 @@ function annotationProfileInput(profile) {
|
|
|
33
33
|
}
|
|
34
34
|
return result;
|
|
35
35
|
}
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const value = annotationQuery("meshSideOffset", { region, sideSign, size: modelSize });
|
|
41
|
-
return value ? new THREE10.Vector3(value.x, value.y, value.z) : null;
|
|
36
|
+
function readAttribute(attribute) {
|
|
37
|
+
const values = new Float32Array(attribute.count * attribute.itemSize);
|
|
38
|
+
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);
|
|
39
|
+
return values;
|
|
42
40
|
}
|
|
43
|
-
function
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const positions = mesh.geometry.getAttribute("position");
|
|
47
|
-
if (!positions) return null;
|
|
41
|
+
function readMorphGeometry(mesh, names) {
|
|
42
|
+
const position = mesh.geometry.getAttribute("position");
|
|
43
|
+
if (!position || position.itemSize !== 3) return null;
|
|
48
44
|
const attributes = names.flatMap((name) => {
|
|
49
45
|
const index = mesh.morphTargetDictionary?.[name];
|
|
50
46
|
const attribute = index == null ? void 0 : mesh.geometry.morphAttributes.position?.[index];
|
|
51
47
|
return attribute ? [attribute] : [];
|
|
52
48
|
});
|
|
53
|
-
|
|
49
|
+
if (!attributes.length || attributes.some((attribute) => attribute.count !== position.count || attribute.itemSize !== 3)) return null;
|
|
50
|
+
const base = readAttribute(position);
|
|
54
51
|
const morphs = new Float32Array(attributes.length * base.length);
|
|
55
52
|
attributes.forEach((attribute, index) => morphs.set(readAttribute(attribute), index * base.length));
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
return { base, morphs, relative: mesh.geometry.morphTargetsRelative };
|
|
54
|
+
}
|
|
55
|
+
function updateAnnotationWorldMatrices(model) {
|
|
56
|
+
model.parent?.updateWorldMatrix(true, false);
|
|
57
|
+
model.updateMatrixWorld(true);
|
|
58
|
+
}
|
|
59
|
+
function sampleMorphVertices(mesh, indices, samples, vertex = new THREE11.Vector3()) {
|
|
58
60
|
const skinned = mesh;
|
|
59
|
-
if (skinned.isSkinnedMesh)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
if (skinned.isSkinnedMesh) {
|
|
62
|
+
for (const bone of skinned.skeleton.bones) bone.updateWorldMatrix(true, false);
|
|
63
|
+
skinned.skeleton.update();
|
|
64
|
+
}
|
|
65
|
+
for (let offset = 0; offset < indices.length; offset++) {
|
|
66
|
+
mesh.getVertexPosition(indices[offset], vertex).applyMatrix4(mesh.matrixWorld);
|
|
67
|
+
samples[offset * 3] = vertex.x;
|
|
68
|
+
samples[offset * 3 + 1] = vertex.y;
|
|
69
|
+
samples[offset * 3 + 2] = vertex.z;
|
|
65
70
|
}
|
|
66
|
-
const value = core.annotation_morph_center(JSON.stringify(region), samples);
|
|
67
|
-
return value.length === 3 ? new THREE10.Vector3().fromArray(value) : null;
|
|
68
71
|
}
|
|
72
|
+
var MorphGeometryObservation = class {
|
|
73
|
+
constructor() {
|
|
74
|
+
__publicField(this, "revision", 0);
|
|
75
|
+
__publicField(this, "namesJson", "[]");
|
|
76
|
+
__publicField(this, "signature", []);
|
|
77
|
+
}
|
|
78
|
+
observe(mesh) {
|
|
79
|
+
const dictionary = Object.entries(mesh.morphTargetDictionary ?? {}).sort(([a], [b]) => a.localeCompare(b));
|
|
80
|
+
const attributes = [mesh.geometry.getAttribute("position"), ...mesh.geometry.morphAttributes.position ?? []];
|
|
81
|
+
const signature = [mesh.geometry, mesh.geometry.morphTargetsRelative, ...dictionary.flat()];
|
|
82
|
+
for (const attribute of attributes) {
|
|
83
|
+
signature.push(attribute);
|
|
84
|
+
if (attribute) signature.push(
|
|
85
|
+
attribute.array,
|
|
86
|
+
attribute.count,
|
|
87
|
+
attribute.itemSize,
|
|
88
|
+
attribute.normalized,
|
|
89
|
+
"data" in attribute ? attribute.data.version : attribute.version,
|
|
90
|
+
"data" in attribute ? attribute.data.stride : 0,
|
|
91
|
+
"offset" in attribute ? attribute.offset : 0
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (signature.length === this.signature.length && signature.every((value, index) => value === this.signature[index])) return;
|
|
95
|
+
this.signature = signature;
|
|
96
|
+
this.revision++;
|
|
97
|
+
this.namesJson = JSON.stringify(dictionary.map(([name]) => name));
|
|
98
|
+
}
|
|
99
|
+
};
|
|
69
100
|
|
|
70
101
|
// three/annotations/modelObservation.ts
|
|
71
|
-
function readAttribute(attribute) {
|
|
72
|
-
const values = new Float32Array(attribute.count * attribute.itemSize);
|
|
73
|
-
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);
|
|
74
|
-
return values;
|
|
75
|
-
}
|
|
76
102
|
var bounds = (box) => box.isEmpty() ? null : { min: box.min.toArray(), max: box.max.toArray() };
|
|
77
103
|
var AnnotationModelObserver = class {
|
|
78
104
|
constructor(model) {
|
|
@@ -80,6 +106,10 @@ var AnnotationModelObserver = class {
|
|
|
80
106
|
__publicField(this, "objects", /* @__PURE__ */ new Map());
|
|
81
107
|
__publicField(this, "meshes", []);
|
|
82
108
|
__publicField(this, "ids", /* @__PURE__ */ new Map());
|
|
109
|
+
__publicField(this, "morphGeneration", null);
|
|
110
|
+
__publicField(this, "morphObservations", []);
|
|
111
|
+
__publicField(this, "morphGeometry", /* @__PURE__ */ new Map());
|
|
112
|
+
__publicField(this, "vertex", new THREE11.Vector3());
|
|
83
113
|
model.traverse((object) => {
|
|
84
114
|
const id = this.objects.size + 1;
|
|
85
115
|
this.ids.set(object, id);
|
|
@@ -90,13 +120,16 @@ var AnnotationModelObserver = class {
|
|
|
90
120
|
id(object) {
|
|
91
121
|
return this.ids.get(object);
|
|
92
122
|
}
|
|
123
|
+
updateWorldMatrices() {
|
|
124
|
+
updateAnnotationWorldMatrices(this.model);
|
|
125
|
+
}
|
|
93
126
|
capture() {
|
|
94
|
-
this.
|
|
95
|
-
const modelBox = new
|
|
96
|
-
if (modelBox.isEmpty()) modelBox.setFromCenterAndSize(this.model.getWorldPosition(new
|
|
127
|
+
this.updateWorldMatrices();
|
|
128
|
+
const modelBox = new THREE11.Box3().setFromObject(this.model);
|
|
129
|
+
if (modelBox.isEmpty()) modelBox.setFromCenterAndSize(this.model.getWorldPosition(new THREE11.Vector3()), new THREE11.Vector3());
|
|
97
130
|
return {
|
|
98
131
|
bounds: bounds(modelBox),
|
|
99
|
-
quaternion: this.model.getWorldQuaternion(new
|
|
132
|
+
quaternion: this.model.getWorldQuaternion(new THREE11.Quaternion()).toArray(),
|
|
100
133
|
inverse: this.model.matrixWorld.clone().invert().toArray(),
|
|
101
134
|
objects: [...this.objects].map(([id, object]) => {
|
|
102
135
|
const mesh = object;
|
|
@@ -106,8 +139,8 @@ var AnnotationModelObserver = class {
|
|
|
106
139
|
name: object.name,
|
|
107
140
|
kind: mesh.isMesh ? "Mesh" : bone ? "Bone" : object.type,
|
|
108
141
|
parent: object.parent ? this.ids.get(object.parent) ?? null : null,
|
|
109
|
-
position: object.getWorldPosition(new
|
|
110
|
-
bounds: bone ? null : bounds(new
|
|
142
|
+
position: object.getWorldPosition(new THREE11.Vector3()).toArray(),
|
|
143
|
+
bounds: bone ? null : bounds(new THREE11.Box3().setFromObject(object)),
|
|
111
144
|
matrix: object.matrixWorld.toArray(),
|
|
112
145
|
inverse: object.matrixWorld.clone().invert().toArray(),
|
|
113
146
|
morphNames: Object.keys(mesh.morphTargetDictionary ?? {})
|
|
@@ -121,7 +154,7 @@ var AnnotationModelObserver = class {
|
|
|
121
154
|
transforms(runtime) {
|
|
122
155
|
const ids = runtime.tracked_objects();
|
|
123
156
|
if (!ids.length) return new Float32Array();
|
|
124
|
-
this.
|
|
157
|
+
this.updateWorldMatrices();
|
|
125
158
|
const values = new Float32Array(ids.length * 17);
|
|
126
159
|
ids.forEach((id, index) => {
|
|
127
160
|
const object = this.objects.get(id);
|
|
@@ -132,16 +165,51 @@ var AnnotationModelObserver = class {
|
|
|
132
165
|
});
|
|
133
166
|
return values;
|
|
134
167
|
}
|
|
135
|
-
|
|
168
|
+
prepareMorphAnchors(runtime) {
|
|
169
|
+
const generation = runtime.generation();
|
|
170
|
+
if (!runtime.is_current(generation)) return;
|
|
171
|
+
if (this.morphGeneration !== generation) this.morphObservations = [];
|
|
172
|
+
this.morphGeneration = generation;
|
|
173
|
+
const changed = /* @__PURE__ */ new Set();
|
|
174
|
+
for (const mesh of this.meshes) {
|
|
175
|
+
const id = this.ids.get(mesh);
|
|
176
|
+
let observation = this.morphGeometry.get(id);
|
|
177
|
+
if (!observation) {
|
|
178
|
+
observation = new MorphGeometryObservation();
|
|
179
|
+
this.morphGeometry.set(id, observation);
|
|
180
|
+
}
|
|
181
|
+
observation.observe(mesh);
|
|
182
|
+
if (runtime.observe_morph_geometry(generation, id, observation.revision, observation.namesJson)) changed.add(id);
|
|
183
|
+
}
|
|
184
|
+
if (!changed.size) return;
|
|
185
|
+
this.morphObservations = this.morphObservations.filter((observation) => !changed.has(observation.objectId));
|
|
136
186
|
const requests = JSON.parse(runtime.command("morphRequests", "{}", performance.now()));
|
|
137
|
-
const anchors = {};
|
|
138
187
|
for (const request3 of requests) {
|
|
188
|
+
if (!changed.has(request3.objectId)) continue;
|
|
139
189
|
const mesh = this.objects.get(request3.objectId);
|
|
140
190
|
if (!mesh?.isMesh) continue;
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
191
|
+
const geometry = readMorphGeometry(mesh, request3.morphNames);
|
|
192
|
+
if (!geometry) continue;
|
|
193
|
+
const plan = runtime.prepare_morph_anchor(generation, request3.objectId, request3.region.name, geometry.base, geometry.morphs, geometry.relative);
|
|
194
|
+
if (plan.length < 2) continue;
|
|
195
|
+
const indices = plan.slice(1);
|
|
196
|
+
this.morphObservations.push({ planId: plan[0], objectId: request3.objectId, mesh, indices, samples: new Float32Array(indices.length * 3) });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Transport only: Rust owns the vertex selection, weights and live anchor. */
|
|
200
|
+
observeMorphAnchors(runtime) {
|
|
201
|
+
this.prepareMorphAnchors(runtime);
|
|
202
|
+
if (this.morphGeneration === null || !runtime.is_current(this.morphGeneration)) {
|
|
203
|
+
this.morphObservations = [];
|
|
204
|
+
this.morphGeneration = null;
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!this.morphObservations.length) return;
|
|
208
|
+
this.updateWorldMatrices();
|
|
209
|
+
for (const { planId, mesh, indices, samples } of this.morphObservations) {
|
|
210
|
+
sampleMorphVertices(mesh, indices, samples, this.vertex);
|
|
211
|
+
runtime.observe_morph_anchor(this.morphGeneration, planId, samples);
|
|
143
212
|
}
|
|
144
|
-
return anchors;
|
|
145
213
|
}
|
|
146
214
|
/** Rust selects influenced vertices; Three evaluates the current native pose. */
|
|
147
215
|
observeFocus(runtime, request3) {
|
|
@@ -162,7 +230,7 @@ var AnnotationModelObserver = class {
|
|
|
162
230
|
if (index < 0 || !skinIndices || !skinWeights) continue;
|
|
163
231
|
mesh.skeleton.update();
|
|
164
232
|
const selected = core.annotation_bone_vertices(readAttribute(skinIndices), readAttribute(skinWeights), new Uint32Array([index]));
|
|
165
|
-
const vertex = new
|
|
233
|
+
const vertex = new THREE11.Vector3();
|
|
166
234
|
for (const selectedIndex of selected) {
|
|
167
235
|
mesh.getVertexPosition(selectedIndex, vertex).applyMatrix4(mesh.matrixWorld);
|
|
168
236
|
points.push(vertex.x, vertex.y, vertex.z);
|
|
@@ -192,9 +260,9 @@ function bindSceneRendering(controller, scene, camera, binding) {
|
|
|
192
260
|
if (owner.scene !== scene || owner.camera !== camera) throw new Error("Rendering and camera controllers must share their scene and camera.");
|
|
193
261
|
if (owner.binding) throw new Error("Character scene rendering already has a camera controller.");
|
|
194
262
|
owner.binding = binding;
|
|
195
|
-
const
|
|
196
|
-
if (
|
|
197
|
-
if (
|
|
263
|
+
const snapshot3 = controller.getSnapshot();
|
|
264
|
+
if (snapshot3.renderer) void binding.commit(snapshot3.renderer).catch(binding.onError);
|
|
265
|
+
if (snapshot3.status !== "ready" && snapshot3.status !== "error") void binding.suspend().catch(binding.onError);
|
|
198
266
|
return () => {
|
|
199
267
|
if (owner.binding === binding) {
|
|
200
268
|
owner.drain = binding.suspend();
|
|
@@ -236,8 +304,8 @@ function prepareModelSkinning(model) {
|
|
|
236
304
|
};
|
|
237
305
|
}
|
|
238
306
|
var bindings = /* @__PURE__ */ new WeakMap();
|
|
239
|
-
function bindPlaybackRendering(model, mixer, afterFrame) {
|
|
240
|
-
const binding = { mixer, enabled: true, afterFrame };
|
|
307
|
+
function bindPlaybackRendering(model, mixer, afterFrame, afterSample) {
|
|
308
|
+
const binding = { mixer, enabled: true, disposed: false, afterFrame, afterSample };
|
|
241
309
|
const entries = bindings.get(model) ?? /* @__PURE__ */ new Set();
|
|
242
310
|
entries.add(binding);
|
|
243
311
|
bindings.set(model, entries);
|
|
@@ -246,6 +314,7 @@ function bindPlaybackRendering(model, mixer, afterFrame) {
|
|
|
246
314
|
binding.enabled = enabled;
|
|
247
315
|
},
|
|
248
316
|
dispose() {
|
|
317
|
+
binding.disposed = true;
|
|
249
318
|
entries.delete(binding);
|
|
250
319
|
if (!entries.size) bindings.delete(model);
|
|
251
320
|
}
|
|
@@ -262,8 +331,16 @@ function renderScenePlayback(scene, timer) {
|
|
|
262
331
|
}
|
|
263
332
|
}
|
|
264
333
|
});
|
|
265
|
-
|
|
266
|
-
|
|
334
|
+
const deltaSeconds = timer.getDelta();
|
|
335
|
+
const elapsed = /* @__PURE__ */ new Map();
|
|
336
|
+
for (const mixer of mixers) {
|
|
337
|
+
elapsed.set(mixer, Math.abs(deltaSeconds * mixer.timeScale));
|
|
338
|
+
mixer.update(deltaSeconds);
|
|
339
|
+
}
|
|
340
|
+
for (const binding of active) if (binding.enabled && !binding.disposed) {
|
|
341
|
+
binding.afterSample?.(elapsed.get(binding.mixer));
|
|
342
|
+
}
|
|
343
|
+
for (const binding of active) if (binding.enabled && !binding.disposed) binding.afterFrame(deltaSeconds);
|
|
267
344
|
}
|
|
268
345
|
function createScenePlaybackFrame(scene, document2) {
|
|
269
346
|
const timer = new Timer();
|
|
@@ -349,12 +426,12 @@ function compileModelForRender(renderer, scene, camera, model) {
|
|
|
349
426
|
});
|
|
350
427
|
}
|
|
351
428
|
function drawModelForRender(renderer, scene, camera, model) {
|
|
352
|
-
const viewport = renderer.getViewport(new
|
|
353
|
-
const scissor = renderer.getScissor(new
|
|
429
|
+
const viewport = renderer.getViewport(new THREE11.Vector4());
|
|
430
|
+
const scissor = renderer.getScissor(new THREE11.Vector4());
|
|
354
431
|
const scissorTest = renderer.getScissorTest();
|
|
355
432
|
const restoreRenderTarget = captureRenderTarget(renderer);
|
|
356
433
|
const autoClear = renderer.autoClear;
|
|
357
|
-
const clearColor = renderer.getClearColor(Object.assign(new
|
|
434
|
+
const clearColor = renderer.getClearColor(Object.assign(new THREE11.Color(), { a: 1 }));
|
|
358
435
|
const clearAlpha = renderer.getClearAlpha();
|
|
359
436
|
const native = "isWebGPURenderer" in renderer ? renderer : null;
|
|
360
437
|
const renderObject = native?.getRenderObjectFunction();
|
|
@@ -778,14 +855,14 @@ var CameraDOMControls = class {
|
|
|
778
855
|
this.wrapper.remove();
|
|
779
856
|
}
|
|
780
857
|
};
|
|
781
|
-
var ThreeAnnotationControls = class extends
|
|
858
|
+
var ThreeAnnotationControls = class extends THREE11.EventDispatcher {
|
|
782
859
|
constructor(domElement, runtime, advance, captureInput = true) {
|
|
783
860
|
super();
|
|
784
861
|
this.domElement = domElement;
|
|
785
862
|
this.runtime = runtime;
|
|
786
863
|
this.advance = advance;
|
|
787
864
|
this.captureInput = captureInput;
|
|
788
|
-
__publicField(this, "target", new
|
|
865
|
+
__publicField(this, "target", new THREE11.Vector3());
|
|
789
866
|
__publicField(this, "disposed", false);
|
|
790
867
|
__publicField(this, "previousTouchAction", "");
|
|
791
868
|
__publicField(this, "connected", false);
|
|
@@ -928,12 +1005,12 @@ var ThreeAnnotationRenderer = class {
|
|
|
928
1005
|
this.domElement = domElement;
|
|
929
1006
|
this.onPick = onPick;
|
|
930
1007
|
this.onHover = onHover;
|
|
931
|
-
__publicField(this, "group", new
|
|
1008
|
+
__publicField(this, "group", new THREE11.Group());
|
|
932
1009
|
__publicField(this, "objects", /* @__PURE__ */ new Map());
|
|
933
1010
|
__publicField(this, "overlay");
|
|
934
|
-
__publicField(this, "raycaster", new
|
|
935
|
-
__publicField(this, "pointer", new
|
|
936
|
-
__publicField(this, "viewProjection", new
|
|
1011
|
+
__publicField(this, "raycaster", new THREE11.Raycaster());
|
|
1012
|
+
__publicField(this, "pointer", new THREE11.Vector2());
|
|
1013
|
+
__publicField(this, "viewProjection", new THREE11.Matrix4());
|
|
937
1014
|
__publicField(this, "overlayParent", null);
|
|
938
1015
|
__publicField(this, "releaseParentPosition", null);
|
|
939
1016
|
__publicField(this, "overlayResizeObserver");
|
|
@@ -991,28 +1068,28 @@ var ThreeAnnotationRenderer = class {
|
|
|
991
1068
|
this.attachOverlay();
|
|
992
1069
|
element.addEventListener("click", this.click);
|
|
993
1070
|
}
|
|
994
|
-
sync(
|
|
995
|
-
if (this.revision ===
|
|
996
|
-
if (this.style !==
|
|
997
|
-
this.style =
|
|
998
|
-
const ids = new Set(
|
|
1071
|
+
sync(snapshot3) {
|
|
1072
|
+
if (this.revision === snapshot3.descriptorRevision && this.style === snapshot3.style) return;
|
|
1073
|
+
if (this.style !== snapshot3.style) this.clear();
|
|
1074
|
+
this.style = snapshot3.style;
|
|
1075
|
+
const ids = new Set(snapshot3.descriptors.map((descriptor) => descriptor.id));
|
|
999
1076
|
for (const id of this.objects.keys()) if (!ids.has(id)) this.remove(id);
|
|
1000
|
-
for (const descriptor of
|
|
1077
|
+
for (const descriptor of snapshot3.descriptors) {
|
|
1001
1078
|
const signature = JSON.stringify(descriptor);
|
|
1002
1079
|
if (this.objects.get(descriptor.id)?.descriptor === signature) continue;
|
|
1003
1080
|
this.remove(descriptor.id);
|
|
1004
1081
|
this.objects.set(descriptor.id, this.create(descriptor, signature));
|
|
1005
1082
|
}
|
|
1006
|
-
this.revision =
|
|
1083
|
+
this.revision = snapshot3.descriptorRevision;
|
|
1007
1084
|
}
|
|
1008
1085
|
create(descriptor, signature) {
|
|
1009
1086
|
const { style } = descriptor;
|
|
1010
|
-
const sphere = new
|
|
1087
|
+
const sphere = new THREE11.Mesh(new THREE11.SphereGeometry(descriptor.radius, 12, 12), new THREE11.MeshBasicMaterial({ color: style.markerColor, transparent: true }));
|
|
1011
1088
|
const dashed = style.line.style === "dashed" || style.line.style === "dotted";
|
|
1012
|
-
const material = dashed ? new
|
|
1013
|
-
const geometry = new
|
|
1014
|
-
geometry.setAttribute("position", new
|
|
1015
|
-
const line = new
|
|
1089
|
+
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 });
|
|
1090
|
+
const geometry = new THREE11.BufferGeometry();
|
|
1091
|
+
geometry.setAttribute("position", new THREE11.BufferAttribute(new Float32Array(51), 3));
|
|
1092
|
+
const line = new THREE11.Line(geometry, material);
|
|
1016
1093
|
const canvas = document.createElement("canvas");
|
|
1017
1094
|
const context = canvas.getContext("2d");
|
|
1018
1095
|
if (!context) {
|
|
@@ -1033,9 +1110,9 @@ var ThreeAnnotationRenderer = class {
|
|
|
1033
1110
|
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
1034
1111
|
context.fillStyle = style.labelColor;
|
|
1035
1112
|
context.fillText(descriptor.label, canvas.width / 2, canvas.height / 2);
|
|
1036
|
-
const texture = new
|
|
1037
|
-
const label = new
|
|
1038
|
-
const arrow = style.line.arrowHead ? new
|
|
1113
|
+
const texture = new THREE11.CanvasTexture(canvas);
|
|
1114
|
+
const label = new THREE11.Sprite(new THREE11.SpriteMaterial({ map: texture, transparent: true, depthTest: true, depthWrite: false, sizeAttenuation: false }));
|
|
1115
|
+
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;
|
|
1039
1116
|
let element = null;
|
|
1040
1117
|
if (this.style === "html") {
|
|
1041
1118
|
element = document.createElement("div");
|
|
@@ -1070,7 +1147,7 @@ var ThreeAnnotationRenderer = class {
|
|
|
1070
1147
|
const rays = this.runtime.occlusion_queries();
|
|
1071
1148
|
const hits = new Float32Array(rays.length / 8 * 2);
|
|
1072
1149
|
for (let offset = 0; offset < rays.length; offset += 8) {
|
|
1073
|
-
this.raycaster.set(new
|
|
1150
|
+
this.raycaster.set(new THREE11.Vector3().fromArray(rays, offset + 1), new THREE11.Vector3().fromArray(rays, offset + 4));
|
|
1074
1151
|
this.raycaster.near = 0;
|
|
1075
1152
|
this.raycaster.far = rays[offset + 7];
|
|
1076
1153
|
const intersection = this.raycaster.intersectObjects(this.meshes, false)[0];
|
|
@@ -1097,7 +1174,7 @@ var ThreeAnnotationRenderer = class {
|
|
|
1097
1174
|
positions.array.set(values.subarray(offset + 19, offset + 70));
|
|
1098
1175
|
positions.needsUpdate = true;
|
|
1099
1176
|
objects.line.geometry.computeBoundingSphere();
|
|
1100
|
-
if (objects.line.material instanceof
|
|
1177
|
+
if (objects.line.material instanceof THREE11.LineDashedMaterial) objects.line.computeLineDistances();
|
|
1101
1178
|
objects.label.position.fromArray(values, offset + 5);
|
|
1102
1179
|
objects.label.scale.set(values[offset + 9], values[offset + 10], 1);
|
|
1103
1180
|
objects.label.material.opacity = values[offset + 11];
|
|
@@ -1220,7 +1297,7 @@ var ThreeAnnotationController = class _ThreeAnnotationController {
|
|
|
1220
1297
|
__publicField(this, "markerStateListeners", /* @__PURE__ */ new Set());
|
|
1221
1298
|
__publicField(this, "regionListeners", /* @__PURE__ */ new Set());
|
|
1222
1299
|
__publicField(this, "renderer", null);
|
|
1223
|
-
__publicField(this, "playbackTimer", new
|
|
1300
|
+
__publicField(this, "playbackTimer", new THREE11.Timer());
|
|
1224
1301
|
__publicField(this, "renderFrame");
|
|
1225
1302
|
__publicField(this, "resizeRenderer");
|
|
1226
1303
|
__publicField(this, "onRenderError");
|
|
@@ -1248,7 +1325,7 @@ var ThreeAnnotationController = class _ThreeAnnotationController {
|
|
|
1248
1325
|
}, nativeOptions.captureInput);
|
|
1249
1326
|
this.state = this.readState();
|
|
1250
1327
|
if (this.manageCamera) this.applyCameraState(this.state.camera);
|
|
1251
|
-
else this.controls.target.copy(this.camera.getWorldDirection(new
|
|
1328
|
+
else this.controls.target.copy(this.camera.getWorldDirection(new THREE11.Vector3()).add(this.camera.position));
|
|
1252
1329
|
this.markerRenderer = new ThreeAnnotationRenderer(this.runtime, this.scene, this.camera, this.domElement, (ids) => this.pick(ids), (id) => {
|
|
1253
1330
|
if (!this.disposed) {
|
|
1254
1331
|
this.call("hover", { id });
|
|
@@ -1451,11 +1528,12 @@ var ThreeAnnotationController = class _ThreeAnnotationController {
|
|
|
1451
1528
|
const observer = this.observer;
|
|
1452
1529
|
const generation = this.runtime.generation();
|
|
1453
1530
|
observer.observe(this.runtime);
|
|
1454
|
-
|
|
1531
|
+
observer.observeMorphAnchors(this.runtime);
|
|
1532
|
+
const queries = JSON.parse(this.runtime.plan_markers("{}"));
|
|
1455
1533
|
for (const initial of queries) {
|
|
1456
1534
|
let query = initial;
|
|
1457
1535
|
while (query && this.isCurrent(generation)) {
|
|
1458
|
-
const raycaster = new
|
|
1536
|
+
const raycaster = new THREE11.Raycaster(new THREE11.Vector3().fromArray(query.origin), new THREE11.Vector3().fromArray(query.direction), 0, query.far);
|
|
1459
1537
|
const hits = sliceSurfaceQueries ? await intersectMeshesInSlices(raycaster, observer.meshes, { isCurrent: () => this.isCurrent(generation), yieldToPaint: waitForCharacterLoadPaint }) : raycaster.intersectObjects(observer.meshes, false);
|
|
1460
1538
|
if (!hits || !this.isCurrent(generation)) return;
|
|
1461
1539
|
query = JSON.parse(this.runtime.surface_result(query.generation, query.id, query.phase, new Float32Array(hits.flatMap((hit) => hit.point.toArray()))));
|
|
@@ -1566,6 +1644,7 @@ var ThreeAnnotationController = class _ThreeAnnotationController {
|
|
|
1566
1644
|
if (this.disposed) return;
|
|
1567
1645
|
this.cancelModelPreparation();
|
|
1568
1646
|
this.call("clear");
|
|
1647
|
+
this.observer?.observeMorphAnchors(this.runtime);
|
|
1569
1648
|
this.hostConfig = null;
|
|
1570
1649
|
this.configSnapshot = null;
|
|
1571
1650
|
this.configSnapshotHost = null;
|
|
@@ -1679,7 +1758,10 @@ var ThreeAnnotationController = class _ThreeAnnotationController {
|
|
|
1679
1758
|
this.controls.target.fromArray(pose, 3);
|
|
1680
1759
|
this.camera.lookAt(this.controls.target);
|
|
1681
1760
|
}
|
|
1682
|
-
if (this.observer)
|
|
1761
|
+
if (this.observer) {
|
|
1762
|
+
this.runtime.observe_transforms(this.observer.transforms(this.runtime));
|
|
1763
|
+
this.observer.observeMorphAnchors(this.runtime);
|
|
1764
|
+
}
|
|
1683
1765
|
this.sync();
|
|
1684
1766
|
this.markerRenderer.update(now);
|
|
1685
1767
|
this.settleFlights();
|
|
@@ -1937,7 +2019,7 @@ var ThreeAnnotationMarkers = class {
|
|
|
1937
2019
|
}
|
|
1938
2020
|
getMarkerPosition(name) {
|
|
1939
2021
|
const p = this.controller.getMarkerPosition(name);
|
|
1940
|
-
return p ? new
|
|
2022
|
+
return p ? new THREE11.Vector3(p.x, p.y, p.z) : null;
|
|
1941
2023
|
}
|
|
1942
2024
|
updateLineStyle(name, line) {
|
|
1943
2025
|
this.controller.setRegionLineStyle(name, line);
|
|
@@ -2112,11 +2194,35 @@ function shouldUseFaceCenterForMarkerAnchor(resolved) {
|
|
|
2112
2194
|
function shouldProjectMarkerAnchorToSurface(resolved, legacyDefault) {
|
|
2113
2195
|
return annotationQuery("projectAnchor", { resolved, legacyDefault });
|
|
2114
2196
|
}
|
|
2197
|
+
function getRuntimeAnnotationSide(region) {
|
|
2198
|
+
return annotationQuery("side", { region });
|
|
2199
|
+
}
|
|
2200
|
+
function getRuntimeAUMeshSideOffset(region, sideSign, modelSize) {
|
|
2201
|
+
const value = annotationQuery("meshSideOffset", { region, sideSign, size: modelSize });
|
|
2202
|
+
return value ? new THREE11.Vector3(value.x, value.y, value.z) : null;
|
|
2203
|
+
}
|
|
2204
|
+
function getRuntimeAUMorphAnchorPoint(region, mesh) {
|
|
2205
|
+
const core = requireInitializedEmbodyCore();
|
|
2206
|
+
const names = annotationQuery("morphNames", { region });
|
|
2207
|
+
const geometry = readMorphGeometry(mesh, names);
|
|
2208
|
+
if (!geometry) return null;
|
|
2209
|
+
const candidates = core.annotation_morph_anchor_candidates(JSON.stringify(region), geometry.base, geometry.morphs, geometry.relative);
|
|
2210
|
+
updateAnnotationWorldMatrices(mesh);
|
|
2211
|
+
const positions = new Float32Array(candidates.length / 2 * 3);
|
|
2212
|
+
sampleMorphVertices(mesh, candidates.filter((_value, index) => index % 2 === 0), positions);
|
|
2213
|
+
const samples = new Float32Array(candidates.length / 2 * 4);
|
|
2214
|
+
for (let index = 0; index < candidates.length / 2; index++) {
|
|
2215
|
+
samples.set(positions.subarray(index * 3, index * 3 + 3), index * 4);
|
|
2216
|
+
samples[index * 4 + 3] = candidates[index * 2 + 1];
|
|
2217
|
+
}
|
|
2218
|
+
const value = core.annotation_weighted_anchor(samples);
|
|
2219
|
+
return value.length === 3 ? new THREE11.Vector3().fromArray(value) : null;
|
|
2220
|
+
}
|
|
2115
2221
|
var AnnotationLifecycleBridge = class {
|
|
2116
|
-
constructor(mode, duration,
|
|
2222
|
+
constructor(mode, duration, apply2) {
|
|
2117
2223
|
this.mode = mode;
|
|
2118
2224
|
this.duration = duration;
|
|
2119
|
-
this.apply =
|
|
2225
|
+
this.apply = apply2;
|
|
2120
2226
|
__publicField(this, "readiness", Promise.resolve());
|
|
2121
2227
|
__publicField(this, "initializing", null);
|
|
2122
2228
|
__publicField(this, "core", null);
|
|
@@ -2258,110 +2364,6 @@ function boneResolutionProfile(profile) {
|
|
|
2258
2364
|
suffixPattern: profile.suffixPattern
|
|
2259
2365
|
};
|
|
2260
2366
|
}
|
|
2261
|
-
var json = (value) => JSON.stringify(value ?? {});
|
|
2262
|
-
function request2(op, payload = {}) {
|
|
2263
|
-
return JSON.parse(requireInitializedEmbodyCore().embody_request(json({ op, payload })));
|
|
2264
|
-
}
|
|
2265
|
-
var getPreset = (id) => request2("preset.get", { id });
|
|
2266
|
-
var extendCharacterConfigWithPreset = (config) => request2("profile.extendConfig", { config });
|
|
2267
|
-
var extractProfileOverrides = (config) => request2("profile.extractOverrides", { config });
|
|
2268
|
-
var mergeCharacterRegionsByName = (base, override) => request2("profile.mergeRegions", { base, override });
|
|
2269
|
-
var getMeshNamesForAUProfile = (profile, auId) => request2("profile.getMeshNamesForAU", { profile, auId });
|
|
2270
|
-
var resolveMorphMeshSelections = (profile, categories, availableNames) => request2("profile.resolveMorphMeshSelections", { profile, categories, availableNames });
|
|
2271
|
-
var getHumanoidSpecification = () => request2("humanoid.getSpecification");
|
|
2272
|
-
var setAUMorphTargets = (profile, auId, side, targets) => request2("profile.setAUMorphTargets", { profile, auId, side, targets });
|
|
2273
|
-
var setHumanoidRoleBinding = (profile, role, boneName) => request2("profile.setHumanoidRoleBinding", { profile, role, boneName });
|
|
2274
|
-
var resolveHumanoidCharacterization = (profile) => request2("profile.resolveHumanoidCharacterization", { profile });
|
|
2275
|
-
var hasLeftRightMorphs = (profile, auId) => request2("profile.hasLeftRightMorphs", { profile, auId });
|
|
2276
|
-
var isMixedAU = (profile, auId) => request2("profile.isMixedAU", { profile, auId });
|
|
2277
|
-
var resolveVisemeProfile = (profile) => request2("profile.resolveVisemes", { profile });
|
|
2278
|
-
var isMaxDegreesOnlyAxisBindingUpdate = (update) => request2("bone.isMaxDegreesOnlyUpdate", { update });
|
|
2279
|
-
var findNodeKeyForBone = (boneName, profile) => profile ? request2("bone.findNodeKey", { profile, boneName }) : null;
|
|
2280
|
-
var ensureBoneNodeKey = (profile, boneName) => request2("bone.ensureNodeKey", { profile, boneName });
|
|
2281
|
-
var ensureBilateralBoneNodeKeys = (profile, leftBoneName, rightBoneName) => request2("bone.ensureBilateralNodeKeys", { profile, leftBoneName, rightBoneName });
|
|
2282
|
-
var resolveBoneNameForNodeKey = (profile, nodeKey) => request2("bone.resolveBoneName", { profile, nodeKey });
|
|
2283
|
-
var stripConfiguredBoneAffixes = (profile, boneName) => request2("bone.stripAffixes", { profile: profile ?? {}, boneName });
|
|
2284
|
-
var inferChiralBoneNamePair = (boneNames, selectedBoneName) => request2("bone.inferChiralPair", { boneNames, selectedBoneName });
|
|
2285
|
-
var formatAxisLabel = (axis) => request2("bone.formatAxisLabel", { axis });
|
|
2286
|
-
var formatAxisDirectionLabel = (axis, direction) => request2("bone.formatDirectionLabel", { axis, direction });
|
|
2287
|
-
var getAxisFromChannel = (channel) => channel ? request2("bone.getAxisFromChannel", { channel }) : null;
|
|
2288
|
-
var getBoneAxisBindingState = (profile, boneName, axis) => request2("bone.getAxisState", { profile, boneName, axis });
|
|
2289
|
-
var resolveBoneAxisChannel = (profile, boneName, axis) => request2("bone.resolveAxisChannel", { profile, boneName, axis });
|
|
2290
|
-
var getBilateralAxisBindingState = (profile, boneName, axis) => request2("bone.getBilateralAxisState", { profile, boneName, axis });
|
|
2291
|
-
var getAUBoneBindingState = (profile, auId) => request2("bone.getAUBindingState", { profile, auId });
|
|
2292
|
-
var getAUBoneBindingStates = (profile, auIds) => request2("bone.getAUBindingStates", {
|
|
2293
|
-
profile: {
|
|
2294
|
-
auToBones: profile.auToBones,
|
|
2295
|
-
boneNodes: profile.boneNodes,
|
|
2296
|
-
bonePrefix: profile.bonePrefix,
|
|
2297
|
-
boneSuffix: profile.boneSuffix,
|
|
2298
|
-
suffixPattern: profile.suffixPattern,
|
|
2299
|
-
compositeRotations: profile.compositeRotations,
|
|
2300
|
-
continuumPairs: profile.continuumPairs,
|
|
2301
|
-
humanoidCharacterization: profile.humanoidCharacterization
|
|
2302
|
-
},
|
|
2303
|
-
auIds
|
|
2304
|
-
});
|
|
2305
|
-
var inferEyeControlScope = (profile, binding) => request2("bone.inferEyeScope", { profile, binding });
|
|
2306
|
-
var inferEyeControlFamily = (binding) => request2("bone.inferEyeFamily", { binding });
|
|
2307
|
-
var applyBoneAxisBindingUpdate = (profile, boneName, axis, update) => request2("bone.applyAxisUpdate", { profile, boneName, axis, update });
|
|
2308
|
-
var applyBilateralAxisBindingUpdate = (profile, boneName, axis, scope, update) => request2("bone.applyBilateralAxisUpdate", { profile, boneName, axis, scope, update });
|
|
2309
|
-
var applyAUBoneBindingUpdate = (profile, auId, update) => request2("bone.applyAUBindingUpdate", { profile, auId, update });
|
|
2310
|
-
var createBoneAxisAu = (profile, boneName, axis, direction, options = {}) => request2("bone.createAxisAU", { profile, boneName, axis, direction, name: options.name });
|
|
2311
|
-
var createBilateralBoneAxisAu = (profile, boneName, axis, scope, direction, options = {}) => request2("bone.createBilateralAxisAU", { profile, boneName, axis, scope, direction, name: options.name });
|
|
2312
|
-
var buildBoneAuOptions = (profile) => request2("bone.buildAUOptions", { profile });
|
|
2313
|
-
var isJointControlAuInfo = (info) => request2("bone.isJointControlInfo", { info });
|
|
2314
|
-
var classifyAuAsJointControl = (profile, auId, options = {}) => request2("bone.classifyJointControl", { profile, auId, boneName: options.boneName, nodeKey: options.nodeKey });
|
|
2315
|
-
var resolveContinuumDisplayLabel = (profile, negativeAuId, positiveAuId) => request2("bone.resolveContinuumLabel", { profile, negativeAuId, positiveAuId }) ?? `AU ${negativeAuId} <-> AU ${positiveAuId}`;
|
|
2316
|
-
function inspectModel(model, meshes, profile) {
|
|
2317
|
-
return new ThreeModelInspector().inspectModel(model, { meshes, profile });
|
|
2318
|
-
}
|
|
2319
|
-
function extractModelData(model, meshes, animations = []) {
|
|
2320
|
-
const inspection = inspectModel(model, meshes);
|
|
2321
|
-
return JSON.parse(requireInitializedEmbodyCore().extract_model_data_json(json(inspection.descriptor), json(serializeAnimationClips(model, animations, inspection))));
|
|
2322
|
-
}
|
|
2323
|
-
async function analyzeModel(options) {
|
|
2324
|
-
const source = options.source;
|
|
2325
|
-
const model = source.type === "gltf" ? source.gltf.scene ?? source.gltf.scenes[0] : source.model;
|
|
2326
|
-
const animations = source.type === "gltf" ? source.gltf.animations : source.animations ?? [];
|
|
2327
|
-
const inspection = inspectModel(model, source.type === "runtime" ? source.meshes : void 0);
|
|
2328
|
-
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) })));
|
|
2329
|
-
}
|
|
2330
|
-
function validateMappings(meshes, skeleton, profile, options = {}) {
|
|
2331
|
-
let nextMorphId = 1;
|
|
2332
|
-
const morphTargets = [];
|
|
2333
|
-
const descriptorMeshes = (meshes ?? []).map((value, index) => {
|
|
2334
|
-
const mesh = value;
|
|
2335
|
-
const id = index + 1;
|
|
2336
|
-
const morphTargetIds = Object.entries(mesh.morphTargetDictionary ?? mesh.geometry?.morphTargetDictionary ?? {}).map(([name, hostIndex]) => {
|
|
2337
|
-
const morphId = nextMorphId++;
|
|
2338
|
-
morphTargets.push({ id: morphId, meshId: id, name, hostIndex });
|
|
2339
|
-
return morphId;
|
|
2340
|
-
});
|
|
2341
|
-
return { id, name: mesh.name ?? "", morphTargetIds, visible: true };
|
|
2342
|
-
});
|
|
2343
|
-
const bones = (skeleton?.bones ?? []).map((bone, index) => ({ id: index + 1, name: typeof bone === "string" ? bone : bone.name ?? "" }));
|
|
2344
|
-
return JSON.parse(requireInitializedEmbodyCore().validate_profile_model(json(profile), json({ meshes: descriptorMeshes, morphTargets, bones, objects: [] }), json(options)));
|
|
2345
|
-
}
|
|
2346
|
-
function validateHumanoidCharacterization(model, profile) {
|
|
2347
|
-
return JSON.parse(requireInitializedEmbodyCore().validate_humanoid_characterization(json(profile), json(inspectModel(model, void 0, profile).descriptor)));
|
|
2348
|
-
}
|
|
2349
|
-
function normalizeHairColorAppearance(value, fallback = DEFAULT_HAIR_COLOR_APPEARANCE) {
|
|
2350
|
-
return JSON.parse(requireInitializedEmbodyCore().normalize_hair_color_appearance_json(json(value), json(fallback)));
|
|
2351
|
-
}
|
|
2352
|
-
var getHumanoidSkeletonTemplate = (id) => HUMANOID_SKELETON_TEMPLATES.find((template) => template.id === id) ?? null;
|
|
2353
|
-
function getHumanoidSkeletonTemplateBoneNames(template = CC4_HUMANOID_SKELETON_TEMPLATE) {
|
|
2354
|
-
return JSON.parse(requireInitializedEmbodyCore().humanoid_skeleton_template_bone_names(json(template)));
|
|
2355
|
-
}
|
|
2356
|
-
function createValidationSkeletonFromHumanoidTemplate(template = CC4_HUMANOID_SKELETON_TEMPLATE) {
|
|
2357
|
-
return JSON.parse(requireInitializedEmbodyCore().create_validation_skeleton_json(json(template)));
|
|
2358
|
-
}
|
|
2359
|
-
function computeHumanoidSkeletonTemplateRestBounds(template) {
|
|
2360
|
-
return Array.from(requireInitializedEmbodyCore().compute_humanoid_skeleton_template_rest_bounds(json(template)));
|
|
2361
|
-
}
|
|
2362
|
-
function extractHumanoidSkeletonTemplateFromModel(model, options) {
|
|
2363
|
-
return JSON.parse(requireInitializedEmbodyCore().extract_humanoid_skeleton_template_json(json(inspectModel(model).descriptor), json(options)));
|
|
2364
|
-
}
|
|
2365
2367
|
var isBone = (object) => !!(object.isBone || object.type === "Bone");
|
|
2366
2368
|
var finite = (values, description) => {
|
|
2367
2369
|
if (!values.every(Number.isFinite)) {
|
|
@@ -2486,6 +2488,286 @@ function bindModelReferencePose(model, referencePose) {
|
|
|
2486
2488
|
if (index !== referencePose.nodes.length) throw new Error("Incompatible reference pose hierarchy: extra captured nodes");
|
|
2487
2489
|
return bindings2;
|
|
2488
2490
|
}
|
|
2491
|
+
|
|
2492
|
+
// three/vrm-animation.ts
|
|
2493
|
+
var MAX_BYTES = 64 * 1024 * 1024;
|
|
2494
|
+
var identity = new Matrix4();
|
|
2495
|
+
var isBone2 = (node) => node.isBone || node.type === "Bone";
|
|
2496
|
+
function decomposeReference(matrix, name) {
|
|
2497
|
+
const position = new Vector3();
|
|
2498
|
+
const rotation = new Quaternion();
|
|
2499
|
+
const scale = new Vector3();
|
|
2500
|
+
if (!matrix.elements.every(Number.isFinite) || Math.abs(matrix.determinant()) < 1e-20) {
|
|
2501
|
+
throw new Error(`Invalid reference matrix for ${name}`);
|
|
2502
|
+
}
|
|
2503
|
+
matrix.decompose(position, rotation, scale);
|
|
2504
|
+
const reconstructed = new Matrix4().compose(position, rotation, scale);
|
|
2505
|
+
if (matrix.elements.some((v, i) => Math.abs(v - reconstructed.elements[i]) > 1e-5 * Math.max(1, Math.abs(v)))) {
|
|
2506
|
+
throw new Error(`Reference ${name} contains shear, which VRMA retargeting does not support`);
|
|
2507
|
+
}
|
|
2508
|
+
return { translation: position.toArray(), rotation: rotation.toArray(), scale: scale.toArray() };
|
|
2509
|
+
}
|
|
2510
|
+
function objectsInModel(model) {
|
|
2511
|
+
const all = [];
|
|
2512
|
+
model.traverse((node) => all.push(node));
|
|
2513
|
+
const needed = /* @__PURE__ */ new Set([model]);
|
|
2514
|
+
for (const bone of all.filter(isBone2)) {
|
|
2515
|
+
for (let node = bone; node && node !== model; node = node.parent) needed.add(node);
|
|
2516
|
+
}
|
|
2517
|
+
const objects = all.filter((node) => needed.has(node));
|
|
2518
|
+
if (objects.length > 4096) throw new Error("Animation rig exceeds 4096 nodes");
|
|
2519
|
+
const ids = new Map(objects.map((node, i) => [node, String(i)]));
|
|
2520
|
+
return { objects, ids };
|
|
2521
|
+
}
|
|
2522
|
+
function inspectReference(model, referencePose, metersPerUnit, includeRoot = false) {
|
|
2523
|
+
const bindings2 = bindModelReferencePose(model, referencePose);
|
|
2524
|
+
const { objects, ids } = objectsInModel(model);
|
|
2525
|
+
const nodes = objects.map((node) => ({
|
|
2526
|
+
id: ids.get(node),
|
|
2527
|
+
name: node.name,
|
|
2528
|
+
parent: node === model ? null : ids.get(node.parent),
|
|
2529
|
+
// Placement of the model in a scene is not part of its humanoid coordinate system.
|
|
2530
|
+
...decomposeReference(node === model && !includeRoot ? identity : new Matrix4().fromArray(bindings2.get(node).localMatrix), node.name)
|
|
2531
|
+
}));
|
|
2532
|
+
return { rig: { metersPerUnit, nodes, humanoidBones: {} }, objects, ids };
|
|
2533
|
+
}
|
|
2534
|
+
function inspectSkinBind(model, metersPerUnit) {
|
|
2535
|
+
if (model.parent) throw new Error("Use an explicit referencePose for a parented source model");
|
|
2536
|
+
const initial = captureModelReferencePose(model);
|
|
2537
|
+
const initialBindings = bindModelReferencePose(model, initial);
|
|
2538
|
+
const { objects, ids } = objectsInModel(model);
|
|
2539
|
+
const world = /* @__PURE__ */ new Map();
|
|
2540
|
+
const all = [];
|
|
2541
|
+
model.traverse((node) => all.push(node));
|
|
2542
|
+
for (const node of all) {
|
|
2543
|
+
const mesh = node;
|
|
2544
|
+
if (!mesh.isSkinnedMesh) continue;
|
|
2545
|
+
const { bones, boneInverses } = mesh.skeleton;
|
|
2546
|
+
if (bones.length !== boneInverses.length) throw new Error("Invalid skin bind inverse count");
|
|
2547
|
+
bones.forEach((bone, index) => {
|
|
2548
|
+
const inverse = boneInverses[index];
|
|
2549
|
+
if (Math.abs(inverse.determinant()) < 1e-20) throw new Error(`Singular bind matrix for ${bone.name}`);
|
|
2550
|
+
const bind = inverse.clone().invert();
|
|
2551
|
+
const prior = world.get(bone);
|
|
2552
|
+
if (prior && prior.elements.some((value, i) => Math.abs(value - bind.elements[i]) > 1e-4)) {
|
|
2553
|
+
throw new Error(`Conflicting skin bind matrices for ${bone.name}`);
|
|
2554
|
+
}
|
|
2555
|
+
world.set(bone, bind);
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
const nodes = objects.map((node) => {
|
|
2559
|
+
if (isBone2(node) && !world.has(node)) {
|
|
2560
|
+
throw new Error(`No skin bind reference for ${node.name}; supply an explicit authored T-pose reference`);
|
|
2561
|
+
}
|
|
2562
|
+
const nodeWorld = world.get(node) ?? new Matrix4().fromArray(initialBindings.get(node).worldMatrix);
|
|
2563
|
+
const parentWorld = node.parent ? world.get(node.parent) ?? new Matrix4().fromArray(initialBindings.get(node.parent).worldMatrix) : identity;
|
|
2564
|
+
const local = parentWorld.clone().invert().multiply(nodeWorld);
|
|
2565
|
+
return { id: ids.get(node), name: node.name, parent: node === model ? null : ids.get(node.parent), ...decomposeReference(local, node.name) };
|
|
2566
|
+
});
|
|
2567
|
+
return { rig: { metersPerUnit, nodes, humanoidBones: {} }, objects, ids };
|
|
2568
|
+
}
|
|
2569
|
+
function uniqueBone(objects, name) {
|
|
2570
|
+
const matches = objects.filter((node) => isBone2(node) && (node.name === name || node.uuid === name));
|
|
2571
|
+
if (matches.length !== 1) throw new Error(`Animation bone ${name} must resolve uniquely (found ${matches.length})`);
|
|
2572
|
+
return matches[0];
|
|
2573
|
+
}
|
|
2574
|
+
function convertMixamoToVrmAnimation(source, clip, options) {
|
|
2575
|
+
const core = requireInitializedEmbodyCore();
|
|
2576
|
+
if (clip.blendMode !== NormalAnimationBlendMode) {
|
|
2577
|
+
throw new Error("VRMA conversion requires absolute local animation keys; additive clips are unsupported");
|
|
2578
|
+
}
|
|
2579
|
+
const { rig, objects, ids } = options.referencePose ? inspectReference(source, options.referencePose, options.metersPerUnit, true) : inspectSkinBind(source, options.metersPerUnit);
|
|
2580
|
+
const tracks = clip.tracks.map((track) => {
|
|
2581
|
+
const match = /^(.*)\.(quaternion|position|scale)$/.exec(track.name);
|
|
2582
|
+
if (!match) throw new Error(`Unsupported Mixamo track ${track.name}`);
|
|
2583
|
+
const bone = uniqueBone(objects, match[1]);
|
|
2584
|
+
const interpolation = track.getInterpolation();
|
|
2585
|
+
if (interpolation !== InterpolateLinear && interpolation !== InterpolateDiscrete) {
|
|
2586
|
+
throw new Error(`Unsupported interpolation on ${track.name}`);
|
|
2587
|
+
}
|
|
2588
|
+
return {
|
|
2589
|
+
node: ids.get(bone),
|
|
2590
|
+
path: match[2] === "quaternion" ? "rotation" : match[2] === "position" ? "translation" : "scale",
|
|
2591
|
+
interpolation: interpolation === InterpolateDiscrete ? "STEP" : "LINEAR",
|
|
2592
|
+
times: Array.from(track.times),
|
|
2593
|
+
values: Array.from(track.values)
|
|
2594
|
+
};
|
|
2595
|
+
});
|
|
2596
|
+
const normalized = core.normalize_mixamo_animation(JSON.stringify({ version: 1, name: clip.name, durationSeconds: clip.duration, rig, tracks }));
|
|
2597
|
+
return core.encode_vrma_animation(normalized);
|
|
2598
|
+
}
|
|
2599
|
+
function inspectVrmAnimation(bytes) {
|
|
2600
|
+
if (bytes.byteLength > MAX_BYTES) throw new Error("VRMA exceeds 64 MiB");
|
|
2601
|
+
return JSON.parse(requireInitializedEmbodyCore().decode_vrma_animation(bytes));
|
|
2602
|
+
}
|
|
2603
|
+
function createAnimationClipFromVrmAnimation(bytes, target, options) {
|
|
2604
|
+
const core = requireInitializedEmbodyCore();
|
|
2605
|
+
const document2 = inspectVrmAnimation(bytes);
|
|
2606
|
+
const { rig, objects, ids } = inspectReference(target, options.referencePose, options.metersPerUnit);
|
|
2607
|
+
let humanoidBones = options.humanoidBones;
|
|
2608
|
+
if (!humanoidBones) {
|
|
2609
|
+
if (!options.profile) throw new Error("VRMA target requires a characterized profile or explicit humanoidBones");
|
|
2610
|
+
const inspection = new ThreeModelInspector().inspectModel(target, { referencePose: options.referencePose });
|
|
2611
|
+
const result = JSON.parse(core.validate_humanoid_characterization(JSON.stringify(options.profile), JSON.stringify(inspection.descriptor)));
|
|
2612
|
+
if (!result.valid) throw new Error(`Cannot characterize VRMA target: ${result.errors.join("; ")}`);
|
|
2613
|
+
humanoidBones = Object.fromEntries(Object.entries(result.roles).map(([role, binding]) => [role, binding.boneName]));
|
|
2614
|
+
}
|
|
2615
|
+
for (const [role, name] of Object.entries(humanoidBones)) {
|
|
2616
|
+
if (role !== "leftEye" && role !== "rightEye") rig.humanoidBones[role] = ids.get(uniqueBone(objects, name));
|
|
2617
|
+
}
|
|
2618
|
+
const retargeted = JSON.parse(core.retarget_vrma_animation(JSON.stringify(document2), JSON.stringify(rig)));
|
|
2619
|
+
const byId = new Map(objects.map((node) => [ids.get(node), node]));
|
|
2620
|
+
const tracks = retargeted.tracks.map((track) => {
|
|
2621
|
+
const node = byId.get(track.node);
|
|
2622
|
+
if (!node) throw new Error(`Retargeted node ${track.node} is not in the target model`);
|
|
2623
|
+
const interpolation = track.interpolation === "STEP" ? InterpolateDiscrete : InterpolateLinear;
|
|
2624
|
+
return track.path === "rotation" ? new QuaternionKeyframeTrack(`${node.uuid}.quaternion`, track.times, track.values, interpolation) : new VectorKeyframeTrack(`${node.uuid}.position`, track.times, track.values, interpolation);
|
|
2625
|
+
});
|
|
2626
|
+
return new AnimationClip(options.name ?? document2.name, retargeted.durationSeconds, tracks);
|
|
2627
|
+
}
|
|
2628
|
+
function convertMixamoFbxToVrmAnimations(bytes, options) {
|
|
2629
|
+
requireInitializedEmbodyCore();
|
|
2630
|
+
if (!bytes.byteLength || bytes.byteLength > MAX_BYTES) throw new Error("FBX must be between 1 byte and 64 MiB");
|
|
2631
|
+
const textures = /* @__PURE__ */ new Set();
|
|
2632
|
+
class AnimationTextureLoader extends TextureLoader {
|
|
2633
|
+
load(url) {
|
|
2634
|
+
if (url.startsWith("blob:")) URL.revokeObjectURL(url);
|
|
2635
|
+
const texture = new Texture();
|
|
2636
|
+
textures.add(texture);
|
|
2637
|
+
return texture;
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
const manager = new LoadingManager();
|
|
2641
|
+
manager.addHandler(/.*/, new AnimationTextureLoader(manager));
|
|
2642
|
+
manager.setURLModifier(() => {
|
|
2643
|
+
throw new Error("External FBX resources are not allowed for animation conversion");
|
|
2644
|
+
});
|
|
2645
|
+
let source;
|
|
2646
|
+
try {
|
|
2647
|
+
source = new FBXLoader(manager).parse(bytes, "");
|
|
2648
|
+
if (!source.animations.length) throw new Error("FBX has no animation clips");
|
|
2649
|
+
let hasSkin = false;
|
|
2650
|
+
source.traverse((node) => {
|
|
2651
|
+
if (node.isSkinnedMesh) hasSkin = true;
|
|
2652
|
+
});
|
|
2653
|
+
const referencePose = hasSkin ? void 0 : captureModelReferencePose(source);
|
|
2654
|
+
return source.animations.map((clip) => ({ name: clip.name, bytes: convertMixamoToVrmAnimation(source, clip, { ...options, referencePose }) }));
|
|
2655
|
+
} finally {
|
|
2656
|
+
const materials2 = /* @__PURE__ */ new Set();
|
|
2657
|
+
source?.traverse((node) => {
|
|
2658
|
+
const mesh = node;
|
|
2659
|
+
mesh.geometry?.dispose();
|
|
2660
|
+
if (mesh.material) for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) materials2.add(material);
|
|
2661
|
+
node.skeleton?.dispose();
|
|
2662
|
+
});
|
|
2663
|
+
for (const material of materials2) material.dispose();
|
|
2664
|
+
for (const texture of textures) texture.dispose();
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
var json = (value) => JSON.stringify(value ?? {});
|
|
2668
|
+
function request2(op, payload = {}) {
|
|
2669
|
+
return JSON.parse(requireInitializedEmbodyCore().embody_request(json({ op, payload })));
|
|
2670
|
+
}
|
|
2671
|
+
var getPreset = (id) => request2("preset.get", { id });
|
|
2672
|
+
var extendCharacterConfigWithPreset = (config) => request2("profile.extendConfig", { config });
|
|
2673
|
+
var extractProfileOverrides = (config) => request2("profile.extractOverrides", { config });
|
|
2674
|
+
var mergeCharacterRegionsByName = (base, override) => request2("profile.mergeRegions", { base, override });
|
|
2675
|
+
var getMeshNamesForAUProfile = (profile, auId) => request2("profile.getMeshNamesForAU", { profile, auId });
|
|
2676
|
+
var resolveMorphMeshSelections = (profile, categories, availableNames) => request2("profile.resolveMorphMeshSelections", { profile, categories, availableNames });
|
|
2677
|
+
var getHumanoidSpecification = () => request2("humanoid.getSpecification");
|
|
2678
|
+
var setAUMorphTargets = (profile, auId, side, targets) => request2("profile.setAUMorphTargets", { profile, auId, side, targets });
|
|
2679
|
+
var setHumanoidRoleBinding = (profile, role, boneName) => request2("profile.setHumanoidRoleBinding", { profile, role, boneName });
|
|
2680
|
+
var resolveHumanoidCharacterization = (profile) => request2("profile.resolveHumanoidCharacterization", { profile });
|
|
2681
|
+
var hasLeftRightMorphs = (profile, auId) => request2("profile.hasLeftRightMorphs", { profile, auId });
|
|
2682
|
+
var isMixedAU = (profile, auId) => request2("profile.isMixedAU", { profile, auId });
|
|
2683
|
+
var resolveVisemeProfile = (profile) => request2("profile.resolveVisemes", { profile });
|
|
2684
|
+
var isMaxDegreesOnlyAxisBindingUpdate = (update) => request2("bone.isMaxDegreesOnlyUpdate", { update });
|
|
2685
|
+
var findNodeKeyForBone = (boneName, profile) => profile ? request2("bone.findNodeKey", { profile, boneName }) : null;
|
|
2686
|
+
var ensureBoneNodeKey = (profile, boneName) => request2("bone.ensureNodeKey", { profile, boneName });
|
|
2687
|
+
var ensureBilateralBoneNodeKeys = (profile, leftBoneName, rightBoneName) => request2("bone.ensureBilateralNodeKeys", { profile, leftBoneName, rightBoneName });
|
|
2688
|
+
var resolveBoneNameForNodeKey = (profile, nodeKey) => request2("bone.resolveBoneName", { profile, nodeKey });
|
|
2689
|
+
var stripConfiguredBoneAffixes = (profile, boneName) => request2("bone.stripAffixes", { profile: profile ?? {}, boneName });
|
|
2690
|
+
var inferChiralBoneNamePair = (boneNames, selectedBoneName) => request2("bone.inferChiralPair", { boneNames, selectedBoneName });
|
|
2691
|
+
var formatAxisLabel = (axis) => request2("bone.formatAxisLabel", { axis });
|
|
2692
|
+
var formatAxisDirectionLabel = (axis, direction) => request2("bone.formatDirectionLabel", { axis, direction });
|
|
2693
|
+
var getAxisFromChannel = (channel) => channel ? request2("bone.getAxisFromChannel", { channel }) : null;
|
|
2694
|
+
var getBoneAxisBindingState = (profile, boneName, axis) => request2("bone.getAxisState", { profile, boneName, axis });
|
|
2695
|
+
var resolveBoneAxisChannel = (profile, boneName, axis) => request2("bone.resolveAxisChannel", { profile, boneName, axis });
|
|
2696
|
+
var getBilateralAxisBindingState = (profile, boneName, axis) => request2("bone.getBilateralAxisState", { profile, boneName, axis });
|
|
2697
|
+
var getAUBoneBindingState = (profile, auId) => request2("bone.getAUBindingState", { profile, auId });
|
|
2698
|
+
var getAUBoneBindingStates = (profile, auIds) => request2("bone.getAUBindingStates", {
|
|
2699
|
+
profile: {
|
|
2700
|
+
auToBones: profile.auToBones,
|
|
2701
|
+
boneNodes: profile.boneNodes,
|
|
2702
|
+
bonePrefix: profile.bonePrefix,
|
|
2703
|
+
boneSuffix: profile.boneSuffix,
|
|
2704
|
+
suffixPattern: profile.suffixPattern,
|
|
2705
|
+
compositeRotations: profile.compositeRotations,
|
|
2706
|
+
continuumPairs: profile.continuumPairs,
|
|
2707
|
+
humanoidCharacterization: profile.humanoidCharacterization
|
|
2708
|
+
},
|
|
2709
|
+
auIds
|
|
2710
|
+
});
|
|
2711
|
+
var inferEyeControlScope = (profile, binding) => request2("bone.inferEyeScope", { profile, binding });
|
|
2712
|
+
var inferEyeControlFamily = (binding) => request2("bone.inferEyeFamily", { binding });
|
|
2713
|
+
var applyBoneAxisBindingUpdate = (profile, boneName, axis, update) => request2("bone.applyAxisUpdate", { profile, boneName, axis, update });
|
|
2714
|
+
var applyBilateralAxisBindingUpdate = (profile, boneName, axis, scope, update) => request2("bone.applyBilateralAxisUpdate", { profile, boneName, axis, scope, update });
|
|
2715
|
+
var applyAUBoneBindingUpdate = (profile, auId, update) => request2("bone.applyAUBindingUpdate", { profile, auId, update });
|
|
2716
|
+
var createBoneAxisAu = (profile, boneName, axis, direction, options = {}) => request2("bone.createAxisAU", { profile, boneName, axis, direction, name: options.name });
|
|
2717
|
+
var createBilateralBoneAxisAu = (profile, boneName, axis, scope, direction, options = {}) => request2("bone.createBilateralAxisAU", { profile, boneName, axis, scope, direction, name: options.name });
|
|
2718
|
+
var buildBoneAuOptions = (profile) => request2("bone.buildAUOptions", { profile });
|
|
2719
|
+
var isJointControlAuInfo = (info) => request2("bone.isJointControlInfo", { info });
|
|
2720
|
+
var classifyAuAsJointControl = (profile, auId, options = {}) => request2("bone.classifyJointControl", { profile, auId, boneName: options.boneName, nodeKey: options.nodeKey });
|
|
2721
|
+
var resolveContinuumDisplayLabel = (profile, negativeAuId, positiveAuId) => request2("bone.resolveContinuumLabel", { profile, negativeAuId, positiveAuId }) ?? `AU ${negativeAuId} <-> AU ${positiveAuId}`;
|
|
2722
|
+
function inspectModel(model, meshes, profile) {
|
|
2723
|
+
return new ThreeModelInspector().inspectModel(model, { meshes, profile });
|
|
2724
|
+
}
|
|
2725
|
+
function extractModelData(model, meshes, animations = []) {
|
|
2726
|
+
const inspection = inspectModel(model, meshes);
|
|
2727
|
+
return JSON.parse(requireInitializedEmbodyCore().extract_model_data_json(json(inspection.descriptor), json(serializeAnimationClips(model, animations, inspection))));
|
|
2728
|
+
}
|
|
2729
|
+
async function analyzeModel(options) {
|
|
2730
|
+
const source = options.source;
|
|
2731
|
+
const model = source.type === "gltf" ? source.gltf.scene ?? source.gltf.scenes[0] : source.model;
|
|
2732
|
+
const animations = source.type === "gltf" ? source.gltf.animations : source.animations ?? [];
|
|
2733
|
+
const inspection = inspectModel(model, source.type === "runtime" ? source.meshes : void 0);
|
|
2734
|
+
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) })));
|
|
2735
|
+
}
|
|
2736
|
+
function validateMappings(meshes, skeleton, profile, options = {}) {
|
|
2737
|
+
let nextMorphId = 1;
|
|
2738
|
+
const morphTargets = [];
|
|
2739
|
+
const descriptorMeshes = (meshes ?? []).map((value, index) => {
|
|
2740
|
+
const mesh = value;
|
|
2741
|
+
const id = index + 1;
|
|
2742
|
+
const morphTargetIds = Object.entries(mesh.morphTargetDictionary ?? mesh.geometry?.morphTargetDictionary ?? {}).map(([name, hostIndex]) => {
|
|
2743
|
+
const morphId = nextMorphId++;
|
|
2744
|
+
morphTargets.push({ id: morphId, meshId: id, name, hostIndex });
|
|
2745
|
+
return morphId;
|
|
2746
|
+
});
|
|
2747
|
+
return { id, name: mesh.name ?? "", morphTargetIds, visible: true };
|
|
2748
|
+
});
|
|
2749
|
+
const bones = (skeleton?.bones ?? []).map((bone, index) => ({ id: index + 1, name: typeof bone === "string" ? bone : bone.name ?? "" }));
|
|
2750
|
+
return JSON.parse(requireInitializedEmbodyCore().validate_profile_model(json(profile), json({ meshes: descriptorMeshes, morphTargets, bones, objects: [] }), json(options)));
|
|
2751
|
+
}
|
|
2752
|
+
function validateHumanoidCharacterization(model, profile) {
|
|
2753
|
+
return JSON.parse(requireInitializedEmbodyCore().validate_humanoid_characterization(json(profile), json(inspectModel(model, void 0, profile).descriptor)));
|
|
2754
|
+
}
|
|
2755
|
+
function normalizeHairColorAppearance(value, fallback = DEFAULT_HAIR_COLOR_APPEARANCE) {
|
|
2756
|
+
return JSON.parse(requireInitializedEmbodyCore().normalize_hair_color_appearance_json(json(value), json(fallback)));
|
|
2757
|
+
}
|
|
2758
|
+
var getHumanoidSkeletonTemplate = (id) => HUMANOID_SKELETON_TEMPLATES.find((template) => template.id === id) ?? null;
|
|
2759
|
+
function getHumanoidSkeletonTemplateBoneNames(template = CC4_HUMANOID_SKELETON_TEMPLATE) {
|
|
2760
|
+
return JSON.parse(requireInitializedEmbodyCore().humanoid_skeleton_template_bone_names(json(template)));
|
|
2761
|
+
}
|
|
2762
|
+
function createValidationSkeletonFromHumanoidTemplate(template = CC4_HUMANOID_SKELETON_TEMPLATE) {
|
|
2763
|
+
return JSON.parse(requireInitializedEmbodyCore().create_validation_skeleton_json(json(template)));
|
|
2764
|
+
}
|
|
2765
|
+
function computeHumanoidSkeletonTemplateRestBounds(template) {
|
|
2766
|
+
return Array.from(requireInitializedEmbodyCore().compute_humanoid_skeleton_template_rest_bounds(json(template)));
|
|
2767
|
+
}
|
|
2768
|
+
function extractHumanoidSkeletonTemplateFromModel(model, options) {
|
|
2769
|
+
return JSON.parse(requireInitializedEmbodyCore().extract_humanoid_skeleton_template_json(json(inspectModel(model).descriptor), json(options)));
|
|
2770
|
+
}
|
|
2489
2771
|
function withComponentTracks(clip) {
|
|
2490
2772
|
let expanded = false;
|
|
2491
2773
|
const tracks = clip.tracks.flatMap((track) => {
|
|
@@ -2581,6 +2863,23 @@ var ThreeNativePlaybackConnector = class {
|
|
|
2581
2863
|
__publicField(this, "disposed", false);
|
|
2582
2864
|
__publicField(this, "onError");
|
|
2583
2865
|
__publicField(this, "onRelease");
|
|
2866
|
+
__publicField(this, "afterSample");
|
|
2867
|
+
__publicField(this, "afterFrame", (deltaSeconds) => {
|
|
2868
|
+
this.flushEvents();
|
|
2869
|
+
if (!this.disposed) this.binding.onFrame?.(deltaSeconds);
|
|
2870
|
+
});
|
|
2871
|
+
__publicField(this, "sampleResponse", (deltaSeconds) => {
|
|
2872
|
+
if (this.disposed || !this.renderingEnabled) return;
|
|
2873
|
+
try {
|
|
2874
|
+
this.afterSample?.(deltaSeconds);
|
|
2875
|
+
} catch (error) {
|
|
2876
|
+
const failure = asError(error);
|
|
2877
|
+
queueMicrotask(() => {
|
|
2878
|
+
if (this.onError) this.onError(failure);
|
|
2879
|
+
else throw failure;
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2882
|
+
});
|
|
2584
2883
|
__publicField(this, "finished", (event) => {
|
|
2585
2884
|
const handle = this.actionHandles.get(event.action);
|
|
2586
2885
|
if (!handle || this.disposed) return;
|
|
@@ -2603,8 +2902,14 @@ var ThreeNativePlaybackConnector = class {
|
|
|
2603
2902
|
this.binding = { ...binding, mixer: binding.mixer ?? new AnimationMixer(binding.model) };
|
|
2604
2903
|
this.onError = binding.onError;
|
|
2605
2904
|
this.onRelease = binding.onRelease;
|
|
2905
|
+
this.afterSample = binding.afterSample;
|
|
2606
2906
|
this.binding.mixer.addEventListener("finished", this.finished);
|
|
2607
|
-
this.rendering = bindPlaybackRendering(
|
|
2907
|
+
this.rendering = bindPlaybackRendering(
|
|
2908
|
+
binding.model,
|
|
2909
|
+
this.binding.mixer,
|
|
2910
|
+
this.afterFrame,
|
|
2911
|
+
this.sampleResponse
|
|
2912
|
+
);
|
|
2608
2913
|
}
|
|
2609
2914
|
/** Prepare bindings first; old actions are released before the new binding is exposed. */
|
|
2610
2915
|
rebind(modelKey, profileKey, binding = this.binding) {
|
|
@@ -2615,7 +2920,12 @@ var ThreeNativePlaybackConnector = class {
|
|
|
2615
2920
|
this.rendering.dispose();
|
|
2616
2921
|
this.binding = { ...binding, mixer: binding.mixer ?? new AnimationMixer(binding.model) };
|
|
2617
2922
|
this.binding.mixer.addEventListener("finished", this.finished);
|
|
2618
|
-
this.rendering = bindPlaybackRendering(
|
|
2923
|
+
this.rendering = bindPlaybackRendering(
|
|
2924
|
+
binding.model,
|
|
2925
|
+
this.binding.mixer,
|
|
2926
|
+
this.afterFrame,
|
|
2927
|
+
this.sampleResponse
|
|
2928
|
+
);
|
|
2619
2929
|
this.rendering.setEnabled(this.renderingEnabled);
|
|
2620
2930
|
});
|
|
2621
2931
|
}
|
|
@@ -2949,7 +3259,15 @@ var intensity = (options, fallback = 1) => {
|
|
|
2949
3259
|
const value = options.intensityScale ?? options.intensity ?? options.weight;
|
|
2950
3260
|
return finite2(value) ? Math.max(0, Math.min(2, value)) : fallback;
|
|
2951
3261
|
};
|
|
3262
|
+
var weightRatio = (outgoing, options) => outgoing.getEffectiveWeight() / (finite2(options.mixerWeight) && options.mixerWeight > 0 ? options.mixerWeight : 1);
|
|
3263
|
+
var weighted = (sample, neutral, ratio, quaternion) => {
|
|
3264
|
+
if (ratio === 1) return sample;
|
|
3265
|
+
if (quaternion) return new Quaternion().fromArray(neutral ?? [0, 0, 0, 1]).slerp(new Quaternion().fromArray(sample), ratio).toArray();
|
|
3266
|
+
return sample.map((value, axis) => (neutral?.[axis] ?? 0) + (value - (neutral?.[axis] ?? 0)) * ratio);
|
|
3267
|
+
};
|
|
2952
3268
|
var presetId = (value) => value?.trim().toLowerCase() || "cc4";
|
|
3269
|
+
var HAIR_RESPONSE = "__embody_hair_response";
|
|
3270
|
+
var HAIR_IMPULSE_CLIP = "__embody_hair_impulse";
|
|
2953
3271
|
function supportsRustEmbodyPreset(value) {
|
|
2954
3272
|
return requireInitializedEmbodyCore().has_preset(presetId(value));
|
|
2955
3273
|
}
|
|
@@ -2973,10 +3291,12 @@ var Embody = class _Embody {
|
|
|
2973
3291
|
__publicField(this, "profileMode");
|
|
2974
3292
|
__publicField(this, "preset");
|
|
2975
3293
|
__publicField(this, "sources", /* @__PURE__ */ new Map());
|
|
3294
|
+
__publicField(this, "vrmAnimations", /* @__PURE__ */ new Map());
|
|
2976
3295
|
__publicField(this, "playbacks", /* @__PURE__ */ new Map());
|
|
2977
3296
|
__publicField(this, "current", /* @__PURE__ */ new Map());
|
|
2978
3297
|
__publicField(this, "playDepth", 0);
|
|
2979
3298
|
__publicField(this, "pendingEvents", []);
|
|
3299
|
+
__publicField(this, "pendingReleases", []);
|
|
2980
3300
|
__publicField(this, "bindingGeneration", 0);
|
|
2981
3301
|
__publicField(this, "disposed", false);
|
|
2982
3302
|
__publicField(this, "rendering", true);
|
|
@@ -2984,6 +3304,7 @@ var Embody = class _Embody {
|
|
|
2984
3304
|
__publicField(this, "nextLiveTarget", 0);
|
|
2985
3305
|
__publicField(this, "liveTargets", /* @__PURE__ */ new Map());
|
|
2986
3306
|
__publicField(this, "hairObjects", /* @__PURE__ */ new Map());
|
|
3307
|
+
__publicField(this, "hairHeadObservation");
|
|
2987
3308
|
__publicField(this, "hairConfig", {});
|
|
2988
3309
|
__publicField(this, "hairAppearance", {
|
|
2989
3310
|
hairColor: copy(DEFAULT_HAIR_COLOR_APPEARANCE),
|
|
@@ -3033,6 +3354,7 @@ var Embody = class _Embody {
|
|
|
3033
3354
|
this.assertAlive();
|
|
3034
3355
|
const model = options.model;
|
|
3035
3356
|
if (!model?.isObject3D) throw new Error("onReady requires a Three Object3D model.");
|
|
3357
|
+
this.applier.clearMeshHighlights();
|
|
3036
3358
|
const reference = this.references.get(model) ?? options.referencePose ?? (!this.boundModel ? this.config.referencePose : void 0) ?? captureModelReferencePose(model);
|
|
3037
3359
|
const inspection = this.inspector.inspectModel(model, { meshes: options.meshes, profile: this.profile, referencePose: reference });
|
|
3038
3360
|
this.bind(model, reference, inspection, this.profileSource, this.profile);
|
|
@@ -3072,7 +3394,10 @@ var Embody = class _Embody {
|
|
|
3072
3394
|
}
|
|
3073
3395
|
this.core.free();
|
|
3074
3396
|
this.core = core;
|
|
3075
|
-
if (replacingModel)
|
|
3397
|
+
if (replacingModel) {
|
|
3398
|
+
this.sources.clear();
|
|
3399
|
+
this.vrmAnimations.clear();
|
|
3400
|
+
}
|
|
3076
3401
|
this.boundModel = model;
|
|
3077
3402
|
this.inspection = inspection;
|
|
3078
3403
|
this.referenceBindings = referenceBindings;
|
|
@@ -3093,10 +3418,17 @@ var Embody = class _Embody {
|
|
|
3093
3418
|
this.connector = new ThreeNativePlaybackConnector(session, {
|
|
3094
3419
|
model,
|
|
3095
3420
|
inspection,
|
|
3421
|
+
onFrame: (deltaSeconds) => {
|
|
3422
|
+
if (this.disposed) return;
|
|
3423
|
+
this.applier.applyMeshHighlightFrame(parse(this.core.advance_mesh_highlight(deltaSeconds)));
|
|
3424
|
+
},
|
|
3096
3425
|
onRelease: (id) => {
|
|
3097
3426
|
const playback = this.playbacks.get(id);
|
|
3098
|
-
if (playback?.terminal)
|
|
3099
|
-
|
|
3427
|
+
if (!playback?.terminal) return;
|
|
3428
|
+
if (this.playDepth) this.pendingReleases.push(playback);
|
|
3429
|
+
else this.forget(playback);
|
|
3430
|
+
},
|
|
3431
|
+
afterSample: (mixerDeltaSeconds) => this.sampleHairResponse(mixerDeltaSeconds, true)
|
|
3100
3432
|
});
|
|
3101
3433
|
} catch (error) {
|
|
3102
3434
|
session.free();
|
|
@@ -3107,17 +3439,20 @@ var Embody = class _Embody {
|
|
|
3107
3439
|
this.connector.setTimeScale(this.timeScale);
|
|
3108
3440
|
this.connector.subscribe((event) => this.onPlaybackEvent(event));
|
|
3109
3441
|
this.hairConfig = parse(this.core.get_hair_config_json());
|
|
3442
|
+
this.bindHairHead(model, inspection);
|
|
3110
3443
|
this.registerHairObjects(inspection.allMeshes.filter((mesh) => ["hair", "eyebrow"].includes(this.meshCategory(mesh))));
|
|
3111
3444
|
if (preserveLiveState) this.commitLive();
|
|
3112
3445
|
}
|
|
3113
3446
|
bindModel(meshes) {
|
|
3114
3447
|
const model = this.requireModel();
|
|
3448
|
+
this.applier.clearMeshHighlights();
|
|
3115
3449
|
const reference = this.references.get(model);
|
|
3116
3450
|
const inspection = this.inspector.inspectModel(model, { meshes, profile: this.profile, referencePose: reference });
|
|
3117
3451
|
this.bind(model, reference, inspection, this.profileSource, this.profile);
|
|
3118
3452
|
}
|
|
3119
3453
|
extendReferencePose() {
|
|
3120
3454
|
const model = this.requireModel();
|
|
3455
|
+
this.applier.clearMeshHighlights();
|
|
3121
3456
|
const reference = extendModelReferencePose(model, this.references.get(model));
|
|
3122
3457
|
const inspection = this.inspector.inspectModel(model, { profile: this.profile, referencePose: reference });
|
|
3123
3458
|
this.bind(model, reference, inspection, this.profileSource, this.profile);
|
|
@@ -3132,6 +3467,7 @@ var Embody = class _Embody {
|
|
|
3132
3467
|
this.profile = profile;
|
|
3133
3468
|
return;
|
|
3134
3469
|
}
|
|
3470
|
+
this.applier.clearMeshHighlights();
|
|
3135
3471
|
const reference = this.references.get(this.boundModel);
|
|
3136
3472
|
const inspection = this.inspector.inspectModel(this.boundModel, { profile, referencePose: reference });
|
|
3137
3473
|
this.bind(this.boundModel, reference, inspection, source, profile, false);
|
|
@@ -3156,12 +3492,16 @@ var Embody = class _Embody {
|
|
|
3156
3492
|
const playback = this.playbacks.get(event.handle);
|
|
3157
3493
|
if (!playback) return;
|
|
3158
3494
|
playback.terminal = true;
|
|
3159
|
-
if (!playback.connector.getAction(playback.id))
|
|
3160
|
-
this.playbacks.delete(playback.id);
|
|
3161
|
-
if (this.current.get(playback.source.name) === playback) this.current.delete(playback.source.name);
|
|
3162
|
-
}
|
|
3495
|
+
if (!playback.connector.getAction(playback.id)) this.forget(playback);
|
|
3163
3496
|
playback.notify({ ...event, actionId: event.handle, clipName: playback.source.name });
|
|
3164
3497
|
}
|
|
3498
|
+
flushPendingPlaybackEvents() {
|
|
3499
|
+
while (!this.playDepth && this.pendingReleases.length) {
|
|
3500
|
+
const playback = this.pendingReleases.shift();
|
|
3501
|
+
if (this.playbacks.get(playback.id) === playback) this.forget(playback);
|
|
3502
|
+
}
|
|
3503
|
+
while (!this.playDepth && this.pendingEvents.length) this.onPlaybackEvent(this.pendingEvents.shift());
|
|
3504
|
+
}
|
|
3165
3505
|
compile(source, options) {
|
|
3166
3506
|
const buildOptions = { ..."options" in source ? source.options : {}, ...options, intensityScale: intensity(options) };
|
|
3167
3507
|
switch (source.kind) {
|
|
@@ -3231,7 +3571,10 @@ var Embody = class _Embody {
|
|
|
3231
3571
|
const candidates = outgoing?.getClip().tracks;
|
|
3232
3572
|
const oldTrack = candidates?.[index]?.name === track.name ? candidates[index] : candidates?.find((candidate) => candidate.name === track.name);
|
|
3233
3573
|
if (oldTrack && outgoing) {
|
|
3234
|
-
|
|
3574
|
+
const sample = Array.from(oldTrack.createInterpolant().evaluate(outgoing.time));
|
|
3575
|
+
const quaternion = track.ValueTypeName === "quaternion";
|
|
3576
|
+
const ratio = weightRatio(outgoing, options);
|
|
3577
|
+
previous = outgoing.blendMode === AdditiveAnimationBlendMode ? weighted(sample, void 0, ratio, quaternion) : weighted(sample, rest, ratio, quaternion);
|
|
3235
3578
|
if (outgoing.blendMode === AdditiveAnimationBlendMode && rest) {
|
|
3236
3579
|
previous = track.ValueTypeName === "quaternion" ? new Quaternion().fromArray(rest).multiply(new Quaternion().fromArray(previous)).toArray() : previous.map((value, axis) => value + rest[axis]);
|
|
3237
3580
|
}
|
|
@@ -3367,7 +3710,7 @@ var Embody = class _Embody {
|
|
|
3367
3710
|
validateEasing(options) {
|
|
3368
3711
|
if (options.easing !== void 0 && !["linear", "easeIn", "easeOut", "easeInOut", "easeInOutCubic"].includes(options.easing)) throw new Error(`Unknown playback easing: ${options.easing}`);
|
|
3369
3712
|
}
|
|
3370
|
-
handle(source, options, autoPlay) {
|
|
3713
|
+
handle(source, options, autoPlay, owner = "caller") {
|
|
3371
3714
|
options = { ..."options" in source ? source.options : {}, ...options };
|
|
3372
3715
|
const listeners = /* @__PURE__ */ new Set();
|
|
3373
3716
|
const generation = this.bindingGeneration;
|
|
@@ -3395,8 +3738,10 @@ var Embody = class _Embody {
|
|
|
3395
3738
|
const connector = this.requireConnector();
|
|
3396
3739
|
const inheritedStarts = /* @__PURE__ */ new Map();
|
|
3397
3740
|
const clip = this.materialize(source, options, inheritedStarts);
|
|
3398
|
-
const hairMotion = this.authoredHeadMotion(clip);
|
|
3399
3741
|
const previous = this.current.get(source.name);
|
|
3742
|
+
const outgoing = previous ? connector.getAction(previous.id) : void 0;
|
|
3743
|
+
const predecessorTime = outgoing ? outgoing.time : Number.NaN;
|
|
3744
|
+
const predecessorWeightRatio = outgoing ? weightRatio(outgoing, options) : 1;
|
|
3400
3745
|
const settings = this.nativeOptions(options, clip);
|
|
3401
3746
|
if (previous && connector.getAction(previous.id)) settings.replace = previous.id;
|
|
3402
3747
|
if (current) newPromise();
|
|
@@ -3408,12 +3753,30 @@ var Embody = class _Embody {
|
|
|
3408
3753
|
id = connector.playNativeClip(clip, settings);
|
|
3409
3754
|
} catch (error) {
|
|
3410
3755
|
this.playDepth -= 1;
|
|
3411
|
-
|
|
3756
|
+
this.flushPendingPlaybackEvents();
|
|
3757
|
+
throw error;
|
|
3758
|
+
}
|
|
3759
|
+
try {
|
|
3760
|
+
if (source.kind === "curves") {
|
|
3761
|
+
this.core.record_semantic_slot(
|
|
3762
|
+
source.name,
|
|
3763
|
+
json2(source.input),
|
|
3764
|
+
intensity(options),
|
|
3765
|
+
options.easing ?? "linear",
|
|
3766
|
+
predecessorTime,
|
|
3767
|
+
predecessorWeightRatio
|
|
3768
|
+
);
|
|
3769
|
+
} else this.core.release_semantic_slot(source.name);
|
|
3770
|
+
} catch (error) {
|
|
3771
|
+
connector.control(id, { type: "stop" });
|
|
3772
|
+
this.playDepth -= 1;
|
|
3773
|
+
this.flushPendingPlaybackEvents();
|
|
3412
3774
|
throw error;
|
|
3413
3775
|
}
|
|
3414
3776
|
const playback = {
|
|
3415
3777
|
id,
|
|
3416
3778
|
source,
|
|
3779
|
+
owner,
|
|
3417
3780
|
options: { ...options, intensityScale: intensity(options) },
|
|
3418
3781
|
connector,
|
|
3419
3782
|
terminal: false,
|
|
@@ -3455,7 +3818,7 @@ var Embody = class _Embody {
|
|
|
3455
3818
|
if (this.connector !== connector || !connector.getAction(id)) return result;
|
|
3456
3819
|
ensure();
|
|
3457
3820
|
const seconds = finite2(playback.options.fadeOutSec) ? playback.options.fadeOutSec : 0;
|
|
3458
|
-
if (seconds > 0
|
|
3821
|
+
if (seconds > 0) connector.fadeOut(id, seconds);
|
|
3459
3822
|
else control({ type: "stop" });
|
|
3460
3823
|
return result;
|
|
3461
3824
|
};
|
|
@@ -3489,11 +3852,7 @@ var Embody = class _Embody {
|
|
|
3489
3852
|
if (finite2(fade) && fade > 0) connector.fadeIn(id, fade);
|
|
3490
3853
|
} finally {
|
|
3491
3854
|
this.playDepth -= 1;
|
|
3492
|
-
|
|
3493
|
-
}
|
|
3494
|
-
if (hairMotion && connector.getAction(id)) {
|
|
3495
|
-
this.playHairImpulse(hairMotion.horizontal, hairMotion.vertical);
|
|
3496
|
-
this.setHairGravity(hairMotion.pitch, clip.duration);
|
|
3855
|
+
this.flushPendingPlaybackEvents();
|
|
3497
3856
|
}
|
|
3498
3857
|
return result;
|
|
3499
3858
|
};
|
|
@@ -3531,7 +3890,10 @@ var Embody = class _Embody {
|
|
|
3531
3890
|
}
|
|
3532
3891
|
forget(playback) {
|
|
3533
3892
|
this.playbacks.delete(playback.id);
|
|
3534
|
-
if (this.current.get(playback.source.name) === playback)
|
|
3893
|
+
if (this.current.get(playback.source.name) === playback) {
|
|
3894
|
+
this.current.delete(playback.source.name);
|
|
3895
|
+
this.core.release_semantic_slot(playback.source.name);
|
|
3896
|
+
}
|
|
3535
3897
|
}
|
|
3536
3898
|
updatePlayback(playback, patch) {
|
|
3537
3899
|
this.validateEasing(patch);
|
|
@@ -3547,6 +3909,7 @@ var Embody = class _Embody {
|
|
|
3547
3909
|
if (["weight", "intensity", "intensityScale", "blendMode", "easing", "balance", "balanceMap", "mixWeights"].some((key) => key in patch)) {
|
|
3548
3910
|
const clip = this.materialize(playback.source, options, playback.inheritedStarts);
|
|
3549
3911
|
connector.replaceClip(playback.id, clip);
|
|
3912
|
+
if (playback.source.kind === "curves") this.core.restyle_semantic_slot(playback.source.name, intensity(options), options.easing ?? "linear");
|
|
3550
3913
|
if ("blendMode" in patch) connector.control(playback.id, { type: "setBlendMode", blendMode: clip.blendMode === AdditiveAnimationBlendMode ? "additive" : "normal" });
|
|
3551
3914
|
}
|
|
3552
3915
|
if ("mixerWeight" in patch) connector.control(playback.id, { type: "setWeight", weight: patch.mixerWeight });
|
|
@@ -3572,6 +3935,38 @@ var Embody = class _Embody {
|
|
|
3572
3935
|
playback.options = options;
|
|
3573
3936
|
if (!connector.getAction(playback.id)) this.forget(playback);
|
|
3574
3937
|
}
|
|
3938
|
+
supportsVrmAnimation() {
|
|
3939
|
+
return true;
|
|
3940
|
+
}
|
|
3941
|
+
convertMixamoFbxToVrmAnimations(bytes, options) {
|
|
3942
|
+
this.requireModel();
|
|
3943
|
+
return convertMixamoFbxToVrmAnimations(bytes, options);
|
|
3944
|
+
}
|
|
3945
|
+
getVrmAnimationInfo(bytes) {
|
|
3946
|
+
this.requireModel();
|
|
3947
|
+
const document2 = inspectVrmAnimation(bytes);
|
|
3948
|
+
return { name: document2.name, durationSeconds: document2.durationSeconds, trackCount: document2.tracks.length };
|
|
3949
|
+
}
|
|
3950
|
+
/** Registers a source; Animation owns the later playback request. */
|
|
3951
|
+
registerVrmAnimation(name, bytes, options) {
|
|
3952
|
+
if (typeof name !== "string" || !name.trim()) throw new Error("VRM Animation requires a nonempty clip name.");
|
|
3953
|
+
const model = this.requireModel();
|
|
3954
|
+
const clip = createAnimationClipFromVrmAnimation(bytes, model, {
|
|
3955
|
+
name,
|
|
3956
|
+
metersPerUnit: options.metersPerUnit,
|
|
3957
|
+
referencePose: this.references.get(model),
|
|
3958
|
+
profile: this.profile
|
|
3959
|
+
});
|
|
3960
|
+
this.registerAnimationClips([clip]);
|
|
3961
|
+
this.vrmAnimations.set(name, { model, clip });
|
|
3962
|
+
}
|
|
3963
|
+
/** Stale editor cleanup must not remove a later same-name native source. */
|
|
3964
|
+
releaseVrmAnimation(name) {
|
|
3965
|
+
const registered = this.vrmAnimations.get(name);
|
|
3966
|
+
const source = this.sources.get(name);
|
|
3967
|
+
if (registered && !this.disposed && registered.model === this.boundModel && source?.kind === "native" && source.clip === registered.clip) this.removeAnimationClip(name);
|
|
3968
|
+
this.vrmAnimations.delete(name);
|
|
3969
|
+
}
|
|
3575
3970
|
registerAnimationClips(clips) {
|
|
3576
3971
|
const model = this.requireModel();
|
|
3577
3972
|
for (const clip of clips) {
|
|
@@ -3583,12 +3978,13 @@ var Embody = class _Embody {
|
|
|
3583
3978
|
}
|
|
3584
3979
|
loadAnimationClips(clips) {
|
|
3585
3980
|
this.stopAllAnimations();
|
|
3981
|
+
this.vrmAnimations.clear();
|
|
3586
3982
|
for (const source of [...this.sources.values()]) if (source.kind === "native") {
|
|
3587
3983
|
this.sources.delete(source.name);
|
|
3588
3984
|
this.core.remove_animation_clip(source.name);
|
|
3589
3985
|
}
|
|
3590
3986
|
this.registerAnimationClips(clips ?? []);
|
|
3591
|
-
this.
|
|
3987
|
+
this.sampleHairResponse(0);
|
|
3592
3988
|
}
|
|
3593
3989
|
removeAnimationClip(name) {
|
|
3594
3990
|
this.stopAnimation(name);
|
|
@@ -3603,12 +3999,12 @@ var Embody = class _Embody {
|
|
|
3603
3999
|
if (!source) throw new Error(`Unknown animation clip: ${name}`);
|
|
3604
4000
|
return this.handle(source, options, true);
|
|
3605
4001
|
}
|
|
3606
|
-
build(source, options, play) {
|
|
4002
|
+
build(source, options, play, owner = "caller") {
|
|
3607
4003
|
this.requireModel();
|
|
3608
4004
|
this.validateEasing(options);
|
|
3609
4005
|
this.compile(source, options);
|
|
3610
4006
|
this.sources.set(source.name, source);
|
|
3611
|
-
return this.handle(source, options, play);
|
|
4007
|
+
return this.handle(source, options, play, owner);
|
|
3612
4008
|
}
|
|
3613
4009
|
buildClip(name, curves, options = {}) {
|
|
3614
4010
|
if (options.semanticAnimation) throw new Error("Decode legacy named animation tracks to numeric AU curves before building a clip.");
|
|
@@ -3641,7 +4037,7 @@ var Embody = class _Embody {
|
|
|
3641
4037
|
this.core.cleanup_snippet(name);
|
|
3642
4038
|
}
|
|
3643
4039
|
crossfadeTo(name, seconds, options = {}) {
|
|
3644
|
-
const outgoing = [...this.current.values()].filter((playback) =>
|
|
4040
|
+
const outgoing = [...this.current.values()].filter((playback) => playback.owner === "caller");
|
|
3645
4041
|
const handle = this.playAnimation(name, { ...options, fadeInSec: seconds });
|
|
3646
4042
|
for (const playback of outgoing) if (playback.connector.getAction(playback.id)) playback.connector.fadeOut(playback.id, seconds);
|
|
3647
4043
|
return handle;
|
|
@@ -3652,28 +4048,35 @@ var Embody = class _Embody {
|
|
|
3652
4048
|
if (playback.connector.getAction(playback.id)) playback.connector.control(playback.id, { type: "stop" });
|
|
3653
4049
|
this.forget(playback);
|
|
3654
4050
|
}
|
|
4051
|
+
/** Stop every caller playback. Live setter targets and hair actions keep running. */
|
|
3655
4052
|
stopAllAnimations() {
|
|
3656
4053
|
for (const playback of [...this.playbacks.values()]) {
|
|
3657
|
-
if (playback.
|
|
4054
|
+
if (playback.owner !== "caller") continue;
|
|
3658
4055
|
if (playback.connector.getAction(playback.id)) playback.connector.control(playback.id, { type: "stop" });
|
|
3659
4056
|
this.forget(playback);
|
|
3660
4057
|
}
|
|
3661
4058
|
}
|
|
3662
4059
|
pauseAnimation(name) {
|
|
4060
|
+
if (name === HAIR_RESPONSE) this.core.set_hair_response_paused(true);
|
|
3663
4061
|
const p = this.current.get(name);
|
|
3664
4062
|
if (p) p.connector.control(p.id, { type: "pause" });
|
|
3665
4063
|
}
|
|
3666
4064
|
resumeAnimation(name) {
|
|
4065
|
+
if (name === HAIR_RESPONSE) {
|
|
4066
|
+
this.core.set_hair_response_paused(false);
|
|
4067
|
+
this.sampleHairResponse(0);
|
|
4068
|
+
}
|
|
3667
4069
|
const p = this.current.get(name);
|
|
3668
4070
|
if (!p) return;
|
|
3669
4071
|
if (p.terminal) p.replay({ ...p.options, time: p.connector.getAction(p.id)?.time ?? 0 });
|
|
3670
4072
|
else p.connector.control(p.id, { type: "resume" });
|
|
3671
4073
|
}
|
|
4074
|
+
// Hair actions follow the hair configuration and Embody.stop()/start(), not caller pause commands.
|
|
3672
4075
|
pauseAllAnimations() {
|
|
3673
|
-
for (const p of this.playbacks.values()) if (!p.terminal) p.connector.control(p.id, { type: "pause" });
|
|
4076
|
+
for (const p of this.playbacks.values()) if (!p.terminal && p.owner !== "hair") p.connector.control(p.id, { type: "pause" });
|
|
3674
4077
|
}
|
|
3675
4078
|
resumeAllAnimations() {
|
|
3676
|
-
for (const name of [...this.current
|
|
4079
|
+
for (const [name, p] of [...this.current]) if (p.owner !== "hair") this.resumeAnimation(name);
|
|
3677
4080
|
}
|
|
3678
4081
|
updateClipParams(name, options) {
|
|
3679
4082
|
const p = this.current.get(name);
|
|
@@ -3774,7 +4177,8 @@ var Embody = class _Embody {
|
|
|
3774
4177
|
const handle = this.handle(
|
|
3775
4178
|
{ name: releaseClip.name, kind: "compiled", clip: releaseClip },
|
|
3776
4179
|
{ loopMode: "once", finalPose: "release", mixerWeight: action.getEffectiveWeight() },
|
|
3777
|
-
true
|
|
4180
|
+
true,
|
|
4181
|
+
"live"
|
|
3778
4182
|
);
|
|
3779
4183
|
connector.fadeOut(handle.actionId, duration);
|
|
3780
4184
|
const record = { track: released, handle, releasing: true };
|
|
@@ -3792,11 +4196,17 @@ var Embody = class _Embody {
|
|
|
3792
4196
|
const outgoing = previous?.handle.actionId && connector.getAction(previous.handle.actionId);
|
|
3793
4197
|
let nativeTrack = track;
|
|
3794
4198
|
if (duration > 0 && outgoing) {
|
|
3795
|
-
const
|
|
4199
|
+
const outgoingTrack = outgoing.getClip().tracks[0];
|
|
4200
|
+
const start = weighted(
|
|
4201
|
+
Array.from(outgoingTrack.createInterpolant().evaluate(outgoing.time)),
|
|
4202
|
+
this.restValues(outgoingTrack),
|
|
4203
|
+
outgoing.getEffectiveWeight(),
|
|
4204
|
+
outgoingTrack.ValueTypeName === "quaternion"
|
|
4205
|
+
);
|
|
3796
4206
|
nativeTrack = { ...track, values: [...start, ...endpoint(track)], inheritStart: false };
|
|
3797
4207
|
}
|
|
3798
4208
|
const targetClip = { ...clip, name, tracks: [nativeTrack] };
|
|
3799
|
-
const handle = this.handle({ name, kind: "compiled", clip: targetClip }, { loopMode: "once", finalPose: "hold" }, true);
|
|
4209
|
+
const handle = this.handle({ name, kind: "compiled", clip: targetClip }, { loopMode: "once", finalPose: "hold" }, true, "live");
|
|
3800
4210
|
this.liveTargets.set(key, { track, handle, releasing: false });
|
|
3801
4211
|
handles.push(handle);
|
|
3802
4212
|
}
|
|
@@ -3841,6 +4251,49 @@ var Embody = class _Embody {
|
|
|
3841
4251
|
getContinuum(negative, positive) {
|
|
3842
4252
|
return this.core.get_continuum(negative, positive);
|
|
3843
4253
|
}
|
|
4254
|
+
/**
|
|
4255
|
+
* One clip slot's own semantic AU contribution right now (`{auId: value}`),
|
|
4256
|
+
* evaluated in Rust at the native action's time and effective weight.
|
|
4257
|
+
* Null when the slot is not playing or was not an AU-curve clip.
|
|
4258
|
+
*/
|
|
4259
|
+
readAUContribution(name) {
|
|
4260
|
+
const playback = this.current.get(name);
|
|
4261
|
+
const action = playback?.connector.getAction(playback.id);
|
|
4262
|
+
return playback && action ? parse(this.core.read_semantic_slot(name, action.time, action.getEffectiveWeight())) : null;
|
|
4263
|
+
}
|
|
4264
|
+
/**
|
|
4265
|
+
* On-demand AU state: live control values, active AU-curve clip
|
|
4266
|
+
* contributions and their sum. Baked/native and typed clips cannot be
|
|
4267
|
+
* inverted to AU values and are listed as `unreadable`, never estimated.
|
|
4268
|
+
*/
|
|
4269
|
+
readAUState() {
|
|
4270
|
+
const active = [];
|
|
4271
|
+
const unreadable = [];
|
|
4272
|
+
for (const [name, playback] of this.current) {
|
|
4273
|
+
const action = playback.connector.getAction(playback.id);
|
|
4274
|
+
if (!action) continue;
|
|
4275
|
+
if (playback.source.kind === "curves") active.push({ name, time: action.time, weight: action.getEffectiveWeight() });
|
|
4276
|
+
else if (playback.source.kind !== "compiled") unreadable.push(name);
|
|
4277
|
+
}
|
|
4278
|
+
return { ...parse(this.core.read_semantic_state(json2(active))), unreadable };
|
|
4279
|
+
}
|
|
4280
|
+
/**
|
|
4281
|
+
* Distance from a slot's current contribution to AU destinations, measured
|
|
4282
|
+
* along profile continuum pairs, and whether it reverses unfinished travel.
|
|
4283
|
+
* A slot that is not playing is at zero. Callers choose any timing policy.
|
|
4284
|
+
*/
|
|
4285
|
+
measureAUGoal(name, destinations) {
|
|
4286
|
+
const playback = this.current.get(name);
|
|
4287
|
+
const action = playback?.connector.getAction(playback.id);
|
|
4288
|
+
const unfinished = !!playback && !!action && !playback.terminal && action.time < action.getClip().duration;
|
|
4289
|
+
return parse(this.core.measure_au_goal(
|
|
4290
|
+
name,
|
|
4291
|
+
action?.time ?? Number.NaN,
|
|
4292
|
+
action?.getEffectiveWeight() ?? Number.NaN,
|
|
4293
|
+
json2(destinations),
|
|
4294
|
+
unfinished
|
|
4295
|
+
));
|
|
4296
|
+
}
|
|
3844
4297
|
transitionAU(id, value, durationMs = 200, balance = this.core.get_au_balance(id)) {
|
|
3845
4298
|
return this.transition(this.mutateLive(() => this.core.set_au_signed(id, value, balance), durationMs));
|
|
3846
4299
|
}
|
|
@@ -3940,7 +4393,9 @@ var Embody = class _Embody {
|
|
|
3940
4393
|
this.meshProfile(name).visible = visible;
|
|
3941
4394
|
}
|
|
3942
4395
|
highlightMesh(name, color, value) {
|
|
3943
|
-
this.
|
|
4396
|
+
this.requireModel();
|
|
4397
|
+
this.core.request_mesh_highlight(name ?? void 0, color, value);
|
|
4398
|
+
this.applier.applyMeshHighlightFrame(parse(this.core.evaluate_mesh_highlight_frame()));
|
|
3944
4399
|
}
|
|
3945
4400
|
getMeshMaterialConfig(name) {
|
|
3946
4401
|
return this.applier.getMeshMaterialConfig(this.requireModel(), name);
|
|
@@ -3958,7 +4413,9 @@ var Embody = class _Embody {
|
|
|
3958
4413
|
const material = mesh && (Array.isArray(mesh.material) ? mesh.material[0] : mesh.material);
|
|
3959
4414
|
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 };
|
|
3960
4415
|
}
|
|
3961
|
-
this.
|
|
4416
|
+
for (const [name, playback] of [...this.current]) if (playback.owner === "hair") this.stopAnimation(name);
|
|
4417
|
+
this.core.reset_hair_response();
|
|
4418
|
+
this.sampleHairResponse(0);
|
|
3962
4419
|
return [...this.hairObjects.values()].map((mesh) => ({ name: mesh.name, isMesh: true, isEyebrow: this.meshCategory(mesh) === "eyebrow" }));
|
|
3963
4420
|
}
|
|
3964
4421
|
getRegisteredHairObjects() {
|
|
@@ -3998,12 +4455,69 @@ var Embody = class _Embody {
|
|
|
3998
4455
|
setHairOutline(value) {
|
|
3999
4456
|
Object.assign(this.hairAppearance, { showOutline: value.show }, value.color ? { outlineColor: value.color } : {}, value.opacity !== void 0 ? { outlineOpacity: value.opacity } : {});
|
|
4000
4457
|
}
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4458
|
+
hairClipOptions(options) {
|
|
4459
|
+
return { ...options, meshNames: [...this.hairObjects.keys()], source: "hair", blendMode: "additive" };
|
|
4460
|
+
}
|
|
4461
|
+
bindHairHead(model, inspection) {
|
|
4462
|
+
this.hairHeadObservation = void 0;
|
|
4463
|
+
const binding = parse(this.core.get_hair_head_binding_json());
|
|
4464
|
+
const head = binding && inspection.boneBindings.get(binding.boneId);
|
|
4465
|
+
if (!binding || !head) return;
|
|
4466
|
+
const chain = [];
|
|
4467
|
+
for (let node2 = head; node2 && node2 !== model; node2 = node2.parent) chain.push(node2);
|
|
4468
|
+
if (!chain.length || chain[chain.length - 1].parent !== model) return;
|
|
4469
|
+
const reference = new Quaternion();
|
|
4470
|
+
const node = new Quaternion();
|
|
4471
|
+
for (let index = chain.length - 1; index >= 0; index -= 1) {
|
|
4472
|
+
const rotation = this.referenceBindings.get(chain[index])?.transform.rotation;
|
|
4473
|
+
reference.multiply(rotation ? node.set(rotation.x, rotation.y, rotation.z, rotation.w) : chain[index].quaternion);
|
|
4474
|
+
}
|
|
4475
|
+
this.hairHeadObservation = {
|
|
4476
|
+
boneName: binding.boneName,
|
|
4477
|
+
chain,
|
|
4478
|
+
reference: binding.reference,
|
|
4479
|
+
modelToReference: new Quaternion().fromArray(binding.reference).multiply(reference.normalize().invert()),
|
|
4480
|
+
rotation: new Quaternion()
|
|
4481
|
+
};
|
|
4482
|
+
}
|
|
4483
|
+
sampleHairResponse(deltaSeconds, observeHead = false) {
|
|
4484
|
+
if (this.disposed || !this.connector || !this.inspection) return;
|
|
4485
|
+
if (!this.hairConfig.enabled || !this.hairObjects.size) {
|
|
4486
|
+
this.stopAnimation(HAIR_RESPONSE);
|
|
4487
|
+
return;
|
|
4488
|
+
}
|
|
4489
|
+
const previous = this.current.get(HAIR_RESPONSE);
|
|
4490
|
+
const action = previous?.connector.getAction(previous.id);
|
|
4491
|
+
const head = this.hairHeadObservation;
|
|
4492
|
+
let pose = null;
|
|
4493
|
+
if (observeHead && head) {
|
|
4494
|
+
const rotation = head.rotation.copy(head.modelToReference);
|
|
4495
|
+
for (let index = head.chain.length - 1; index >= 0; index -= 1) rotation.multiply(head.chain[index].quaternion);
|
|
4496
|
+
pose = { boneName: head.boneName, reference: head.reference, current: rotation.normalize().toArray() };
|
|
4497
|
+
}
|
|
4498
|
+
const ir = parse(this.core.build_hair_response_clip(
|
|
4499
|
+
HAIR_RESPONSE,
|
|
4500
|
+
JSON.stringify(pose),
|
|
4501
|
+
action?.paused ? 0 : deltaSeconds,
|
|
4502
|
+
json2({ meshNames: [...this.hairObjects.keys()] })
|
|
4503
|
+
));
|
|
4504
|
+
if (!ir.tracks.length) {
|
|
4505
|
+
this.stopAnimation(HAIR_RESPONSE);
|
|
4506
|
+
return;
|
|
4507
|
+
}
|
|
4508
|
+
const clip = createAnimationClipFromClipIR(ir, this.inspection);
|
|
4509
|
+
clip.blendMode = AdditiveAnimationBlendMode;
|
|
4510
|
+
const oldTracks = action?.getClip().tracks;
|
|
4511
|
+
const sameBindings = oldTracks && oldTracks.length === clip.tracks.length && clip.tracks.every((track, index) => {
|
|
4512
|
+
const old = oldTracks[index];
|
|
4513
|
+
return old.name === track.name && old.ValueTypeName === track.ValueTypeName && old.getValueSize() === track.getValueSize();
|
|
4514
|
+
});
|
|
4515
|
+
if (previous && action && sameBindings) {
|
|
4516
|
+
this.connector.replaceClip(previous.id, clip);
|
|
4517
|
+
previous.source = { name: HAIR_RESPONSE, kind: "native", clip };
|
|
4518
|
+
} else {
|
|
4519
|
+
const handle = this.handle({ name: HAIR_RESPONSE, kind: "native", clip }, { loopMode: "repeat", blendMode: "additive" }, true, "hair");
|
|
4520
|
+
if (action?.paused || this.core.is_hair_response_paused()) handle.pause();
|
|
4007
4521
|
}
|
|
4008
4522
|
}
|
|
4009
4523
|
setHairPhysicsEnabled(value) {
|
|
@@ -4013,8 +4527,12 @@ var Embody = class _Embody {
|
|
|
4013
4527
|
return !!this.hairConfig.enabled;
|
|
4014
4528
|
}
|
|
4015
4529
|
setHairPhysicsConfig(value) {
|
|
4530
|
+
this.assertAlive();
|
|
4016
4531
|
this.hairConfig = parse(this.core.set_hair_config_json(json2(value)));
|
|
4017
|
-
this.
|
|
4532
|
+
if (!this.hairConfig.enabled) {
|
|
4533
|
+
for (const [name, playback] of [...this.current]) if (playback.owner === "hair") this.stopAnimation(name);
|
|
4534
|
+
}
|
|
4535
|
+
this.sampleHairResponse(0);
|
|
4018
4536
|
}
|
|
4019
4537
|
getHairPhysicsConfig() {
|
|
4020
4538
|
return copy(this.hairConfig);
|
|
@@ -4030,41 +4548,12 @@ var Embody = class _Embody {
|
|
|
4030
4548
|
if (state.position) mesh.position.set(state.position.x, state.position.y, state.position.z);
|
|
4031
4549
|
if (state.color) this.applier.applyHairAppearance([mesh], state.color);
|
|
4032
4550
|
}
|
|
4033
|
-
authoredHeadMotion(clip) {
|
|
4034
|
-
if (!this.hairConfig.enabled || clip.name.startsWith("__embody_hair") || !this.hairObjects.size) return void 0;
|
|
4035
|
-
const tracks = [];
|
|
4036
|
-
for (const track of clip.tracks) {
|
|
4037
|
-
if (track.ValueTypeName !== "quaternion") continue;
|
|
4038
|
-
const parsed = PropertyBinding.parseTrackName(track.name);
|
|
4039
|
-
let object = PropertyBinding.findNode(this.requireModel(), parsed.nodeName);
|
|
4040
|
-
if (parsed.objectName === "bones" && object) {
|
|
4041
|
-
const skeleton = object.skeleton;
|
|
4042
|
-
object = skeleton?.bones.find((bone) => bone.name === String(parsed.objectIndex)) ?? skeleton?.bones[Number(parsed.objectIndex)];
|
|
4043
|
-
}
|
|
4044
|
-
if (!object) continue;
|
|
4045
|
-
const cubic = track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true;
|
|
4046
|
-
const stride = track.getValueSize();
|
|
4047
|
-
const reference = this.restValues(track);
|
|
4048
|
-
if (!reference) continue;
|
|
4049
|
-
const samples = [];
|
|
4050
|
-
for (let index = 0; index < track.times.length; index += 1) {
|
|
4051
|
-
const offset = index * stride + (cubic ? 4 : 0);
|
|
4052
|
-
samples.push(Array.from(track.values.subarray(offset, offset + 4)));
|
|
4053
|
-
}
|
|
4054
|
-
tracks.push({ boneName: object.name, current: object.quaternion.toArray(), reference, samples });
|
|
4055
|
-
}
|
|
4056
|
-
return parse(this.core.resolve_hair_motion_json(json2(tracks), clip.blendMode === AdditiveAnimationBlendMode)) ?? void 0;
|
|
4057
|
-
}
|
|
4058
|
-
setHairGravity(pitch, durationSeconds) {
|
|
4059
|
-
const clip = parse(this.core.build_hair_gravity_clip("__embody_hair_gravity", pitch, durationSeconds, json2({ meshNames: [...this.hairObjects.keys()], source: "hair" })));
|
|
4060
|
-
if (!clip.tracks.length) return;
|
|
4061
|
-
this.handle({ name: clip.name, kind: "compiled", clip }, { loopMode: "once", finalPose: "hold", blendMode: "additive" }, true);
|
|
4062
|
-
}
|
|
4063
4551
|
playHairImpulse(horizontal, vertical) {
|
|
4064
4552
|
if (!this.hairConfig.enabled || !this.hairObjects.size) return void 0;
|
|
4065
4553
|
const curves = parse(this.wasm.build_hair_impulse_curves(json2(this.hairConfig), this.hairConfig.impulseClipDuration, horizontal, vertical));
|
|
4066
4554
|
if (!Object.keys(curves).length) return void 0;
|
|
4067
|
-
|
|
4555
|
+
const options = this.hairClipOptions({ loopMode: "once", finalPose: "release" });
|
|
4556
|
+
return this.build({ name: HAIR_IMPULSE_CLIP, kind: "curves", input: curves, options: copy(options) }, options, true, "hair");
|
|
4068
4557
|
}
|
|
4069
4558
|
start() {
|
|
4070
4559
|
this.assertAlive();
|
|
@@ -4085,11 +4574,14 @@ var Embody = class _Embody {
|
|
|
4085
4574
|
dispose() {
|
|
4086
4575
|
if (this.disposed) return;
|
|
4087
4576
|
this.connector?.dispose();
|
|
4577
|
+
this.applier.clearMeshHighlights();
|
|
4088
4578
|
this.core.free();
|
|
4089
4579
|
this.playbacks.clear();
|
|
4090
4580
|
this.current.clear();
|
|
4091
4581
|
this.sources.clear();
|
|
4582
|
+
this.vrmAnimations.clear();
|
|
4092
4583
|
this.hairObjects.clear();
|
|
4584
|
+
this.hairHeadObservation = void 0;
|
|
4093
4585
|
this.meshes.length = 0;
|
|
4094
4586
|
this.meshByName.clear();
|
|
4095
4587
|
this.bones = {};
|
|
@@ -4131,7 +4623,7 @@ function createSceneInkBackgroundCss(palette = DEFAULT_PALETTE) {
|
|
|
4131
4623
|
createSceneInkBackgroundCss();
|
|
4132
4624
|
|
|
4133
4625
|
// three/sceneInkBackground.ts
|
|
4134
|
-
var toCssColor = (tone) => `#${new
|
|
4626
|
+
var toCssColor = (tone) => `#${new THREE11.Color(tone).getHexString()}`;
|
|
4135
4627
|
function createSceneInkBackground(onBackgroundChange) {
|
|
4136
4628
|
let currentBackground = "";
|
|
4137
4629
|
let disposed = false;
|
|
@@ -4271,7 +4763,7 @@ function sampleTexture(texture) {
|
|
|
4271
4763
|
if (alpha < 0.18) {
|
|
4272
4764
|
continue;
|
|
4273
4765
|
}
|
|
4274
|
-
const color = new
|
|
4766
|
+
const color = new THREE11.Color(
|
|
4275
4767
|
data[index] / 255,
|
|
4276
4768
|
data[index + 1] / 255,
|
|
4277
4769
|
data[index + 2] / 255
|
|
@@ -4316,7 +4808,7 @@ function buildPalette(samples) {
|
|
|
4316
4808
|
const hsl = { h: 0, s: 0, l: 0 };
|
|
4317
4809
|
sample.color.getHSL(hsl);
|
|
4318
4810
|
const bucketId = Math.floor(hsl.h * 18);
|
|
4319
|
-
const bucket = buckets.get(bucketId) ?? { color: new
|
|
4811
|
+
const bucket = buckets.get(bucketId) ?? { color: new THREE11.Color(0, 0, 0), score: 0 };
|
|
4320
4812
|
bucket.color.r += sample.color.r * sample.score;
|
|
4321
4813
|
bucket.color.g += sample.color.g * sample.score;
|
|
4322
4814
|
bucket.color.b += sample.color.b * sample.score;
|
|
@@ -4349,18 +4841,18 @@ function buildPalette(samples) {
|
|
|
4349
4841
|
function massageBackgroundTone(color) {
|
|
4350
4842
|
const hsl = { h: 0, s: 0, l: 0 };
|
|
4351
4843
|
color.getHSL(hsl);
|
|
4352
|
-
const saturation =
|
|
4353
|
-
const lightness =
|
|
4354
|
-
return new
|
|
4844
|
+
const saturation = THREE11.MathUtils.clamp(Math.max(hsl.s * 1.35, 0.46), 0.46, 0.82);
|
|
4845
|
+
const lightness = THREE11.MathUtils.clamp(hsl.l + 0.08, 0.42, 0.66);
|
|
4846
|
+
return new THREE11.Color().setHSL(hsl.h, saturation, lightness);
|
|
4355
4847
|
}
|
|
4356
4848
|
function createCompanionTone(source, index) {
|
|
4357
4849
|
const hsl = { h: 0, s: 0, l: 0 };
|
|
4358
4850
|
source.getHSL(hsl);
|
|
4359
4851
|
const hueOffset = index === 1 ? 0.075 : -0.065;
|
|
4360
|
-
return new
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4852
|
+
return new THREE11.Color().setHSL(
|
|
4853
|
+
THREE11.MathUtils.euclideanModulo(hsl.h + hueOffset, 1),
|
|
4854
|
+
THREE11.MathUtils.clamp(hsl.s * (index === 1 ? 0.92 : 0.82), 0.38, 0.76),
|
|
4855
|
+
THREE11.MathUtils.clamp(hsl.l + (index === 1 ? 0.04 : -0.03), 0.38, 0.68)
|
|
4364
4856
|
);
|
|
4365
4857
|
}
|
|
4366
4858
|
function colorDistance(a, b) {
|
|
@@ -4399,6 +4891,11 @@ var prepare = (model, castShadows = true) => {
|
|
|
4399
4891
|
});
|
|
4400
4892
|
return meshes;
|
|
4401
4893
|
};
|
|
4894
|
+
var prepareCharacterModelResult = (model, animations, gltf, options) => ({ model, meshes: prepare(model, options.castShadows), animations, gltf });
|
|
4895
|
+
var bufferFormat = (data) => {
|
|
4896
|
+
const header = new TextDecoder().decode(new Uint8Array(data, 0, Math.min(data.byteLength, 4096)));
|
|
4897
|
+
return header.startsWith("Kaydara FBX Binary \0") || /^\s*FBXHeaderExtension\s*:/m.test(header) ? "fbx" : "gltf";
|
|
4898
|
+
};
|
|
4402
4899
|
var progress = (listener) => {
|
|
4403
4900
|
let last = -1;
|
|
4404
4901
|
return (event) => {
|
|
@@ -4431,8 +4928,8 @@ function disposeCharacterModel(scene, model) {
|
|
|
4431
4928
|
});
|
|
4432
4929
|
}
|
|
4433
4930
|
function loadCharacterModel(url, options = {}) {
|
|
4434
|
-
const done = (model, animations, gltf) => (
|
|
4435
|
-
if (/\.fbx(
|
|
4931
|
+
const done = (model, animations, gltf) => prepareCharacterModelResult(model, animations, gltf, options);
|
|
4932
|
+
if ((options.format ?? (/\.fbx(?:[?#]|$)/i.test(url) ? "fbx" : "gltf")) === "fbx") {
|
|
4436
4933
|
fbxLoader ?? (fbxLoader = new FBXLoader());
|
|
4437
4934
|
return new Promise((resolve, reject) => fbxLoader.load(url, (model) => resolve(done(model, model.animations ?? [], null)), progress(options.onProgress), reject));
|
|
4438
4935
|
}
|
|
@@ -4440,8 +4937,15 @@ function loadCharacterModel(url, options = {}) {
|
|
|
4440
4937
|
return new Promise((resolve, reject) => loader.load(url, (gltf) => resolve(done(gltf.scene, gltf.animations ?? [], gltf)), progress(options.onProgress), reject));
|
|
4441
4938
|
}
|
|
4442
4939
|
function parseCharacterModel(data, resourcePath = "", options = {}) {
|
|
4940
|
+
if ((options.format ?? bufferFormat(data)) === "fbx") {
|
|
4941
|
+
return Promise.resolve().then(() => {
|
|
4942
|
+
fbxLoader ?? (fbxLoader = new FBXLoader());
|
|
4943
|
+
const model = fbxLoader.parse(data, resourcePath);
|
|
4944
|
+
return prepareCharacterModelResult(model, model.animations ?? [], null, options);
|
|
4945
|
+
});
|
|
4946
|
+
}
|
|
4443
4947
|
const loader = getGltfLoader(options.dracoDecoderPath);
|
|
4444
|
-
return new Promise((resolve, reject) => loader.parse(data, resourcePath, (gltf) => resolve(
|
|
4948
|
+
return new Promise((resolve, reject) => loader.parse(data, resourcePath, (gltf) => resolve(prepareCharacterModelResult(gltf.scene, gltf.animations ?? [], gltf, options)), reject));
|
|
4445
4949
|
}
|
|
4446
4950
|
|
|
4447
4951
|
// three/modelMorphLayout.ts
|
|
@@ -4591,6 +5095,63 @@ function assertRenderableCharacterMorphAttributes(model) {
|
|
|
4591
5095
|
}
|
|
4592
5096
|
});
|
|
4593
5097
|
}
|
|
5098
|
+
var owners2 = /* @__PURE__ */ new WeakMap();
|
|
5099
|
+
var snapshot = (scene) => ({ value: scene.background, intensity: scene.backgroundIntensity, rotation: scene.backgroundRotation.clone() });
|
|
5100
|
+
var apply = (scene, value) => {
|
|
5101
|
+
scene.background = value.value;
|
|
5102
|
+
scene.backgroundIntensity = value.intensity;
|
|
5103
|
+
scene.backgroundRotation.copy(value.rotation);
|
|
5104
|
+
};
|
|
5105
|
+
var ownerFor = (scene) => {
|
|
5106
|
+
let owner = owners2.get(scene);
|
|
5107
|
+
if (!owner) {
|
|
5108
|
+
owner = { base: snapshot(scene), override: null, disposed: false };
|
|
5109
|
+
owners2.set(scene, owner);
|
|
5110
|
+
}
|
|
5111
|
+
return owner;
|
|
5112
|
+
};
|
|
5113
|
+
function setSceneBaseBackground(scene, value) {
|
|
5114
|
+
const owner = ownerFor(scene);
|
|
5115
|
+
owner.base = { value, intensity: 1, rotation: new Euler() };
|
|
5116
|
+
if (!owner.override?.value) apply(scene, owner.base);
|
|
5117
|
+
}
|
|
5118
|
+
function acquireSceneBackground(scene, onSceneDispose) {
|
|
5119
|
+
const owner = ownerFor(scene);
|
|
5120
|
+
if (owner.disposed) throw new Error("The scenery scene has been disposed.");
|
|
5121
|
+
if (owner.override) throw new Error("The scene already has a scenery renderer.");
|
|
5122
|
+
owner.base = snapshot(scene);
|
|
5123
|
+
const override = { value: null, onSceneDispose };
|
|
5124
|
+
owner.override = override;
|
|
5125
|
+
let disposed = false;
|
|
5126
|
+
return {
|
|
5127
|
+
set(value, intensity2, rotationDegrees) {
|
|
5128
|
+
if (disposed || owner.disposed) throw new Error("The scenery background has been disposed.");
|
|
5129
|
+
override.value = { value, intensity: intensity2, rotation: new Euler(0, rotationDegrees * Math.PI / 180, 0) };
|
|
5130
|
+
apply(scene, override.value);
|
|
5131
|
+
},
|
|
5132
|
+
clear() {
|
|
5133
|
+
if (disposed) return;
|
|
5134
|
+
override.value = null;
|
|
5135
|
+
apply(scene, owner.base);
|
|
5136
|
+
},
|
|
5137
|
+
dispose() {
|
|
5138
|
+
if (disposed) return;
|
|
5139
|
+
disposed = true;
|
|
5140
|
+
if (owner.override === override) {
|
|
5141
|
+
owner.override = null;
|
|
5142
|
+
apply(scene, owner.base);
|
|
5143
|
+
}
|
|
5144
|
+
}
|
|
5145
|
+
};
|
|
5146
|
+
}
|
|
5147
|
+
function disposeSceneBackground(scene) {
|
|
5148
|
+
const owner = ownerFor(scene);
|
|
5149
|
+
if (owner.disposed) return;
|
|
5150
|
+
owner.disposed = true;
|
|
5151
|
+
owner.override?.onSceneDispose();
|
|
5152
|
+
}
|
|
5153
|
+
|
|
5154
|
+
// three/scene.ts
|
|
4594
5155
|
var DEFAULT_CHARACTER_LIGHTING_PRESETS = {
|
|
4595
5156
|
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 } },
|
|
4596
5157
|
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 } },
|
|
@@ -4835,7 +5396,8 @@ function constructDefaultCharacterScene(container, options, renderer, disposeRen
|
|
|
4835
5396
|
Object.assign(renderer.domElement.style, { display: "block", width: "100%", height: "100%" });
|
|
4836
5397
|
const scene = new Scene();
|
|
4837
5398
|
const background = options.background === void 0 ? sceneType.background : options.background;
|
|
4838
|
-
scene
|
|
5399
|
+
setSceneBaseBackground(scene, background == null ? null : new Color(background));
|
|
5400
|
+
lifetime.add(() => disposeSceneBackground(scene));
|
|
4839
5401
|
const camera = new PerspectiveCamera(options.cameraFov ?? 45, width / height, 0.1, 1e3);
|
|
4840
5402
|
const preset = DEFAULT_CHARACTER_LIGHTING_PRESETS[options.lightingPreset ?? sceneType.lightingPreset]?.settings ?? DEFAULT_CHARACTER_LIGHTING_SETTINGS;
|
|
4841
5403
|
const lighting = createLighting(scene, { ...preset, ...options.lighting });
|
|
@@ -4856,7 +5418,7 @@ function constructDefaultCharacterScene(container, options, renderer, disposeRen
|
|
|
4856
5418
|
const type = CHARACTER_SCENE_TYPES[value.type ?? DEFAULT_CHARACTER_SCENE_TYPE_ID] ?? CHARACTER_SCENE_TYPES[DEFAULT_CHARACTER_SCENE_TYPE_ID];
|
|
4857
5419
|
currentSceneType = type.id;
|
|
4858
5420
|
const background2 = value.background === void 0 ? type.background : value.background;
|
|
4859
|
-
scene
|
|
5421
|
+
setSceneBaseBackground(scene, background2 == null ? null : new Color(background2));
|
|
4860
5422
|
camera.fov = value.cameraFov ?? 45;
|
|
4861
5423
|
camera.updateProjectionMatrix();
|
|
4862
5424
|
const preset2 = DEFAULT_CHARACTER_LIGHTING_PRESETS[value.lightingPreset ?? type.lightingPreset] ?? DEFAULT_CHARACTER_LIGHTING_PRESETS[DEFAULT_CHARACTER_LIGHTING_PRESET_ID];
|
|
@@ -4991,17 +5553,17 @@ function prepareCameraCoordinates(scene, camera, backend) {
|
|
|
4991
5553
|
}
|
|
4992
5554
|
});
|
|
4993
5555
|
const previous = Array.from(cameras, (current) => [current, current.coordinateSystem]);
|
|
4994
|
-
const
|
|
5556
|
+
const apply2 = (current, coordinates2) => {
|
|
4995
5557
|
current.coordinateSystem = coordinates2;
|
|
4996
5558
|
current.updateProjectionMatrix();
|
|
4997
5559
|
};
|
|
4998
5560
|
const coordinates = backend === "webgpu" ? WebGPUCoordinateSystem : WebGLCoordinateSystem;
|
|
4999
|
-
cameras.forEach((current) =>
|
|
5561
|
+
cameras.forEach((current) => apply2(current, coordinates));
|
|
5000
5562
|
shadows.forEach((shadow) => {
|
|
5001
5563
|
shadow.needsUpdate = true;
|
|
5002
5564
|
});
|
|
5003
5565
|
return () => {
|
|
5004
|
-
previous.forEach(([current, original]) =>
|
|
5566
|
+
previous.forEach(([current, original]) => apply2(current, original));
|
|
5005
5567
|
shadows.forEach((shadow) => {
|
|
5006
5568
|
shadow.needsUpdate = true;
|
|
5007
5569
|
});
|
|
@@ -5015,7 +5577,8 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5015
5577
|
let currentSceneType = sceneType.id;
|
|
5016
5578
|
const scene = new Scene();
|
|
5017
5579
|
const background = options.background === void 0 ? sceneType.background : options.background;
|
|
5018
|
-
scene
|
|
5580
|
+
setSceneBaseBackground(scene, background == null ? null : new Color(background));
|
|
5581
|
+
lifetime.add(() => disposeSceneBackground(scene));
|
|
5019
5582
|
const camera = new PerspectiveCamera(options.cameraFov ?? 45, 1, 0.1, 1e3);
|
|
5020
5583
|
const preset = DEFAULT_CHARACTER_LIGHTING_PRESETS[options.lightingPreset ?? sceneType.lightingPreset]?.settings ?? DEFAULT_CHARACTER_LIGHTING_SETTINGS;
|
|
5021
5584
|
const lighting = createMutableCharacterLighting(scene, { ...preset, ...options.lighting });
|
|
@@ -5055,10 +5618,10 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5055
5618
|
return { ...common, backend: null, renderer: null };
|
|
5056
5619
|
};
|
|
5057
5620
|
const notify = () => {
|
|
5058
|
-
const
|
|
5621
|
+
const snapshot3 = getSnapshot();
|
|
5059
5622
|
for (const listener of listeners) {
|
|
5060
5623
|
try {
|
|
5061
|
-
listener(
|
|
5624
|
+
listener(snapshot3);
|
|
5062
5625
|
} catch (failure) {
|
|
5063
5626
|
console.error("Character rendering subscriber failed.", failure);
|
|
5064
5627
|
}
|
|
@@ -5185,9 +5748,9 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5185
5748
|
rollback(() => oldRenderer?.dispose());
|
|
5186
5749
|
status = "ready";
|
|
5187
5750
|
error = null;
|
|
5188
|
-
const
|
|
5751
|
+
const snapshot3 = getSnapshot();
|
|
5189
5752
|
notify();
|
|
5190
|
-
return
|
|
5753
|
+
return snapshot3;
|
|
5191
5754
|
} catch (failure) {
|
|
5192
5755
|
if (suspended) {
|
|
5193
5756
|
try {
|
|
@@ -5266,6 +5829,17 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5266
5829
|
}
|
|
5267
5830
|
} };
|
|
5268
5831
|
},
|
|
5832
|
+
acquireRendererAsync: async () => {
|
|
5833
|
+
for (; ; ) {
|
|
5834
|
+
if (disposed) throw renderingAborted();
|
|
5835
|
+
const settling = pending;
|
|
5836
|
+
if (settling) {
|
|
5837
|
+
await settling.catch(() => void 0);
|
|
5838
|
+
continue;
|
|
5839
|
+
}
|
|
5840
|
+
return rendering.acquireRenderer();
|
|
5841
|
+
}
|
|
5842
|
+
},
|
|
5269
5843
|
releaseSceneResources: (release) => {
|
|
5270
5844
|
if (disposal) return disposal.then(release, release);
|
|
5271
5845
|
const previous = pending;
|
|
@@ -5349,7 +5923,7 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5349
5923
|
const type = CHARACTER_SCENE_TYPES[value.type ?? DEFAULT_CHARACTER_SCENE_TYPE_ID] ?? CHARACTER_SCENE_TYPES[DEFAULT_CHARACTER_SCENE_TYPE_ID];
|
|
5350
5924
|
currentSceneType = type.id;
|
|
5351
5925
|
const background2 = value.background === void 0 ? type.background : value.background;
|
|
5352
|
-
scene
|
|
5926
|
+
setSceneBaseBackground(scene, background2 == null ? null : new Color(background2));
|
|
5353
5927
|
camera.fov = value.cameraFov ?? 45;
|
|
5354
5928
|
camera.updateProjectionMatrix();
|
|
5355
5929
|
const preset2 = DEFAULT_CHARACTER_LIGHTING_PRESETS[value.lightingPreset ?? type.lightingPreset] ?? DEFAULT_CHARACTER_LIGHTING_PRESETS[DEFAULT_CHARACTER_LIGHTING_PRESET_ID];
|
|
@@ -5392,9 +5966,9 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
5392
5966
|
async function createDefaultCharacterSceneAsync(container, options = {}) {
|
|
5393
5967
|
const runtime = createDefaultCharacterSceneRuntime(container, options);
|
|
5394
5968
|
try {
|
|
5395
|
-
const
|
|
5396
|
-
if (!
|
|
5397
|
-
const handle =
|
|
5969
|
+
const snapshot3 = await runtime.ready;
|
|
5970
|
+
if (!snapshot3.renderer) throw new Error("Character scene renderer was not initialized.");
|
|
5971
|
+
const handle = snapshot3.backend === "webgl" ? { ...runtime, renderer: snapshot3.renderer, backend: "webgl" } : { ...runtime, renderer: snapshot3.renderer, backend: "webgpu" };
|
|
5398
5972
|
Object.defineProperties(handle, {
|
|
5399
5973
|
renderer: { get: () => runtime.renderer },
|
|
5400
5974
|
backend: { get: () => runtime.backend },
|
|
@@ -5481,7 +6055,14 @@ async function createCharacterHost(options) {
|
|
|
5481
6055
|
if (!rendering.getSnapshot().renderer) throw error;
|
|
5482
6056
|
}
|
|
5483
6057
|
check(signal);
|
|
5484
|
-
|
|
6058
|
+
const lease = await rendering.acquireRendererAsync();
|
|
6059
|
+
try {
|
|
6060
|
+
check(signal);
|
|
6061
|
+
return lease;
|
|
6062
|
+
} catch (error) {
|
|
6063
|
+
lease.release();
|
|
6064
|
+
throw error;
|
|
6065
|
+
}
|
|
5485
6066
|
};
|
|
5486
6067
|
const makeEngine = (value, character) => {
|
|
5487
6068
|
let profile = character.profile ?? options.embody?.profile;
|
|
@@ -5730,6 +6311,302 @@ async function createCharacterHost(options) {
|
|
|
5730
6311
|
throw error;
|
|
5731
6312
|
}
|
|
5732
6313
|
}
|
|
6314
|
+
var finite4 = (value) => typeof value === "number" && Number.isFinite(value);
|
|
6315
|
+
var identity2 = (value) => typeof value === "string" && value.trim().length > 0;
|
|
6316
|
+
var messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
6317
|
+
function copyLayout(layout) {
|
|
6318
|
+
if (layout === null) return { skybox: null, instances: [] };
|
|
6319
|
+
if (!layout || !Array.isArray(layout.instances)) throw new Error("Scenery requires an instances array.");
|
|
6320
|
+
const ids = /* @__PURE__ */ new Set();
|
|
6321
|
+
const instances = layout.instances.map((instance) => {
|
|
6322
|
+
if (!instance || !identity2(instance.instanceId) || !identity2(instance.assetId)) throw new Error("Each scenery placement requires an instanceId and assetId.");
|
|
6323
|
+
if (ids.has(instance.instanceId)) throw new Error(`Duplicate scenery instanceId: ${instance.instanceId}`);
|
|
6324
|
+
ids.add(instance.instanceId);
|
|
6325
|
+
const position = instance.position;
|
|
6326
|
+
if (!position || !finite4(position.x) || !finite4(position.y) || !finite4(position.z)) throw new Error(`Scenery placement ${instance.instanceId} requires finite X/Y/Z coordinates.`);
|
|
6327
|
+
return { instanceId: instance.instanceId, assetId: instance.assetId, position: { x: position.x, y: position.y, z: position.z } };
|
|
6328
|
+
});
|
|
6329
|
+
const skybox = layout.skybox;
|
|
6330
|
+
if (skybox !== null) {
|
|
6331
|
+
if (!skybox || !["skybox", "backdrop", "color"].includes(skybox.kind)) throw new Error("Scenery background kind must be skybox, backdrop or color.");
|
|
6332
|
+
if (skybox.assetId !== null && !identity2(skybox.assetId)) throw new Error("The scenery background assetId must be a nonempty string or null.");
|
|
6333
|
+
if (skybox.kind === "color" && skybox.assetId !== null) throw new Error("A solid scenery color cannot use an image asset.");
|
|
6334
|
+
if (typeof skybox.color !== "string" || !/^#[\da-f]{6}$/i.test(skybox.color)) throw new Error("Scenery background color must use six-digit #rrggbb.");
|
|
6335
|
+
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.");
|
|
6336
|
+
}
|
|
6337
|
+
return { instances, skybox: skybox === null ? null : { kind: skybox.kind, assetId: skybox.assetId, color: skybox.color, intensity: skybox.intensity, rotation: skybox.rotation } };
|
|
6338
|
+
}
|
|
6339
|
+
function disposeRoots(roots) {
|
|
6340
|
+
const geometries = /* @__PURE__ */ new Set(), materials2 = /* @__PURE__ */ new Set(), textures = /* @__PURE__ */ new Set();
|
|
6341
|
+
const skeletons = /* @__PURE__ */ new Set();
|
|
6342
|
+
for (const root of roots) root.traverse((object) => {
|
|
6343
|
+
if (object.geometry) geometries.add(object.geometry);
|
|
6344
|
+
if (object.skeleton) skeletons.add(object.skeleton);
|
|
6345
|
+
for (const material of object.material ? Array.isArray(object.material) ? object.material : [object.material] : []) {
|
|
6346
|
+
materials2.add(material);
|
|
6347
|
+
for (const value of Object.values(material)) if (value?.isTexture) textures.add(value);
|
|
6348
|
+
}
|
|
6349
|
+
});
|
|
6350
|
+
const images = /* @__PURE__ */ new Set();
|
|
6351
|
+
for (const texture of textures) {
|
|
6352
|
+
for (const image of Array.isArray(texture.image) ? texture.image : [texture.image]) if (image) images.add(image);
|
|
6353
|
+
texture.dispose();
|
|
6354
|
+
}
|
|
6355
|
+
for (const geometry of geometries) geometry.dispose();
|
|
6356
|
+
for (const material of materials2) material.dispose();
|
|
6357
|
+
for (const skeleton of skeletons) skeleton.dispose();
|
|
6358
|
+
for (const image of images) image.close?.();
|
|
6359
|
+
}
|
|
6360
|
+
function display(value, skybox) {
|
|
6361
|
+
const texture = "isTexture" in value ? value : null;
|
|
6362
|
+
if (texture) {
|
|
6363
|
+
texture.colorSpace = SRGBColorSpace;
|
|
6364
|
+
if (skybox.kind === "skybox") texture.mapping = EquirectangularReflectionMapping;
|
|
6365
|
+
}
|
|
6366
|
+
let disposed = false;
|
|
6367
|
+
return {
|
|
6368
|
+
value,
|
|
6369
|
+
texture,
|
|
6370
|
+
intensity: skybox.intensity,
|
|
6371
|
+
rotation: skybox.kind === "skybox" ? skybox.rotation : 0,
|
|
6372
|
+
dispose() {
|
|
6373
|
+
if (disposed) return;
|
|
6374
|
+
disposed = true;
|
|
6375
|
+
texture?.dispose();
|
|
6376
|
+
}
|
|
6377
|
+
};
|
|
6378
|
+
}
|
|
6379
|
+
function generatedSky(skybox) {
|
|
6380
|
+
const canvas = document.createElement("canvas");
|
|
6381
|
+
canvas.width = 1024;
|
|
6382
|
+
canvas.height = 512;
|
|
6383
|
+
const context = canvas.getContext("2d");
|
|
6384
|
+
if (!context) throw new Error("The browser could not create the scenery skybox.");
|
|
6385
|
+
const gradient = context.createLinearGradient(0, 0, 0, 512);
|
|
6386
|
+
gradient.addColorStop(0, "#10172c");
|
|
6387
|
+
gradient.addColorStop(0.48, skybox.color);
|
|
6388
|
+
gradient.addColorStop(0.52, "#b6aaa0");
|
|
6389
|
+
gradient.addColorStop(1, "#151c25");
|
|
6390
|
+
context.fillStyle = gradient;
|
|
6391
|
+
context.fillRect(0, 0, 1024, 512);
|
|
6392
|
+
return display(new CanvasTexture(canvas), skybox);
|
|
6393
|
+
}
|
|
6394
|
+
async function imageDisplay(asset, skybox) {
|
|
6395
|
+
if (!["image/png", "image/jpeg", "image/webp"].includes(asset.contentType)) throw new Error("Scenery backgrounds require a PNG, JPEG or WebP image.");
|
|
6396
|
+
const url = URL.createObjectURL(new Blob([asset.bytes], { type: asset.contentType }));
|
|
6397
|
+
let pending;
|
|
6398
|
+
try {
|
|
6399
|
+
const texture = await new Promise((resolve, reject) => {
|
|
6400
|
+
pending = new TextureLoader().load(url, resolve, void 0, reject);
|
|
6401
|
+
});
|
|
6402
|
+
return display(texture, skybox);
|
|
6403
|
+
} catch (error) {
|
|
6404
|
+
pending?.dispose();
|
|
6405
|
+
throw new Error(`Scenery background image could not decode: ${messageOf(error)}`);
|
|
6406
|
+
} finally {
|
|
6407
|
+
URL.revokeObjectURL(url);
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
async function parseModel(asset) {
|
|
6411
|
+
if (asset.contentType !== "model/gltf-binary" || asset.bytes.byteLength < 20) throw new Error("Scenery models require a self-contained GLB 2.0 file.");
|
|
6412
|
+
const header = new DataView(asset.bytes);
|
|
6413
|
+
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.");
|
|
6414
|
+
let offset = 12, document2;
|
|
6415
|
+
while (offset < asset.bytes.byteLength) {
|
|
6416
|
+
if (offset + 8 > asset.bytes.byteLength) throw new Error("The scenery GLB has a truncated chunk header.");
|
|
6417
|
+
const size = header.getUint32(offset, true), kind = header.getUint32(offset + 4, true);
|
|
6418
|
+
if (size % 4 || size > asset.bytes.byteLength - offset - 8) throw new Error("The scenery GLB has an invalid chunk length.");
|
|
6419
|
+
if (offset === 12 && kind !== 1313821514) throw new Error("The scenery GLB must begin with its JSON chunk.");
|
|
6420
|
+
if (kind === 1313821514) {
|
|
6421
|
+
if (document2) throw new Error("The scenery GLB contains duplicate JSON chunks.");
|
|
6422
|
+
try {
|
|
6423
|
+
document2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(asset.bytes, offset + 8, size)));
|
|
6424
|
+
} catch (error) {
|
|
6425
|
+
throw new Error(`The scenery GLB JSON is invalid: ${messageOf(error)}`);
|
|
6426
|
+
}
|
|
6427
|
+
if (!document2 || typeof document2 !== "object" || Array.isArray(document2)) throw new Error("The scenery GLB JSON must be an object.");
|
|
6428
|
+
}
|
|
6429
|
+
offset += 8 + size;
|
|
6430
|
+
}
|
|
6431
|
+
for (const resources of [document2?.buffers, document2?.images]) {
|
|
6432
|
+
if (resources !== void 0 && !Array.isArray(resources)) throw new Error("The scenery GLB buffers and images must be arrays.");
|
|
6433
|
+
for (const resource of resources ?? []) {
|
|
6434
|
+
if (resource?.uri !== void 0 && (typeof resource.uri !== "string" || !resource.uri.startsWith("data:"))) {
|
|
6435
|
+
throw new Error("Scenery GLB files must embed their geometry and textures; external URLs are not allowed.");
|
|
6436
|
+
}
|
|
6437
|
+
}
|
|
6438
|
+
}
|
|
6439
|
+
const manager = new LoadingManager();
|
|
6440
|
+
manager.setURLModifier((url) => {
|
|
6441
|
+
if (url.startsWith("data:") || url.startsWith("blob:")) return url;
|
|
6442
|
+
throw new Error("Scenery GLB files must embed their geometry and textures; external URLs are not allowed.");
|
|
6443
|
+
});
|
|
6444
|
+
const gltf = await new GLTFLoader(manager).parseAsync(asset.bytes, "");
|
|
6445
|
+
if (!gltf.scene?.isObject3D) throw new Error("The scenery GLB has no scene to display.");
|
|
6446
|
+
return [gltf.scene, ...gltf.scenes.filter((root) => root !== gltf.scene)];
|
|
6447
|
+
}
|
|
6448
|
+
function createSceneSceneryRenderer(options) {
|
|
6449
|
+
const { scene, rendering, loadAsset } = options;
|
|
6450
|
+
if (!scene?.isScene || typeof loadAsset !== "function") throw new Error("Scenery requires a Three scene and an asset loader.");
|
|
6451
|
+
let disposed = false, disposal = null;
|
|
6452
|
+
let backgroundKey = null, backgroundGeneration = 0;
|
|
6453
|
+
let backgroundLoad = null, currentDisplay = null;
|
|
6454
|
+
const models = /* @__PURE__ */ new Map(), tasks = /* @__PURE__ */ new Set();
|
|
6455
|
+
const report = (message) => {
|
|
6456
|
+
try {
|
|
6457
|
+
options.onError?.(message);
|
|
6458
|
+
} catch (error) {
|
|
6459
|
+
console.error("Scenery error subscriber failed.", error);
|
|
6460
|
+
}
|
|
6461
|
+
};
|
|
6462
|
+
const track = (task) => {
|
|
6463
|
+
tasks.add(task);
|
|
6464
|
+
void task.then(() => tasks.delete(task), (error) => {
|
|
6465
|
+
tasks.delete(task);
|
|
6466
|
+
report(messageOf(error));
|
|
6467
|
+
});
|
|
6468
|
+
return task;
|
|
6469
|
+
};
|
|
6470
|
+
const release = (callback) => {
|
|
6471
|
+
try {
|
|
6472
|
+
return rendering ? rendering.releaseSceneResources(callback) : (callback(), Promise.resolve());
|
|
6473
|
+
} catch (error) {
|
|
6474
|
+
return Promise.reject(error);
|
|
6475
|
+
}
|
|
6476
|
+
};
|
|
6477
|
+
const background = acquireSceneBackground(scene, () => {
|
|
6478
|
+
void dispose().catch((error) => report(messageOf(error)));
|
|
6479
|
+
});
|
|
6480
|
+
const retire = (model) => {
|
|
6481
|
+
if (model.retired) return Promise.resolve();
|
|
6482
|
+
model.retired = true;
|
|
6483
|
+
model.controller.abort();
|
|
6484
|
+
return release(() => {
|
|
6485
|
+
model.wrapper.removeFromParent();
|
|
6486
|
+
model.attached = false;
|
|
6487
|
+
disposeRoots(model.roots);
|
|
6488
|
+
model.roots = [];
|
|
6489
|
+
});
|
|
6490
|
+
};
|
|
6491
|
+
const mount = (instance) => {
|
|
6492
|
+
const wrapper = new Group();
|
|
6493
|
+
wrapper.name = `Scenery ${instance.instanceId}`;
|
|
6494
|
+
wrapper.position.set(instance.position.x, instance.position.y, instance.position.z);
|
|
6495
|
+
const model = { assetId: instance.assetId, wrapper, controller: new AbortController(), roots: [], attached: false, retired: false };
|
|
6496
|
+
models.set(instance.instanceId, model);
|
|
6497
|
+
track((async () => {
|
|
6498
|
+
let roots = [];
|
|
6499
|
+
try {
|
|
6500
|
+
const asset = await loadAsset(model.assetId, model.controller.signal);
|
|
6501
|
+
if (model.controller.signal.aborted) return;
|
|
6502
|
+
roots = await parseModel(asset);
|
|
6503
|
+
if (model.controller.signal.aborted || disposed) {
|
|
6504
|
+
disposeRoots(roots);
|
|
6505
|
+
roots = [];
|
|
6506
|
+
return;
|
|
6507
|
+
}
|
|
6508
|
+
await release(() => {
|
|
6509
|
+
if (model.controller.signal.aborted || disposed) {
|
|
6510
|
+
disposeRoots(roots);
|
|
6511
|
+
roots = [];
|
|
6512
|
+
return;
|
|
6513
|
+
}
|
|
6514
|
+
model.roots = roots;
|
|
6515
|
+
roots = [];
|
|
6516
|
+
wrapper.add(model.roots[0]);
|
|
6517
|
+
scene.add(wrapper);
|
|
6518
|
+
model.attached = true;
|
|
6519
|
+
});
|
|
6520
|
+
} catch (error) {
|
|
6521
|
+
if (roots.length) disposeRoots(roots);
|
|
6522
|
+
if (!model.controller.signal.aborted && !disposed) {
|
|
6523
|
+
if (models.get(instance.instanceId) === model) models.delete(instance.instanceId);
|
|
6524
|
+
await retire(model);
|
|
6525
|
+
report(`Scenery placement ${instance.instanceId} could not load: ${messageOf(error)}`);
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
})());
|
|
6529
|
+
};
|
|
6530
|
+
const updateBackground = (skybox) => {
|
|
6531
|
+
const key = JSON.stringify(skybox);
|
|
6532
|
+
if (backgroundKey === key) return;
|
|
6533
|
+
backgroundKey = key;
|
|
6534
|
+
const generation = ++backgroundGeneration;
|
|
6535
|
+
backgroundLoad?.abort();
|
|
6536
|
+
const controller = new AbortController();
|
|
6537
|
+
backgroundLoad = controller;
|
|
6538
|
+
track((async () => {
|
|
6539
|
+
let next = null;
|
|
6540
|
+
try {
|
|
6541
|
+
if (skybox) {
|
|
6542
|
+
if (skybox.kind === "color") next = display(new Color(skybox.color), skybox);
|
|
6543
|
+
else if (!skybox.assetId) next = generatedSky(skybox);
|
|
6544
|
+
else {
|
|
6545
|
+
const asset = await loadAsset(skybox.assetId, controller.signal);
|
|
6546
|
+
if (controller.signal.aborted) return;
|
|
6547
|
+
next = await imageDisplay(asset, skybox);
|
|
6548
|
+
}
|
|
6549
|
+
}
|
|
6550
|
+
await release(() => {
|
|
6551
|
+
if (controller.signal.aborted || generation !== backgroundGeneration || disposed) {
|
|
6552
|
+
next?.dispose();
|
|
6553
|
+
next = null;
|
|
6554
|
+
return;
|
|
6555
|
+
}
|
|
6556
|
+
if (next) background.set(next.value, next.intensity, next.rotation);
|
|
6557
|
+
else background.clear();
|
|
6558
|
+
const previous = currentDisplay;
|
|
6559
|
+
currentDisplay = next;
|
|
6560
|
+
next = null;
|
|
6561
|
+
previous?.dispose();
|
|
6562
|
+
});
|
|
6563
|
+
} catch (error) {
|
|
6564
|
+
next?.dispose();
|
|
6565
|
+
if (!controller.signal.aborted && generation === backgroundGeneration && !disposed) {
|
|
6566
|
+
backgroundKey = null;
|
|
6567
|
+
report(`Scenery background could not load: ${messageOf(error)}`);
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6570
|
+
})());
|
|
6571
|
+
};
|
|
6572
|
+
function dispose() {
|
|
6573
|
+
if (disposal) return disposal;
|
|
6574
|
+
disposed = true;
|
|
6575
|
+
backgroundGeneration++;
|
|
6576
|
+
backgroundLoad?.abort();
|
|
6577
|
+
background.dispose();
|
|
6578
|
+
const releases = [...models.values()].map(retire);
|
|
6579
|
+
models.clear();
|
|
6580
|
+
releases.push(release(() => {
|
|
6581
|
+
currentDisplay?.dispose();
|
|
6582
|
+
currentDisplay = null;
|
|
6583
|
+
}));
|
|
6584
|
+
disposal = (async () => {
|
|
6585
|
+
const results = await Promise.allSettled([...tasks, ...releases]);
|
|
6586
|
+
const failed = results.find((result) => result.status === "rejected");
|
|
6587
|
+
if (failed) throw failed.reason;
|
|
6588
|
+
})();
|
|
6589
|
+
return disposal;
|
|
6590
|
+
}
|
|
6591
|
+
return {
|
|
6592
|
+
update(value) {
|
|
6593
|
+
if (disposed) throw new Error("The scenery renderer has been disposed.");
|
|
6594
|
+
const layout = copyLayout(value);
|
|
6595
|
+
const next = new Map(layout.instances.map((instance) => [instance.instanceId, instance]));
|
|
6596
|
+
for (const [id, model] of models) if (next.get(id)?.assetId !== model.assetId) {
|
|
6597
|
+
models.delete(id);
|
|
6598
|
+
track(retire(model));
|
|
6599
|
+
}
|
|
6600
|
+
for (const instance of layout.instances) {
|
|
6601
|
+
const current = models.get(instance.instanceId);
|
|
6602
|
+
if (current) current.wrapper.position.set(instance.position.x, instance.position.y, instance.position.z);
|
|
6603
|
+
else mount(instance);
|
|
6604
|
+
}
|
|
6605
|
+
updateBackground(layout.skybox);
|
|
6606
|
+
},
|
|
6607
|
+
dispose
|
|
6608
|
+
};
|
|
6609
|
+
}
|
|
5733
6610
|
var COLORS = {
|
|
5734
6611
|
mapped: 4889258,
|
|
5735
6612
|
unmapped: 5925498,
|
|
@@ -5856,8 +6733,8 @@ function createOctahedralBoneGeometry() {
|
|
|
5856
6733
|
-w,
|
|
5857
6734
|
MID_POSITION
|
|
5858
6735
|
]);
|
|
5859
|
-
const geometry = new
|
|
5860
|
-
geometry.setAttribute("position", new
|
|
6736
|
+
const geometry = new THREE11.BufferGeometry();
|
|
6737
|
+
geometry.setAttribute("position", new THREE11.BufferAttribute(positions, 3));
|
|
5861
6738
|
geometry.computeVertexNormals();
|
|
5862
6739
|
return geometry;
|
|
5863
6740
|
}
|
|
@@ -5874,24 +6751,24 @@ function collectBones(root) {
|
|
|
5874
6751
|
});
|
|
5875
6752
|
return Array.from(bones);
|
|
5876
6753
|
}
|
|
5877
|
-
function
|
|
6754
|
+
function isBone3(obj) {
|
|
5878
6755
|
return !!obj && obj.isBone;
|
|
5879
6756
|
}
|
|
5880
6757
|
function countBoneSubtree(bone) {
|
|
5881
6758
|
let count = 1;
|
|
5882
6759
|
for (const child of bone.children) {
|
|
5883
|
-
if (
|
|
6760
|
+
if (isBone3(child)) {
|
|
5884
6761
|
count += countBoneSubtree(child);
|
|
5885
6762
|
}
|
|
5886
6763
|
}
|
|
5887
6764
|
return count;
|
|
5888
6765
|
}
|
|
5889
6766
|
function resolveTopLevelHelperRoot(root) {
|
|
5890
|
-
const topLevelBones = collectBones(root).filter((bone) => !
|
|
6767
|
+
const topLevelBones = collectBones(root).filter((bone) => !isBone3(bone.parent));
|
|
5891
6768
|
if (topLevelBones.length !== 1) return null;
|
|
5892
6769
|
let helperRoot = topLevelBones[0];
|
|
5893
6770
|
while (ROOT_HELPER_NAME_PATTERN.test(helperRoot.name)) {
|
|
5894
|
-
const boneChildren = helperRoot.children.filter(
|
|
6771
|
+
const boneChildren = helperRoot.children.filter(isBone3);
|
|
5895
6772
|
if (boneChildren.length !== 1) break;
|
|
5896
6773
|
const onlyChild = boneChildren[0];
|
|
5897
6774
|
if (!ROOT_HELPER_NAME_PATTERN.test(onlyChild.name)) break;
|
|
@@ -5902,7 +6779,7 @@ function resolveTopLevelHelperRoot(root) {
|
|
|
5902
6779
|
function resolveBodyDisplayRoot(root) {
|
|
5903
6780
|
const helperRoot = resolveTopLevelHelperRoot(root);
|
|
5904
6781
|
if (!helperRoot) return null;
|
|
5905
|
-
const boneChildren = helperRoot.children.filter(
|
|
6782
|
+
const boneChildren = helperRoot.children.filter(isBone3);
|
|
5906
6783
|
if (boneChildren.length === 0) return null;
|
|
5907
6784
|
if (boneChildren.length === 1) return boneChildren[0];
|
|
5908
6785
|
const subtreeSizes = boneChildren.map((child) => ({ child, size: countBoneSubtree(child) })).sort((left, right) => right.size - left.size);
|
|
@@ -5914,7 +6791,7 @@ function resolveBodyDisplayRoot(root) {
|
|
|
5914
6791
|
return null;
|
|
5915
6792
|
}
|
|
5916
6793
|
function collectBoneChildren(bone) {
|
|
5917
|
-
return bone.children.filter(
|
|
6794
|
+
return bone.children.filter(isBone3);
|
|
5918
6795
|
}
|
|
5919
6796
|
function hasZeroLengthConnection(parentBone, childBone) {
|
|
5920
6797
|
parentBone.getWorldPosition(_parentPos);
|
|
@@ -5923,7 +6800,7 @@ function hasZeroLengthConnection(parentBone, childBone) {
|
|
|
5923
6800
|
}
|
|
5924
6801
|
function hasRootHelperAncestor(bone) {
|
|
5925
6802
|
let current = bone.parent;
|
|
5926
|
-
while (
|
|
6803
|
+
while (isBone3(current)) {
|
|
5927
6804
|
if (ROOT_HELPER_NAME_PATTERN.test(current.name)) return true;
|
|
5928
6805
|
current = current.parent;
|
|
5929
6806
|
}
|
|
@@ -5933,7 +6810,7 @@ function shouldSkipRootHelperConnection(parentBone, boneChildren) {
|
|
|
5933
6810
|
if (boneChildren.length !== 1) return false;
|
|
5934
6811
|
const childBone = boneChildren[0];
|
|
5935
6812
|
if (ROOT_HELPER_NAME_PATTERN.test(parentBone.name)) {
|
|
5936
|
-
if (!
|
|
6813
|
+
if (!isBone3(parentBone.parent)) return true;
|
|
5937
6814
|
return collectBoneChildren(childBone).some((grandChild) => hasZeroLengthConnection(childBone, grandChild));
|
|
5938
6815
|
}
|
|
5939
6816
|
return hasRootHelperAncestor(parentBone) && HIP_NAME_PATTERN.test(parentBone.name) && PELVIS_NAME_PATTERN.test(childBone.name) && hasZeroLengthConnection(parentBone, childBone);
|
|
@@ -5946,20 +6823,20 @@ function setRayFromPointer(clientX, clientY, camera, domElement) {
|
|
|
5946
6823
|
_raycaster.setFromCamera(_mouse, camera);
|
|
5947
6824
|
return true;
|
|
5948
6825
|
}
|
|
5949
|
-
var _parentPos = new
|
|
5950
|
-
var _childPos = new
|
|
5951
|
-
var _direction = new
|
|
5952
|
-
var _zAxis = new
|
|
5953
|
-
var _quaternion = new
|
|
5954
|
-
var _bonePos = new
|
|
5955
|
-
var _boneQuat = new
|
|
5956
|
-
var _boneEuler = new
|
|
5957
|
-
var _mouse = new
|
|
5958
|
-
var _raycaster = new
|
|
5959
|
-
var _cameraSpacePosition = new
|
|
5960
|
-
var _planeNormal = new
|
|
5961
|
-
var _dragPlane = new
|
|
5962
|
-
var _planeHit = new
|
|
6826
|
+
var _parentPos = new THREE11.Vector3();
|
|
6827
|
+
var _childPos = new THREE11.Vector3();
|
|
6828
|
+
var _direction = new THREE11.Vector3();
|
|
6829
|
+
var _zAxis = new THREE11.Vector3(0, 0, 1);
|
|
6830
|
+
var _quaternion = new THREE11.Quaternion();
|
|
6831
|
+
var _bonePos = new THREE11.Vector3();
|
|
6832
|
+
var _boneQuat = new THREE11.Quaternion();
|
|
6833
|
+
var _boneEuler = new THREE11.Euler();
|
|
6834
|
+
var _mouse = new THREE11.Vector2();
|
|
6835
|
+
var _raycaster = new THREE11.Raycaster();
|
|
6836
|
+
var _cameraSpacePosition = new THREE11.Vector3();
|
|
6837
|
+
var _planeNormal = new THREE11.Vector3();
|
|
6838
|
+
var _dragPlane = new THREE11.Plane();
|
|
6839
|
+
var _planeHit = new THREE11.Vector3();
|
|
5963
6840
|
function resolveSkeletonOverlayStartBoneName(root) {
|
|
5964
6841
|
return resolveBodyDisplayRoot(root)?.name ?? null;
|
|
5965
6842
|
}
|
|
@@ -6006,9 +6883,9 @@ var SkeletonOverlay = class {
|
|
|
6006
6883
|
this.scene = scene;
|
|
6007
6884
|
this.mappedBoneNames = mappedBoneNames;
|
|
6008
6885
|
this.semanticKeyMap = semanticKeyMap;
|
|
6009
|
-
this.opacityScale =
|
|
6886
|
+
this.opacityScale = THREE11.MathUtils.clamp(options.opacityScale ?? 1, 0, 1);
|
|
6010
6887
|
this.focusSelectedBone = options.focusSelectedBone ?? false;
|
|
6011
|
-
this.group = new
|
|
6888
|
+
this.group = new THREE11.Group();
|
|
6012
6889
|
this.group.name = "SkeletonOverlay";
|
|
6013
6890
|
this.group.renderOrder = 999;
|
|
6014
6891
|
this.group.visible = false;
|
|
@@ -6017,50 +6894,50 @@ var SkeletonOverlay = class {
|
|
|
6017
6894
|
transparent: true,
|
|
6018
6895
|
depthTest: false,
|
|
6019
6896
|
depthWrite: false,
|
|
6020
|
-
side:
|
|
6897
|
+
side: THREE11.DoubleSide
|
|
6021
6898
|
};
|
|
6022
|
-
this.mappedMaterial = new
|
|
6023
|
-
this.unmappedMaterial = new
|
|
6024
|
-
this.highlightMaterial = new
|
|
6025
|
-
this.selectedMaterial = new
|
|
6026
|
-
this.contextMaterial = new
|
|
6027
|
-
this.mutedMaterial = new
|
|
6899
|
+
this.mappedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.mapped, opacity: OPACITY.mapped });
|
|
6900
|
+
this.unmappedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.unmapped, opacity: OPACITY.unmapped });
|
|
6901
|
+
this.highlightMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.highlighted, opacity: OPACITY.highlighted });
|
|
6902
|
+
this.selectedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.selected, opacity: OPACITY.selected });
|
|
6903
|
+
this.contextMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.selected, opacity: OPACITY.context });
|
|
6904
|
+
this.mutedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.unmapped, opacity: OPACITY.muted });
|
|
6028
6905
|
this.mappedMaterial.forceSinglePass = true;
|
|
6029
6906
|
this.unmappedMaterial.forceSinglePass = true;
|
|
6030
6907
|
this.highlightMaterial.forceSinglePass = true;
|
|
6031
6908
|
this.selectedMaterial.forceSinglePass = true;
|
|
6032
6909
|
this.contextMaterial.forceSinglePass = true;
|
|
6033
6910
|
this.mutedMaterial.forceSinglePass = true;
|
|
6034
|
-
this.axisRingGeometry = new
|
|
6035
|
-
this.axisRingHitGeometry = new
|
|
6036
|
-
this.axisCenterGeometry = new
|
|
6037
|
-
this.pitchRingMaterial = new
|
|
6038
|
-
this.yawRingMaterial = new
|
|
6039
|
-
this.rollRingMaterial = new
|
|
6040
|
-
this.axisHitMaterial = new
|
|
6911
|
+
this.axisRingGeometry = new THREE11.TorusGeometry(1, 0.032, 10, 120);
|
|
6912
|
+
this.axisRingHitGeometry = new THREE11.TorusGeometry(1, AXIS_GIZMO_HIT_TUBE, 8, 64);
|
|
6913
|
+
this.axisCenterGeometry = new THREE11.SphereGeometry(0.082, 12, 8);
|
|
6914
|
+
this.pitchRingMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.pitch, opacity: AXIS_GIZMO_IDLE_OPACITY });
|
|
6915
|
+
this.yawRingMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.yaw, opacity: AXIS_GIZMO_IDLE_OPACITY });
|
|
6916
|
+
this.rollRingMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.roll, opacity: AXIS_GIZMO_IDLE_OPACITY });
|
|
6917
|
+
this.axisHitMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, opacity: 0 });
|
|
6041
6918
|
this.axisHitMaterial.colorWrite = false;
|
|
6042
|
-
this.axisCenterMaterial = new
|
|
6043
|
-
this.axisGizmoGroup = new
|
|
6919
|
+
this.axisCenterMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: 16119802, opacity: 0.92 });
|
|
6920
|
+
this.axisGizmoGroup = new THREE11.Group();
|
|
6044
6921
|
this.axisGizmoGroup.name = "SelectedBoneAxisGizmo";
|
|
6045
6922
|
this.axisGizmoGroup.visible = false;
|
|
6046
6923
|
this.axisGizmoGroup.renderOrder = 1e3;
|
|
6047
|
-
const pitchRing = new
|
|
6924
|
+
const pitchRing = new THREE11.Mesh(this.axisRingGeometry, this.pitchRingMaterial);
|
|
6048
6925
|
pitchRing.renderOrder = 1e3;
|
|
6049
6926
|
pitchRing.userData.axis = "pitch";
|
|
6050
|
-
const pitchHit = new
|
|
6927
|
+
const pitchHit = new THREE11.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
|
|
6051
6928
|
pitchHit.userData.axis = "pitch";
|
|
6052
|
-
const yawRing = new
|
|
6929
|
+
const yawRing = new THREE11.Mesh(this.axisRingGeometry, this.yawRingMaterial);
|
|
6053
6930
|
yawRing.renderOrder = 1e3;
|
|
6054
6931
|
yawRing.userData.axis = "yaw";
|
|
6055
|
-
const yawHit = new
|
|
6932
|
+
const yawHit = new THREE11.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
|
|
6056
6933
|
yawHit.userData.axis = "yaw";
|
|
6057
|
-
const rollRing = new
|
|
6934
|
+
const rollRing = new THREE11.Mesh(this.axisRingGeometry, this.rollRingMaterial);
|
|
6058
6935
|
rollRing.renderOrder = 1e3;
|
|
6059
6936
|
rollRing.userData.axis = "roll";
|
|
6060
|
-
const rollHit = new
|
|
6937
|
+
const rollHit = new THREE11.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
|
|
6061
6938
|
rollHit.rotation.copy(rollRing.rotation);
|
|
6062
6939
|
rollHit.userData.axis = "roll";
|
|
6063
|
-
const center = new
|
|
6940
|
+
const center = new THREE11.Mesh(this.axisCenterGeometry, this.axisCenterMaterial);
|
|
6064
6941
|
center.renderOrder = 1001;
|
|
6065
6942
|
this.axisGizmoMeshes = {
|
|
6066
6943
|
pitch: pitchRing,
|
|
@@ -6100,11 +6977,11 @@ var SkeletonOverlay = class {
|
|
|
6100
6977
|
this.boneTree = allBones.map((bone) => {
|
|
6101
6978
|
const isMapped = this.mappedBoneNames.has(bone.name);
|
|
6102
6979
|
const semanticKey = this.semanticKeyMap.get(bone.name) ?? null;
|
|
6103
|
-
const boneChildren = bone.children.filter((c) =>
|
|
6104
|
-
const parentName = bone.parent &&
|
|
6980
|
+
const boneChildren = bone.children.filter((c) => isBone3(c) && includedBones.has(c)).map((c) => c.name);
|
|
6981
|
+
const parentName = bone.parent && isBone3(bone.parent) && includedBones.has(bone.parent) ? bone.parent.name : null;
|
|
6105
6982
|
let depth = 0;
|
|
6106
6983
|
let current = bone.parent;
|
|
6107
|
-
while (current &&
|
|
6984
|
+
while (current && isBone3(current) && includedBones.has(current)) {
|
|
6108
6985
|
depth++;
|
|
6109
6986
|
current = current.parent;
|
|
6110
6987
|
}
|
|
@@ -6120,7 +6997,7 @@ var SkeletonOverlay = class {
|
|
|
6120
6997
|
});
|
|
6121
6998
|
for (const bone of allBones) {
|
|
6122
6999
|
const boneChildren = bone.children.filter(
|
|
6123
|
-
(c) =>
|
|
7000
|
+
(c) => isBone3(c) && includedBones.has(c)
|
|
6124
7001
|
);
|
|
6125
7002
|
if (boneChildren.length > 0) {
|
|
6126
7003
|
if (shouldSkipRootHelperConnection(bone, boneChildren)) {
|
|
@@ -6134,15 +7011,15 @@ var SkeletonOverlay = class {
|
|
|
6134
7011
|
}
|
|
6135
7012
|
}
|
|
6136
7013
|
this.group.add(this.axisGizmoGroup);
|
|
6137
|
-
const sentinelGeo = new
|
|
6138
|
-
const sentinelMat = new
|
|
7014
|
+
const sentinelGeo = new THREE11.SphereGeometry(1e-6, 4, 2);
|
|
7015
|
+
const sentinelMat = new THREE11.MeshBasicMaterial({
|
|
6139
7016
|
transparent: true,
|
|
6140
7017
|
opacity: 0,
|
|
6141
7018
|
depthWrite: false,
|
|
6142
7019
|
depthTest: false
|
|
6143
7020
|
});
|
|
6144
7021
|
sentinelMat.colorWrite = false;
|
|
6145
|
-
const sentinel = new
|
|
7022
|
+
const sentinel = new THREE11.Mesh(sentinelGeo, sentinelMat);
|
|
6146
7023
|
sentinel.visible = true;
|
|
6147
7024
|
sentinel.frustumCulled = false;
|
|
6148
7025
|
sentinel.onBeforeRender = (renderer, _scene, camera) => this.update(camera, renderer.domElement);
|
|
@@ -6154,7 +7031,7 @@ var SkeletonOverlay = class {
|
|
|
6154
7031
|
createBoneMesh(parentBone, childBone) {
|
|
6155
7032
|
const isMapped = this.mappedBoneNames.has(parentBone.name);
|
|
6156
7033
|
const material = isMapped ? this.mappedMaterial : this.unmappedMaterial;
|
|
6157
|
-
const mesh = new
|
|
7034
|
+
const mesh = new THREE11.Mesh(this.sharedGeometry, material);
|
|
6158
7035
|
mesh.name = `bone_vis_${parentBone.name}${childBone ? `_${childBone.name}` : "_leaf"}`;
|
|
6159
7036
|
mesh.frustumCulled = false;
|
|
6160
7037
|
mesh.renderOrder = 999;
|
|
@@ -6234,20 +7111,20 @@ var SkeletonOverlay = class {
|
|
|
6234
7111
|
bone.getWorldPosition(_parentPos);
|
|
6235
7112
|
let referenceLength = 0;
|
|
6236
7113
|
for (const child of bone.children) {
|
|
6237
|
-
if (!
|
|
7114
|
+
if (!isBone3(child)) continue;
|
|
6238
7115
|
child.getWorldPosition(_childPos);
|
|
6239
7116
|
const nextDistance = _parentPos.distanceTo(_childPos);
|
|
6240
7117
|
if (nextDistance <= 1e-4) continue;
|
|
6241
7118
|
referenceLength = referenceLength === 0 ? nextDistance : Math.min(referenceLength, nextDistance);
|
|
6242
7119
|
}
|
|
6243
|
-
if (referenceLength <= 1e-4 &&
|
|
7120
|
+
if (referenceLength <= 1e-4 && isBone3(bone.parent)) {
|
|
6244
7121
|
bone.parent.getWorldPosition(_childPos);
|
|
6245
7122
|
referenceLength = _parentPos.distanceTo(_childPos);
|
|
6246
7123
|
}
|
|
6247
7124
|
if (referenceLength <= 1e-4) {
|
|
6248
7125
|
referenceLength = LEAF_BONE_LENGTH;
|
|
6249
7126
|
}
|
|
6250
|
-
return
|
|
7127
|
+
return THREE11.MathUtils.clamp(
|
|
6251
7128
|
referenceLength * AXIS_GIZMO_RADIUS_FACTOR,
|
|
6252
7129
|
AXIS_GIZMO_MIN_RADIUS,
|
|
6253
7130
|
AXIS_GIZMO_MAX_RADIUS
|
|
@@ -6263,7 +7140,7 @@ var SkeletonOverlay = class {
|
|
|
6263
7140
|
_cameraSpacePosition.copy(origin).applyMatrix4(perspectiveCamera.matrixWorldInverse);
|
|
6264
7141
|
const depth = -_cameraSpacePosition.z;
|
|
6265
7142
|
if (!Number.isFinite(depth) || depth <= perspectiveCamera.near) return null;
|
|
6266
|
-
const visibleWorldHeight = 2 * depth * Math.tan(
|
|
7143
|
+
const visibleWorldHeight = 2 * depth * Math.tan(THREE11.MathUtils.degToRad(perspectiveCamera.getEffectiveFOV()) / 2);
|
|
6267
7144
|
return visibleWorldHeight * (AXIS_GIZMO_SCREEN_RADIUS_PX / viewportHeight);
|
|
6268
7145
|
}
|
|
6269
7146
|
if (camera.isOrthographicCamera) {
|
|
@@ -6394,9 +7271,9 @@ var SkeletonOverlay = class {
|
|
|
6394
7271
|
return {
|
|
6395
7272
|
position: [_bonePos.x, _bonePos.y, _bonePos.z],
|
|
6396
7273
|
rotation: [
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
7274
|
+
THREE11.MathUtils.radToDeg(_boneEuler.x),
|
|
7275
|
+
THREE11.MathUtils.radToDeg(_boneEuler.y),
|
|
7276
|
+
THREE11.MathUtils.radToDeg(_boneEuler.z)
|
|
6400
7277
|
]
|
|
6401
7278
|
};
|
|
6402
7279
|
}
|
|
@@ -6666,7 +7543,7 @@ function buildMorphVertexPreviewRenderData(meshes, morphName, cache, options = {
|
|
|
6666
7543
|
if (pointCount === 0) return null;
|
|
6667
7544
|
const positions = previous?.positions.length === pointCount * 3 ? previous.positions : new Float32Array(pointCount * 3);
|
|
6668
7545
|
const colors = previous?.colors.length === pointCount * 3 ? previous.colors : new Float32Array(pointCount * 3);
|
|
6669
|
-
const vertex = new
|
|
7546
|
+
const vertex = new THREE11.Vector3();
|
|
6670
7547
|
let cursor = 0;
|
|
6671
7548
|
for (const { mesh, sample } of sampledMeshes) {
|
|
6672
7549
|
mesh.updateWorldMatrix(true, false);
|
|
@@ -6760,7 +7637,7 @@ var MorphVertexPreview = class {
|
|
|
6760
7637
|
this.points = null;
|
|
6761
7638
|
}
|
|
6762
7639
|
createPoints() {
|
|
6763
|
-
const points = new
|
|
7640
|
+
const points = new THREE11.Points(new THREE11.BufferGeometry(), new THREE11.PointsMaterial({
|
|
6764
7641
|
size: 0.012,
|
|
6765
7642
|
sizeAttenuation: true,
|
|
6766
7643
|
transparent: true,
|
|
@@ -6798,8 +7675,8 @@ var MorphVertexPreview = class {
|
|
|
6798
7675
|
const color = geometry.getAttribute("color");
|
|
6799
7676
|
if (!position || !color || position.count !== data.pointCount || color.count !== data.pointCount) {
|
|
6800
7677
|
if (position || color) geometry.dispose();
|
|
6801
|
-
geometry.setAttribute("position", new
|
|
6802
|
-
geometry.setAttribute("color", new
|
|
7678
|
+
geometry.setAttribute("position", new THREE11.BufferAttribute(data.positions, 3));
|
|
7679
|
+
geometry.setAttribute("color", new THREE11.BufferAttribute(data.colors, 3));
|
|
6803
7680
|
return;
|
|
6804
7681
|
}
|
|
6805
7682
|
if (position.array !== data.positions) position.array.set(data.positions);
|
|
@@ -6859,7 +7736,7 @@ var transform = (obj) => ({
|
|
|
6859
7736
|
rotation: { x: obj.quaternion.x, y: obj.quaternion.y, z: obj.quaternion.z, w: obj.quaternion.w },
|
|
6860
7737
|
scale: { x: obj.scale.x, y: obj.scale.y, z: obj.scale.z }
|
|
6861
7738
|
});
|
|
6862
|
-
var
|
|
7739
|
+
var snapshot2 = (obj, reference) => {
|
|
6863
7740
|
if (!reference) {
|
|
6864
7741
|
return {
|
|
6865
7742
|
obj,
|
|
@@ -6909,6 +7786,7 @@ var ThreeModelInspector = class {
|
|
|
6909
7786
|
const allMeshes = [];
|
|
6910
7787
|
const boneObjects = [];
|
|
6911
7788
|
model.traverse((obj) => {
|
|
7789
|
+
if (highlightOverlays.has(obj)) return;
|
|
6912
7790
|
objects.push(obj);
|
|
6913
7791
|
if (obj.isMesh) allMeshes.push(obj);
|
|
6914
7792
|
if (obj.isBone || obj.type === "Bone") boneObjects.push(obj);
|
|
@@ -6944,7 +7822,7 @@ var ThreeModelInspector = class {
|
|
|
6944
7822
|
}
|
|
6945
7823
|
const bones = {};
|
|
6946
7824
|
for (const bone of boneObjects) {
|
|
6947
|
-
if (bone.name) bones[bone.name] =
|
|
7825
|
+
if (bone.name) bones[bone.name] = snapshot2(bone, references?.get(bone));
|
|
6948
7826
|
}
|
|
6949
7827
|
const profile = options.profile;
|
|
6950
7828
|
if (profile?.boneNodes) {
|
|
@@ -6953,7 +7831,7 @@ var ThreeModelInspector = class {
|
|
|
6953
7831
|
const suffix = profile.boneSuffix ?? "";
|
|
6954
7832
|
const name = `${base.startsWith(prefix) ? "" : prefix}${base}${base.endsWith(suffix) ? "" : suffix}`;
|
|
6955
7833
|
const object = model.getObjectByName(name) ?? model.getObjectByName(base);
|
|
6956
|
-
if (object) bones[key] = bones[object.name] ??
|
|
7834
|
+
if (object) bones[key] = bones[object.name] ?? snapshot2(object, references?.get(object));
|
|
6957
7835
|
}
|
|
6958
7836
|
}
|
|
6959
7837
|
return {
|
|
@@ -7135,15 +8013,17 @@ function serializeAnimationClips(model, clips, inspection) {
|
|
|
7135
8013
|
});
|
|
7136
8014
|
}
|
|
7137
8015
|
var materials = (value) => value ? Array.isArray(value) ? value : [value] : [];
|
|
8016
|
+
var highlightOverlays = /* @__PURE__ */ new WeakSet();
|
|
7138
8017
|
var ThreeFrameApplier = class {
|
|
7139
8018
|
constructor() {
|
|
7140
8019
|
__publicField(this, "meshes", /* @__PURE__ */ new Map());
|
|
7141
8020
|
__publicField(this, "morphs", /* @__PURE__ */ new Map());
|
|
7142
8021
|
__publicField(this, "bones", /* @__PURE__ */ new Map());
|
|
7143
8022
|
__publicField(this, "objects", /* @__PURE__ */ new Map());
|
|
7144
|
-
__publicField(this, "
|
|
8023
|
+
__publicField(this, "highlighted", /* @__PURE__ */ new Map());
|
|
7145
8024
|
}
|
|
7146
8025
|
setBindings(inspection) {
|
|
8026
|
+
this.clearMeshHighlights();
|
|
7147
8027
|
this.meshes = new Map(inspection.meshBindings);
|
|
7148
8028
|
this.morphs = new Map(inspection.morphBindings);
|
|
7149
8029
|
this.bones = new Map(inspection.boneBindings);
|
|
@@ -7189,7 +8069,7 @@ var ThreeFrameApplier = class {
|
|
|
7189
8069
|
const config = configs[object.name];
|
|
7190
8070
|
if (!config) return;
|
|
7191
8071
|
if (typeof config.visible === "boolean") object.visible = config.visible;
|
|
7192
|
-
if (config.material) this.
|
|
8072
|
+
if (config.material) this.applyHighlightedMeshMaterial(object, config.material);
|
|
7193
8073
|
});
|
|
7194
8074
|
}
|
|
7195
8075
|
getMeshMaterialConfig(root, name) {
|
|
@@ -7197,38 +8077,97 @@ var ThreeFrameApplier = class {
|
|
|
7197
8077
|
this.visitMesh(root, name, (mesh) => {
|
|
7198
8078
|
const material = materials(mesh.material)[0];
|
|
7199
8079
|
if (!material) return;
|
|
7200
|
-
const blending = Object.
|
|
8080
|
+
const blending = Object.keys(THREE_BLENDING_MODES).find((key) => THREE_BLENDING_MODES[key] === material.blending) ?? "Normal";
|
|
7201
8081
|
result = { renderOrder: mesh.renderOrder, transparent: material.transparent, opacity: material.opacity, depthWrite: material.depthWrite, depthTest: material.depthTest, blending };
|
|
7202
8082
|
});
|
|
7203
8083
|
return result;
|
|
7204
8084
|
}
|
|
7205
8085
|
setMeshMaterialConfig(root, name, config) {
|
|
7206
|
-
this.visitMesh(root, name, (mesh) => this.
|
|
8086
|
+
this.visitMesh(root, name, (mesh) => this.applyHighlightedMeshMaterial(mesh, config));
|
|
8087
|
+
}
|
|
8088
|
+
applyHighlightedMeshMaterial(mesh, config) {
|
|
8089
|
+
this.applyMaterial(mesh, config);
|
|
8090
|
+
const original = this.highlighted.get(mesh)?.original;
|
|
8091
|
+
if (!original) return;
|
|
8092
|
+
const highlighted = mesh.material;
|
|
8093
|
+
try {
|
|
8094
|
+
mesh.material = original;
|
|
8095
|
+
this.applyMaterial(mesh, config);
|
|
8096
|
+
} finally {
|
|
8097
|
+
mesh.material = highlighted;
|
|
8098
|
+
}
|
|
7207
8099
|
}
|
|
7208
8100
|
setMeshVisible(root, name, visible) {
|
|
7209
8101
|
this.visitMesh(root, name, (mesh) => {
|
|
7210
8102
|
mesh.visible = visible;
|
|
7211
8103
|
});
|
|
7212
8104
|
}
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
8105
|
+
/** Apply a complete Rust snapshot. Only native resources and ID bindings live here. */
|
|
8106
|
+
applyMeshHighlightFrame(frame) {
|
|
8107
|
+
if (frame.version !== 1) throw new Error(`Unsupported mesh highlight frame version: ${frame.version}`);
|
|
8108
|
+
const selected = new Set(frame.highlights.map((write) => this.meshes.get(write.meshId)));
|
|
8109
|
+
for (const mesh of this.highlighted.keys()) {
|
|
8110
|
+
if (!selected.has(mesh)) this.clearMeshHighlight(mesh);
|
|
8111
|
+
}
|
|
8112
|
+
for (const write of frame.highlights) {
|
|
8113
|
+
if (write.edgeMode !== "triangles") throw new Error(`Unsupported highlight edge mode: ${write.edgeMode}`);
|
|
8114
|
+
const mesh = this.meshes.get(write.meshId);
|
|
8115
|
+
if (!mesh) continue;
|
|
8116
|
+
let state = this.highlighted.get(mesh);
|
|
8117
|
+
if (!state) {
|
|
8118
|
+
const original = mesh.material;
|
|
8119
|
+
const copies = materials(original).map((material) => {
|
|
8120
|
+
const copy2 = material.clone();
|
|
8121
|
+
copy2.polygonOffset = true;
|
|
8122
|
+
copy2.polygonOffsetFactor = 1;
|
|
8123
|
+
copy2.polygonOffsetUnits = 1;
|
|
8124
|
+
return copy2;
|
|
8125
|
+
});
|
|
8126
|
+
mesh.material = Array.isArray(original) ? copies : copies[0];
|
|
8127
|
+
const overlay = mesh.clone(false);
|
|
8128
|
+
overlay.name = "";
|
|
8129
|
+
overlay.userData = {};
|
|
8130
|
+
overlay.position.set(0, 0, 0);
|
|
8131
|
+
overlay.quaternion.identity();
|
|
8132
|
+
overlay.scale.set(1, 1, 1);
|
|
8133
|
+
overlay.matrix.identity();
|
|
8134
|
+
overlay.matrixAutoUpdate = true;
|
|
8135
|
+
overlay.visible = true;
|
|
8136
|
+
overlay.castShadow = overlay.receiveShadow = false;
|
|
8137
|
+
overlay.raycast = () => {
|
|
8138
|
+
};
|
|
8139
|
+
overlay.material = new MeshBasicMaterial({ wireframe: write.edgeMode === "triangles", transparent: true, depthWrite: false, toneMapped: false });
|
|
8140
|
+
overlay.onBeforeRender = () => {
|
|
8141
|
+
overlay.geometry = mesh.geometry;
|
|
8142
|
+
overlay.morphTargetInfluences = mesh.morphTargetInfluences;
|
|
8143
|
+
};
|
|
8144
|
+
highlightOverlays.add(overlay);
|
|
8145
|
+
mesh.add(overlay);
|
|
8146
|
+
state = { original, overlay };
|
|
8147
|
+
this.highlighted.set(mesh, state);
|
|
8148
|
+
}
|
|
8149
|
+
for (const material of materials(mesh.material)) {
|
|
7217
8150
|
if (!material.emissive) continue;
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
if (!this.originalEmissive.has(key)) this.originalEmissive.set(key, { color: material.emissive.getHex(), intensity: material.emissiveIntensity ?? 0 });
|
|
7221
|
-
material.emissive.setHex(color);
|
|
7222
|
-
material.emissiveIntensity = intensity2;
|
|
7223
|
-
} else {
|
|
7224
|
-
const original = this.originalEmissive.get(key);
|
|
7225
|
-
if (original) {
|
|
7226
|
-
material.emissive.setHex(original.color);
|
|
7227
|
-
material.emissiveIntensity = original.intensity;
|
|
7228
|
-
}
|
|
7229
|
-
}
|
|
8151
|
+
material.emissive.setRGB(...write.fillColor);
|
|
8152
|
+
material.emissiveIntensity = write.fillIntensity;
|
|
7230
8153
|
}
|
|
7231
|
-
|
|
8154
|
+
const edges = state.overlay.material;
|
|
8155
|
+
edges.color.setRGB(...write.edgeColor);
|
|
8156
|
+
edges.opacity = write.edgeOpacity;
|
|
8157
|
+
edges.side = materials(mesh.material)[0]?.side ?? edges.side;
|
|
8158
|
+
state.overlay.renderOrder = mesh.renderOrder + 1;
|
|
8159
|
+
}
|
|
8160
|
+
}
|
|
8161
|
+
clearMeshHighlights() {
|
|
8162
|
+
for (const mesh of this.highlighted.keys()) this.clearMeshHighlight(mesh);
|
|
8163
|
+
}
|
|
8164
|
+
clearMeshHighlight(mesh) {
|
|
8165
|
+
const state = this.highlighted.get(mesh);
|
|
8166
|
+
state.overlay.removeFromParent();
|
|
8167
|
+
state.overlay.material.dispose();
|
|
8168
|
+
for (const material of materials(mesh.material)) material.dispose();
|
|
8169
|
+
mesh.material = state.original;
|
|
8170
|
+
this.highlighted.delete(mesh);
|
|
7232
8171
|
}
|
|
7233
8172
|
applyHairAppearance(meshes, appearance) {
|
|
7234
8173
|
for (const mesh of meshes) {
|
|
@@ -7389,11 +8328,11 @@ var ThreeFrameApplier = class {
|
|
|
7389
8328
|
var collectMorphMeshes = (root) => {
|
|
7390
8329
|
const meshes = [];
|
|
7391
8330
|
root.traverse((object) => {
|
|
7392
|
-
if (object.isMesh && morphEntries(object).length > 0) meshes.push(object);
|
|
8331
|
+
if (object.isMesh && !highlightOverlays.has(object) && morphEntries(object).length > 0) meshes.push(object);
|
|
7393
8332
|
});
|
|
7394
8333
|
return meshes;
|
|
7395
8334
|
};
|
|
7396
8335
|
|
|
7397
|
-
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 };
|
|
8336
|
+
export { BLENDING_MODES, CHARACTER_SCENE_TYPES, CHARACTER_SCENE_TYPE_IDS, ThreeAnnotationController as CameraController, CameraDOMControls, CameraDOMControls as Controls, DEFAULT_CHARACTER_LIGHTING_PRESETS, DEFAULT_CHARACTER_LIGHTING_PRESET_ID, DEFAULT_CHARACTER_LIGHTING_PRESET_IDS, DEFAULT_CHARACTER_LIGHTING_SETTINGS, DEFAULT_CHARACTER_SCENE_TYPE_ID, DEFAULT_MARKER_AUTO_HIDE_MS, DEFAULT_PREVIEW_AUTO_CLEAR_MS, Embody, HtmlAnnotationMarkers, ThreeAnnotationMarkers as Markers3D, HtmlAnnotationMarkers as MarkersHTML, MorphVertexPreview, HtmlAnnotationMarkers as OverlayAnnotationMarkers, RUNTIME_ANNOTATION_REGION_PREFIX, RustAnnotationCameraCore, SkeletonOverlay, THREE_BLENDING_MODES, ThreeAnnotationController, ThreeAnnotationMarkers, ThreeAnnotations, ThreeFrameApplier, ThreeModelInspector, ThreeNativePlaybackConnector, analyzeModel, applyAUBoneBindingUpdate, applyBilateralAxisBindingUpdate, applyBoneAxisBindingUpdate, applyCharacterModelTransform, bindModelReferencePose, boneResolutionProfile, buildBoneAuOptions, buildMappedBoneInfo, buildMorphVertexPreviewRenderData, buildMorphVertexPreviewSample, buildRegionDisplayOptions, captureModelReferencePose, classifyAuAsJointControl, collectMorphMeshes, collectSkeletonBones, computeHumanoidSkeletonTemplateRestBounds, convertMixamoFbxToVrmAnimations, convertMixamoToVrmAnimation, createAnimationClipFromClipIR, createAnimationClipFromVrmAnimation, createBilateralBoneAxisAu, createBoneAxisAu, createCharacterHost, createDefaultCharacterLighting, createDefaultCharacterScene, createDefaultCharacterSceneAsync, createDefaultCharacterSceneRuntime, createMarkerLifecycleRevealOptions, createMarkerVisibilityLifecycle, createRuntimeAnnotationPreviewLifecycle, createRustAnnotationCameraCore, createRustEmbodyHost, createRustEmbodyHostUnbound, createScenePlaybackFrame, createSceneSceneryRenderer, createShadowPlane, createValidationSkeletonFromHumanoidTemplate, detectAnnotationLaterality, disposeCharacterModel, ensureBilateralBoneNodeKeys, ensureBoneNodeKey, extendCharacterConfigWithPreset, extendModelReferencePose, extractHumanoidSkeletonTemplateFromModel, extractModelData, extractProfileOverrides, filterCameraViewRegions, findModelRoot, findNodeKeyForBone, formatAxisDirectionLabel, formatAxisLabel, fuzzyNameMatch, getAUBoneBindingState, getAUBoneBindingStates, getAxisFromChannel, getBilateralAxisBindingState, getBoneAxisBindingState, getDefaultAnnotationLaterality, getHumanoidSkeletonTemplate, getHumanoidSkeletonTemplateBoneNames, getHumanoidSpecification, getMeshNamesForAUProfile, getModelLocalOrbitAngle, getPreset, getRuntimeAUMeshSideOffset, getRuntimeAUMorphAnchorPoint, getRuntimeAnnotationSide, getSemanticHorizontalSign, getSemanticHorizontalSignForSide, getWorldDirectionForCameraAngle, hasLeftRightMorphs, inferChiralBoneNamePair, inferEyeControlFamily, inferEyeControlScope, inferRuntimeAnnotationPreviewSide, inspectVrmAnimation, isJointControlAuInfo, isMaxDegreesOnlyAxisBindingUpdate, isMixedAU, isRuntimeAnnotationRegionName, loadCharacterModel, mergeCharacterRegionsByName, normalizeDefaultCharacterLightingSettings, normalizeHairColorAppearance, parseCharacterModel, passesMarkerCameraAngleGate, pickContinuumAnnotationAuId, renderScenePlayback, resolveAnnotationCharacterConfig, resolveBoneAxisChannel, resolveBoneNameForNodeKey, resolveBoneNames, resolveContinuumDisplayLabel, resolveFaceCenter, resolveHumanoidCharacterization, resolveMarkerAnchorRegion, resolveMorphMeshSelections, resolveMorphPreviewMeshes, resolveRegionCameraAngle, resolveRegionVisibilityCameraAngle, resolveSkeletonOverlayStartBoneName, resolveVisemeProfile, serializeAnimationClips, setAUMorphTargets, setHumanoidRoleBinding, shouldProjectMarkerAnchorToSurface, shouldUseFaceCenterForMarkerAnchor, stripConfiguredBoneAffixes, supportsRustEmbodyPreset, toWorldDirection, validateHumanoidCharacterization, validateMappings };
|
|
7398
8337
|
//# sourceMappingURL=index.js.map
|
|
7399
8338
|
//# sourceMappingURL=index.js.map
|