@lovelace_lol/embody 0.0.15 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import * as THREE10 from 'three';
2
- import { NoBlending, MultiplyBlending, SubtractiveBlending, AdditiveBlending, NormalBlending, Timer, Quaternion, Vector3, Matrix4, AnimationMixer, AdditiveAnimationBlendMode, NormalAnimationBlendMode, LoopOnce, LoopRepeat, LoopPingPong, PropertyBinding, Euler, AnimationUtils, InterpolateDiscrete, InterpolateLinear, Box3, MathUtils, Mesh, PlaneGeometry, ShadowMaterial, HemisphereLight, DirectionalLight, SRGBColorSpace, ACESFilmicToneMapping, WebGLRenderer, Scene, Color, PerspectiveCamera, PCFShadowMap, PCFSoftShadowMap, NumberKeyframeTrack, QuaternionKeyframeTrack, VectorKeyframeTrack, AnimationClip, PMREMGenerator, FloatType, Float32BufferAttribute, BufferAttribute, WebGPUCoordinateSystem, WebGLCoordinateSystem } from 'three';
3
- import { CC4_HUMANOID_SKELETON_TEMPLATE, HUMANOID_SKELETON_TEMPLATES, DEFAULT_HAIR_COLOR_APPEARANCE, requireInitializedEmbodyCore, initEmbodyCore } from '@lovelace_lol/embody/wasm';
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 getRuntimeAnnotationSide(region) {
37
- return annotationQuery("side", { region });
38
- }
39
- function getRuntimeAUMeshSideOffset(region, sideSign, modelSize) {
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 getRuntimeAUMorphAnchorPoint(region, mesh) {
44
- const core = requireInitializedEmbodyCore();
45
- const names = annotationQuery("morphNames", { region });
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
- const base = readAttribute(positions);
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
- const candidates = core.annotation_morph_candidates(base, morphs, mesh.geometry.morphTargetsRelative);
57
- mesh.updateWorldMatrix(true, false);
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) skinned.skeleton.update();
60
- const samples = new Float32Array(candidates.length / 2 * 4);
61
- const vertex = new THREE10.Vector3();
62
- for (let offset = 0; offset < candidates.length; offset += 2) {
63
- mesh.getVertexPosition(candidates[offset], vertex).applyMatrix4(mesh.matrixWorld);
64
- samples.set([vertex.x, vertex.y, vertex.z, candidates[offset + 1]], offset * 2);
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.model.updateWorldMatrix(true, true);
95
- const modelBox = new THREE10.Box3().setFromObject(this.model);
96
- if (modelBox.isEmpty()) modelBox.setFromCenterAndSize(this.model.getWorldPosition(new THREE10.Vector3()), new THREE10.Vector3());
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 THREE10.Quaternion()).toArray(),
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 THREE10.Vector3()).toArray(),
110
- bounds: bone ? null : bounds(new THREE10.Box3().setFromObject(object)),
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.model.updateWorldMatrix(true, true);
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
- morphAnchors(runtime) {
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 point2 = getRuntimeAUMorphAnchorPoint(request3.region, mesh);
142
- if (point2) anchors[request3.region.name] = { x: point2.x, y: point2.y, z: point2.z };
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 THREE10.Vector3();
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 snapshot2 = controller.getSnapshot();
196
- if (snapshot2.renderer) void binding.commit(snapshot2.renderer).catch(binding.onError);
197
- if (snapshot2.status !== "ready" && snapshot2.status !== "error") void binding.suspend().catch(binding.onError);
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
- for (const mixer of mixers) mixer.update(timer.getDelta());
266
- for (const binding of active) binding.afterFrame();
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 THREE10.Vector4());
353
- const scissor = renderer.getScissor(new THREE10.Vector4());
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 THREE10.Color(), { a: 1 }));
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 THREE10.EventDispatcher {
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 THREE10.Vector3());
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 THREE10.Group());
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 THREE10.Raycaster());
935
- __publicField(this, "pointer", new THREE10.Vector2());
936
- __publicField(this, "viewProjection", new THREE10.Matrix4());
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(snapshot2) {
995
- if (this.revision === snapshot2.descriptorRevision && this.style === snapshot2.style) return;
996
- if (this.style !== snapshot2.style) this.clear();
997
- this.style = snapshot2.style;
998
- const ids = new Set(snapshot2.descriptors.map((descriptor) => descriptor.id));
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 snapshot2.descriptors) {
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 = snapshot2.descriptorRevision;
1083
+ this.revision = snapshot3.descriptorRevision;
1007
1084
  }
1008
1085
  create(descriptor, signature) {
1009
1086
  const { style } = descriptor;
1010
- const sphere = new THREE10.Mesh(new THREE10.SphereGeometry(descriptor.radius, 12, 12), new THREE10.MeshBasicMaterial({ color: style.markerColor, transparent: true }));
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 THREE10.LineDashedMaterial({ color: style.lineColor, transparent: true, dashSize: style.line.style === "dotted" ? 5e-3 : 0.02, gapSize: 0.01, linewidth: style.line.thickness }) : new THREE10.LineBasicMaterial({ color: style.lineColor, transparent: true, linewidth: style.line.thickness });
1013
- const geometry = new THREE10.BufferGeometry();
1014
- geometry.setAttribute("position", new THREE10.BufferAttribute(new Float32Array(51), 3));
1015
- const line = new THREE10.Line(geometry, material);
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 THREE10.CanvasTexture(canvas);
1037
- const label = new THREE10.Sprite(new THREE10.SpriteMaterial({ map: texture, transparent: true, depthTest: true, depthWrite: false, sizeAttenuation: false }));
1038
- const arrow = style.line.arrowHead ? new THREE10.Mesh(new THREE10.ConeGeometry(descriptor.arrowRadius, descriptor.arrowLength, 8), new THREE10.MeshBasicMaterial({ color: style.lineColor, transparent: true })) : null;
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 THREE10.Vector3().fromArray(rays, offset + 1), new THREE10.Vector3().fromArray(rays, offset + 4));
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 THREE10.LineDashedMaterial) objects.line.computeLineDistances();
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 THREE10.Timer());
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 THREE10.Vector3()).add(this.camera.position));
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
- const queries = JSON.parse(this.runtime.plan_markers(JSON.stringify(observer.morphAnchors(this.runtime))));
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 THREE10.Raycaster(new THREE10.Vector3().fromArray(query.origin), new THREE10.Vector3().fromArray(query.direction), 0, query.far);
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) this.runtime.observe_transforms(this.observer.transforms(this.runtime));
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 THREE10.Vector3(p.x, p.y, p.z) : null;
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, apply) {
2222
+ constructor(mode, duration, apply2) {
2117
2223
  this.mode = mode;
2118
2224
  this.duration = duration;
2119
- this.apply = 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(binding.model, this.binding.mixer, () => this.flushEvents());
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(binding.model, this.binding.mixer, () => this.flushEvents());
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) this.sources.clear();
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) this.forget(playback);
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
- previous = Array.from(oldTrack.createInterpolant().evaluate(outgoing.time));
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
- while (!this.playDepth && this.pendingEvents.length) this.onPlaybackEvent(this.pendingEvents.shift());
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 && !playback.terminal) connector.fadeOut(id, seconds);
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
- while (!this.playDepth && this.pendingEvents.length) this.onPlaybackEvent(this.pendingEvents.shift());
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) this.current.delete(playback.source.name);
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.restartHair();
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) => !playback.source.name.startsWith("__embody_"));
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.source.kind === "compiled") continue;
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.keys()]) this.resumeAnimation(name);
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 start = Array.from(outgoing.getClip().tracks[0].createInterpolant().evaluate(outgoing.time));
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.applier.highlightMesh(this.requireModel(), name, color, value);
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.restartHair();
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
- restartHair() {
4002
- for (const name of [...this.current.keys()]) if (name.startsWith("__embody_hair")) this.stopAnimation(name);
4003
- if (!this.hairConfig.enabled || !this.hairObjects.size || !this.connector) return;
4004
- if (this.hairConfig.idleSwayAmount > 0 || this.hairConfig.windStrength > 0) {
4005
- const curves = parse(this.wasm.build_hair_idle_curves(json2(this.hairConfig), this.hairConfig.idleClipDuration));
4006
- if (Object.keys(curves).length) this.playSnippet("__embody_hair_idle", curves, { loopMode: "repeat", meshNames: [...this.hairObjects.keys()], source: "hair", blendMode: "additive" });
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.restartHair();
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
- return this.playSnippet("__embody_hair_impulse", curves, { loopMode: "once", finalPose: "release", meshNames: [...this.hairObjects.keys()], source: "hair", blendMode: "additive" });
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 THREE10.Color(tone).getHexString()}`;
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 THREE10.Color(
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 THREE10.Color(0, 0, 0), score: 0 };
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 = THREE10.MathUtils.clamp(Math.max(hsl.s * 1.35, 0.46), 0.46, 0.82);
4353
- const lightness = THREE10.MathUtils.clamp(hsl.l + 0.08, 0.42, 0.66);
4354
- return new THREE10.Color().setHSL(hsl.h, saturation, lightness);
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 THREE10.Color().setHSL(
4361
- THREE10.MathUtils.euclideanModulo(hsl.h + hueOffset, 1),
4362
- THREE10.MathUtils.clamp(hsl.s * (index === 1 ? 0.92 : 0.82), 0.38, 0.76),
4363
- THREE10.MathUtils.clamp(hsl.l + (index === 1 ? 0.04 : -0.03), 0.38, 0.68)
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) => ({ model, meshes: prepare(model, options.castShadows), animations, gltf });
4435
- if (/\.fbx(?:\?|$)/i.test(url)) {
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({ model: gltf.scene, meshes: prepare(gltf.scene, options.castShadows), animations: gltf.animations ?? [], gltf }), reject));
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,168 @@ 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/sceneVideoCapture.ts
5155
+ var activeCaptures = /* @__PURE__ */ new WeakMap();
5156
+ function endCharacterSceneVideoCaptures(rendering) {
5157
+ for (const end of [...activeCaptures.get(rendering) ?? []]) end();
5158
+ }
5159
+ function acquireCharacterSceneVideoCapture(rendering, options = {}) {
5160
+ const fps = options.fps ?? 30;
5161
+ if (!Number.isFinite(fps) || fps < 1 || fps > 60) throw new Error("Video capture frame rate must be between 1 and 60 fps.");
5162
+ if (typeof MutationObserver === "undefined") throw new Error("Canvas size observation is unavailable for video capture.");
5163
+ const lease = rendering.acquireRenderer();
5164
+ const canvas = lease.renderer.domElement;
5165
+ let tracks = [];
5166
+ let unsubscribe = null;
5167
+ let observer = null;
5168
+ let released = false;
5169
+ let leaseReleased = false;
5170
+ const trackListeners = [];
5171
+ const stoppedTracks = /* @__PURE__ */ new Set();
5172
+ const captures = activeCaptures.get(rendering) ?? /* @__PURE__ */ new Set();
5173
+ activeCaptures.set(rendering, captures);
5174
+ const terminate = () => finish(true);
5175
+ captures.add(terminate);
5176
+ function cleanup() {
5177
+ captures.delete(terminate);
5178
+ if (!captures.size && activeCaptures.get(rendering) === captures) activeCaptures.delete(rendering);
5179
+ const observing = observer;
5180
+ observer = null;
5181
+ try {
5182
+ observing?.disconnect();
5183
+ } catch {
5184
+ }
5185
+ const subscribed = unsubscribe;
5186
+ unsubscribe = null;
5187
+ try {
5188
+ subscribed?.();
5189
+ } catch {
5190
+ }
5191
+ for (const remove of trackListeners.splice(0)) {
5192
+ try {
5193
+ remove();
5194
+ } catch {
5195
+ }
5196
+ }
5197
+ for (const track of tracks) {
5198
+ if (stoppedTracks.has(track)) continue;
5199
+ stoppedTracks.add(track);
5200
+ try {
5201
+ track.stop();
5202
+ } catch {
5203
+ }
5204
+ }
5205
+ if (!leaseReleased) {
5206
+ leaseReleased = true;
5207
+ lease.release();
5208
+ }
5209
+ }
5210
+ function finish(notify) {
5211
+ if (released) {
5212
+ cleanup();
5213
+ return;
5214
+ }
5215
+ released = true;
5216
+ const ended = notify ? tracks.filter((track) => track.readyState !== "ended") : [];
5217
+ cleanup();
5218
+ for (const track of ended) {
5219
+ try {
5220
+ track.dispatchEvent(new Event("ended"));
5221
+ } catch {
5222
+ }
5223
+ }
5224
+ }
5225
+ function check(snapshot3) {
5226
+ if (snapshot3.renderer !== lease.renderer || !["ready", "error"].includes(snapshot3.status)) finish(true);
5227
+ }
5228
+ try {
5229
+ if (typeof canvas.captureStream !== "function") throw new Error("This renderer canvas cannot provide a video stream.");
5230
+ const stream = canvas.captureStream(fps);
5231
+ tracks = stream.getTracks();
5232
+ if (released) throw new Error("Video capture ended during canvas acquisition.");
5233
+ if (!stream.getVideoTracks().some((track) => track.readyState === "live")) throw new Error("The renderer canvas did not provide a live video track.");
5234
+ for (const track of tracks) {
5235
+ const ended = () => finish(true);
5236
+ trackListeners.push(() => track.removeEventListener("ended", ended));
5237
+ track.addEventListener("ended", ended);
5238
+ if (released) throw new Error("Video capture ended during acquisition.");
5239
+ }
5240
+ const width = canvas.width, height = canvas.height;
5241
+ unsubscribe = rendering.subscribe(check);
5242
+ if (released) throw new Error("The renderer changed during video capture acquisition.");
5243
+ check(rendering.getSnapshot());
5244
+ if (released) throw new Error("The renderer is unavailable for video capture.");
5245
+ observer = new MutationObserver(() => {
5246
+ if (canvas.width !== width || canvas.height !== height) finish(true);
5247
+ });
5248
+ if (released) throw new Error("Video capture ended during size observer creation.");
5249
+ observer.observe(canvas, { attributes: true, attributeFilter: ["width", "height"] });
5250
+ if (canvas.width !== width || canvas.height !== height) finish(true);
5251
+ if (released) throw new Error("The renderer canvas changed size during video capture acquisition.");
5252
+ return { stream, release: () => finish(false) };
5253
+ } catch (error) {
5254
+ finish(false);
5255
+ throw error;
5256
+ }
5257
+ }
5258
+
5259
+ // three/scene.ts
4594
5260
  var DEFAULT_CHARACTER_LIGHTING_PRESETS = {
4595
5261
  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
5262
  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 +5501,8 @@ function constructDefaultCharacterScene(container, options, renderer, disposeRen
4835
5501
  Object.assign(renderer.domElement.style, { display: "block", width: "100%", height: "100%" });
4836
5502
  const scene = new Scene();
4837
5503
  const background = options.background === void 0 ? sceneType.background : options.background;
4838
- scene.background = background == null ? null : new Color(background);
5504
+ setSceneBaseBackground(scene, background == null ? null : new Color(background));
5505
+ lifetime.add(() => disposeSceneBackground(scene));
4839
5506
  const camera = new PerspectiveCamera(options.cameraFov ?? 45, width / height, 0.1, 1e3);
4840
5507
  const preset = DEFAULT_CHARACTER_LIGHTING_PRESETS[options.lightingPreset ?? sceneType.lightingPreset]?.settings ?? DEFAULT_CHARACTER_LIGHTING_SETTINGS;
4841
5508
  const lighting = createLighting(scene, { ...preset, ...options.lighting });
@@ -4856,7 +5523,7 @@ function constructDefaultCharacterScene(container, options, renderer, disposeRen
4856
5523
  const type = CHARACTER_SCENE_TYPES[value.type ?? DEFAULT_CHARACTER_SCENE_TYPE_ID] ?? CHARACTER_SCENE_TYPES[DEFAULT_CHARACTER_SCENE_TYPE_ID];
4857
5524
  currentSceneType = type.id;
4858
5525
  const background2 = value.background === void 0 ? type.background : value.background;
4859
- scene.background = background2 == null ? null : new Color(background2);
5526
+ setSceneBaseBackground(scene, background2 == null ? null : new Color(background2));
4860
5527
  camera.fov = value.cameraFov ?? 45;
4861
5528
  camera.updateProjectionMatrix();
4862
5529
  const preset2 = DEFAULT_CHARACTER_LIGHTING_PRESETS[value.lightingPreset ?? type.lightingPreset] ?? DEFAULT_CHARACTER_LIGHTING_PRESETS[DEFAULT_CHARACTER_LIGHTING_PRESET_ID];
@@ -4991,17 +5658,17 @@ function prepareCameraCoordinates(scene, camera, backend) {
4991
5658
  }
4992
5659
  });
4993
5660
  const previous = Array.from(cameras, (current) => [current, current.coordinateSystem]);
4994
- const apply = (current, coordinates2) => {
5661
+ const apply2 = (current, coordinates2) => {
4995
5662
  current.coordinateSystem = coordinates2;
4996
5663
  current.updateProjectionMatrix();
4997
5664
  };
4998
5665
  const coordinates = backend === "webgpu" ? WebGPUCoordinateSystem : WebGLCoordinateSystem;
4999
- cameras.forEach((current) => apply(current, coordinates));
5666
+ cameras.forEach((current) => apply2(current, coordinates));
5000
5667
  shadows.forEach((shadow) => {
5001
5668
  shadow.needsUpdate = true;
5002
5669
  });
5003
5670
  return () => {
5004
- previous.forEach(([current, original]) => apply(current, original));
5671
+ previous.forEach(([current, original]) => apply2(current, original));
5005
5672
  shadows.forEach((shadow) => {
5006
5673
  shadow.needsUpdate = true;
5007
5674
  });
@@ -5015,7 +5682,8 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
5015
5682
  let currentSceneType = sceneType.id;
5016
5683
  const scene = new Scene();
5017
5684
  const background = options.background === void 0 ? sceneType.background : options.background;
5018
- scene.background = background == null ? null : new Color(background);
5685
+ setSceneBaseBackground(scene, background == null ? null : new Color(background));
5686
+ lifetime.add(() => disposeSceneBackground(scene));
5019
5687
  const camera = new PerspectiveCamera(options.cameraFov ?? 45, 1, 0.1, 1e3);
5020
5688
  const preset = DEFAULT_CHARACTER_LIGHTING_PRESETS[options.lightingPreset ?? sceneType.lightingPreset]?.settings ?? DEFAULT_CHARACTER_LIGHTING_SETTINGS;
5021
5689
  const lighting = createMutableCharacterLighting(scene, { ...preset, ...options.lighting });
@@ -5055,10 +5723,10 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
5055
5723
  return { ...common, backend: null, renderer: null };
5056
5724
  };
5057
5725
  const notify = () => {
5058
- const snapshot2 = getSnapshot();
5726
+ const snapshot3 = getSnapshot();
5059
5727
  for (const listener of listeners) {
5060
5728
  try {
5061
- listener(snapshot2);
5729
+ listener(snapshot3);
5062
5730
  } catch (failure) {
5063
5731
  console.error("Character rendering subscriber failed.", failure);
5064
5732
  }
@@ -5185,9 +5853,9 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
5185
5853
  rollback(() => oldRenderer?.dispose());
5186
5854
  status = "ready";
5187
5855
  error = null;
5188
- const snapshot2 = getSnapshot();
5856
+ const snapshot3 = getSnapshot();
5189
5857
  notify();
5190
- return snapshot2;
5858
+ return snapshot3;
5191
5859
  } catch (failure) {
5192
5860
  if (suspended) {
5193
5861
  try {
@@ -5266,6 +5934,17 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
5266
5934
  }
5267
5935
  } };
5268
5936
  },
5937
+ acquireRendererAsync: async () => {
5938
+ for (; ; ) {
5939
+ if (disposed) throw renderingAborted();
5940
+ const settling = pending;
5941
+ if (settling) {
5942
+ await settling.catch(() => void 0);
5943
+ continue;
5944
+ }
5945
+ return rendering.acquireRenderer();
5946
+ }
5947
+ },
5269
5948
  releaseSceneResources: (release) => {
5270
5949
  if (disposal) return disposal.then(release, release);
5271
5950
  const previous = pending;
@@ -5288,6 +5967,7 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
5288
5967
  pendingResourceReleases--;
5289
5968
  });
5290
5969
  pending = operation;
5970
+ endCharacterSceneVideoCaptures(rendering);
5291
5971
  void operation.then(() => {
5292
5972
  if (pending === operation) pending = null;
5293
5973
  }, () => {
@@ -5349,7 +6029,7 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
5349
6029
  const type = CHARACTER_SCENE_TYPES[value.type ?? DEFAULT_CHARACTER_SCENE_TYPE_ID] ?? CHARACTER_SCENE_TYPES[DEFAULT_CHARACTER_SCENE_TYPE_ID];
5350
6030
  currentSceneType = type.id;
5351
6031
  const background2 = value.background === void 0 ? type.background : value.background;
5352
- scene.background = background2 == null ? null : new Color(background2);
6032
+ setSceneBaseBackground(scene, background2 == null ? null : new Color(background2));
5353
6033
  camera.fov = value.cameraFov ?? 45;
5354
6034
  camera.updateProjectionMatrix();
5355
6035
  const preset2 = DEFAULT_CHARACTER_LIGHTING_PRESETS[value.lightingPreset ?? type.lightingPreset] ?? DEFAULT_CHARACTER_LIGHTING_PRESETS[DEFAULT_CHARACTER_LIGHTING_PRESET_ID];
@@ -5392,9 +6072,9 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
5392
6072
  async function createDefaultCharacterSceneAsync(container, options = {}) {
5393
6073
  const runtime = createDefaultCharacterSceneRuntime(container, options);
5394
6074
  try {
5395
- const snapshot2 = await runtime.ready;
5396
- if (!snapshot2.renderer) throw new Error("Character scene renderer was not initialized.");
5397
- const handle = snapshot2.backend === "webgl" ? { ...runtime, renderer: snapshot2.renderer, backend: "webgl" } : { ...runtime, renderer: snapshot2.renderer, backend: "webgpu" };
6075
+ const snapshot3 = await runtime.ready;
6076
+ if (!snapshot3.renderer) throw new Error("Character scene renderer was not initialized.");
6077
+ const handle = snapshot3.backend === "webgl" ? { ...runtime, renderer: snapshot3.renderer, backend: "webgl" } : { ...runtime, renderer: snapshot3.renderer, backend: "webgpu" };
5398
6078
  Object.defineProperties(handle, {
5399
6079
  renderer: { get: () => runtime.renderer },
5400
6080
  backend: { get: () => runtime.backend },
@@ -5481,7 +6161,14 @@ async function createCharacterHost(options) {
5481
6161
  if (!rendering.getSnapshot().renderer) throw error;
5482
6162
  }
5483
6163
  check(signal);
5484
- return rendering.acquireRenderer();
6164
+ const lease = await rendering.acquireRendererAsync();
6165
+ try {
6166
+ check(signal);
6167
+ return lease;
6168
+ } catch (error) {
6169
+ lease.release();
6170
+ throw error;
6171
+ }
5485
6172
  };
5486
6173
  const makeEngine = (value, character) => {
5487
6174
  let profile = character.profile ?? options.embody?.profile;
@@ -5730,6 +6417,302 @@ async function createCharacterHost(options) {
5730
6417
  throw error;
5731
6418
  }
5732
6419
  }
6420
+ var finite4 = (value) => typeof value === "number" && Number.isFinite(value);
6421
+ var identity2 = (value) => typeof value === "string" && value.trim().length > 0;
6422
+ var messageOf = (error) => error instanceof Error ? error.message : String(error);
6423
+ function copyLayout(layout) {
6424
+ if (layout === null) return { skybox: null, instances: [] };
6425
+ if (!layout || !Array.isArray(layout.instances)) throw new Error("Scenery requires an instances array.");
6426
+ const ids = /* @__PURE__ */ new Set();
6427
+ const instances = layout.instances.map((instance) => {
6428
+ if (!instance || !identity2(instance.instanceId) || !identity2(instance.assetId)) throw new Error("Each scenery placement requires an instanceId and assetId.");
6429
+ if (ids.has(instance.instanceId)) throw new Error(`Duplicate scenery instanceId: ${instance.instanceId}`);
6430
+ ids.add(instance.instanceId);
6431
+ const position = instance.position;
6432
+ if (!position || !finite4(position.x) || !finite4(position.y) || !finite4(position.z)) throw new Error(`Scenery placement ${instance.instanceId} requires finite X/Y/Z coordinates.`);
6433
+ return { instanceId: instance.instanceId, assetId: instance.assetId, position: { x: position.x, y: position.y, z: position.z } };
6434
+ });
6435
+ const skybox = layout.skybox;
6436
+ if (skybox !== null) {
6437
+ if (!skybox || !["skybox", "backdrop", "color"].includes(skybox.kind)) throw new Error("Scenery background kind must be skybox, backdrop or color.");
6438
+ if (skybox.assetId !== null && !identity2(skybox.assetId)) throw new Error("The scenery background assetId must be a nonempty string or null.");
6439
+ if (skybox.kind === "color" && skybox.assetId !== null) throw new Error("A solid scenery color cannot use an image asset.");
6440
+ if (typeof skybox.color !== "string" || !/^#[\da-f]{6}$/i.test(skybox.color)) throw new Error("Scenery background color must use six-digit #rrggbb.");
6441
+ 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.");
6442
+ }
6443
+ return { instances, skybox: skybox === null ? null : { kind: skybox.kind, assetId: skybox.assetId, color: skybox.color, intensity: skybox.intensity, rotation: skybox.rotation } };
6444
+ }
6445
+ function disposeRoots(roots) {
6446
+ const geometries = /* @__PURE__ */ new Set(), materials2 = /* @__PURE__ */ new Set(), textures = /* @__PURE__ */ new Set();
6447
+ const skeletons = /* @__PURE__ */ new Set();
6448
+ for (const root of roots) root.traverse((object) => {
6449
+ if (object.geometry) geometries.add(object.geometry);
6450
+ if (object.skeleton) skeletons.add(object.skeleton);
6451
+ for (const material of object.material ? Array.isArray(object.material) ? object.material : [object.material] : []) {
6452
+ materials2.add(material);
6453
+ for (const value of Object.values(material)) if (value?.isTexture) textures.add(value);
6454
+ }
6455
+ });
6456
+ const images = /* @__PURE__ */ new Set();
6457
+ for (const texture of textures) {
6458
+ for (const image of Array.isArray(texture.image) ? texture.image : [texture.image]) if (image) images.add(image);
6459
+ texture.dispose();
6460
+ }
6461
+ for (const geometry of geometries) geometry.dispose();
6462
+ for (const material of materials2) material.dispose();
6463
+ for (const skeleton of skeletons) skeleton.dispose();
6464
+ for (const image of images) image.close?.();
6465
+ }
6466
+ function display(value, skybox) {
6467
+ const texture = "isTexture" in value ? value : null;
6468
+ if (texture) {
6469
+ texture.colorSpace = SRGBColorSpace;
6470
+ if (skybox.kind === "skybox") texture.mapping = EquirectangularReflectionMapping;
6471
+ }
6472
+ let disposed = false;
6473
+ return {
6474
+ value,
6475
+ texture,
6476
+ intensity: skybox.intensity,
6477
+ rotation: skybox.kind === "skybox" ? skybox.rotation : 0,
6478
+ dispose() {
6479
+ if (disposed) return;
6480
+ disposed = true;
6481
+ texture?.dispose();
6482
+ }
6483
+ };
6484
+ }
6485
+ function generatedSky(skybox) {
6486
+ const canvas = document.createElement("canvas");
6487
+ canvas.width = 1024;
6488
+ canvas.height = 512;
6489
+ const context = canvas.getContext("2d");
6490
+ if (!context) throw new Error("The browser could not create the scenery skybox.");
6491
+ const gradient = context.createLinearGradient(0, 0, 0, 512);
6492
+ gradient.addColorStop(0, "#10172c");
6493
+ gradient.addColorStop(0.48, skybox.color);
6494
+ gradient.addColorStop(0.52, "#b6aaa0");
6495
+ gradient.addColorStop(1, "#151c25");
6496
+ context.fillStyle = gradient;
6497
+ context.fillRect(0, 0, 1024, 512);
6498
+ return display(new CanvasTexture(canvas), skybox);
6499
+ }
6500
+ async function imageDisplay(asset, skybox) {
6501
+ if (!["image/png", "image/jpeg", "image/webp"].includes(asset.contentType)) throw new Error("Scenery backgrounds require a PNG, JPEG or WebP image.");
6502
+ const url = URL.createObjectURL(new Blob([asset.bytes], { type: asset.contentType }));
6503
+ let pending;
6504
+ try {
6505
+ const texture = await new Promise((resolve, reject) => {
6506
+ pending = new TextureLoader().load(url, resolve, void 0, reject);
6507
+ });
6508
+ return display(texture, skybox);
6509
+ } catch (error) {
6510
+ pending?.dispose();
6511
+ throw new Error(`Scenery background image could not decode: ${messageOf(error)}`);
6512
+ } finally {
6513
+ URL.revokeObjectURL(url);
6514
+ }
6515
+ }
6516
+ async function parseModel(asset) {
6517
+ if (asset.contentType !== "model/gltf-binary" || asset.bytes.byteLength < 20) throw new Error("Scenery models require a self-contained GLB 2.0 file.");
6518
+ const header = new DataView(asset.bytes);
6519
+ 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.");
6520
+ let offset = 12, document2;
6521
+ while (offset < asset.bytes.byteLength) {
6522
+ if (offset + 8 > asset.bytes.byteLength) throw new Error("The scenery GLB has a truncated chunk header.");
6523
+ const size = header.getUint32(offset, true), kind = header.getUint32(offset + 4, true);
6524
+ if (size % 4 || size > asset.bytes.byteLength - offset - 8) throw new Error("The scenery GLB has an invalid chunk length.");
6525
+ if (offset === 12 && kind !== 1313821514) throw new Error("The scenery GLB must begin with its JSON chunk.");
6526
+ if (kind === 1313821514) {
6527
+ if (document2) throw new Error("The scenery GLB contains duplicate JSON chunks.");
6528
+ try {
6529
+ document2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(asset.bytes, offset + 8, size)));
6530
+ } catch (error) {
6531
+ throw new Error(`The scenery GLB JSON is invalid: ${messageOf(error)}`);
6532
+ }
6533
+ if (!document2 || typeof document2 !== "object" || Array.isArray(document2)) throw new Error("The scenery GLB JSON must be an object.");
6534
+ }
6535
+ offset += 8 + size;
6536
+ }
6537
+ for (const resources of [document2?.buffers, document2?.images]) {
6538
+ if (resources !== void 0 && !Array.isArray(resources)) throw new Error("The scenery GLB buffers and images must be arrays.");
6539
+ for (const resource of resources ?? []) {
6540
+ if (resource?.uri !== void 0 && (typeof resource.uri !== "string" || !resource.uri.startsWith("data:"))) {
6541
+ throw new Error("Scenery GLB files must embed their geometry and textures; external URLs are not allowed.");
6542
+ }
6543
+ }
6544
+ }
6545
+ const manager = new LoadingManager();
6546
+ manager.setURLModifier((url) => {
6547
+ if (url.startsWith("data:") || url.startsWith("blob:")) return url;
6548
+ throw new Error("Scenery GLB files must embed their geometry and textures; external URLs are not allowed.");
6549
+ });
6550
+ const gltf = await new GLTFLoader(manager).parseAsync(asset.bytes, "");
6551
+ if (!gltf.scene?.isObject3D) throw new Error("The scenery GLB has no scene to display.");
6552
+ return [gltf.scene, ...gltf.scenes.filter((root) => root !== gltf.scene)];
6553
+ }
6554
+ function createSceneSceneryRenderer(options) {
6555
+ const { scene, rendering, loadAsset } = options;
6556
+ if (!scene?.isScene || typeof loadAsset !== "function") throw new Error("Scenery requires a Three scene and an asset loader.");
6557
+ let disposed = false, disposal = null;
6558
+ let backgroundKey = null, backgroundGeneration = 0;
6559
+ let backgroundLoad = null, currentDisplay = null;
6560
+ const models = /* @__PURE__ */ new Map(), tasks = /* @__PURE__ */ new Set();
6561
+ const report = (message) => {
6562
+ try {
6563
+ options.onError?.(message);
6564
+ } catch (error) {
6565
+ console.error("Scenery error subscriber failed.", error);
6566
+ }
6567
+ };
6568
+ const track = (task) => {
6569
+ tasks.add(task);
6570
+ void task.then(() => tasks.delete(task), (error) => {
6571
+ tasks.delete(task);
6572
+ report(messageOf(error));
6573
+ });
6574
+ return task;
6575
+ };
6576
+ const release = (callback) => {
6577
+ try {
6578
+ return rendering ? rendering.releaseSceneResources(callback) : (callback(), Promise.resolve());
6579
+ } catch (error) {
6580
+ return Promise.reject(error);
6581
+ }
6582
+ };
6583
+ const background = acquireSceneBackground(scene, () => {
6584
+ void dispose().catch((error) => report(messageOf(error)));
6585
+ });
6586
+ const retire = (model) => {
6587
+ if (model.retired) return Promise.resolve();
6588
+ model.retired = true;
6589
+ model.controller.abort();
6590
+ return release(() => {
6591
+ model.wrapper.removeFromParent();
6592
+ model.attached = false;
6593
+ disposeRoots(model.roots);
6594
+ model.roots = [];
6595
+ });
6596
+ };
6597
+ const mount = (instance) => {
6598
+ const wrapper = new Group();
6599
+ wrapper.name = `Scenery ${instance.instanceId}`;
6600
+ wrapper.position.set(instance.position.x, instance.position.y, instance.position.z);
6601
+ const model = { assetId: instance.assetId, wrapper, controller: new AbortController(), roots: [], attached: false, retired: false };
6602
+ models.set(instance.instanceId, model);
6603
+ track((async () => {
6604
+ let roots = [];
6605
+ try {
6606
+ const asset = await loadAsset(model.assetId, model.controller.signal);
6607
+ if (model.controller.signal.aborted) return;
6608
+ roots = await parseModel(asset);
6609
+ if (model.controller.signal.aborted || disposed) {
6610
+ disposeRoots(roots);
6611
+ roots = [];
6612
+ return;
6613
+ }
6614
+ await release(() => {
6615
+ if (model.controller.signal.aborted || disposed) {
6616
+ disposeRoots(roots);
6617
+ roots = [];
6618
+ return;
6619
+ }
6620
+ model.roots = roots;
6621
+ roots = [];
6622
+ wrapper.add(model.roots[0]);
6623
+ scene.add(wrapper);
6624
+ model.attached = true;
6625
+ });
6626
+ } catch (error) {
6627
+ if (roots.length) disposeRoots(roots);
6628
+ if (!model.controller.signal.aborted && !disposed) {
6629
+ if (models.get(instance.instanceId) === model) models.delete(instance.instanceId);
6630
+ await retire(model);
6631
+ report(`Scenery placement ${instance.instanceId} could not load: ${messageOf(error)}`);
6632
+ }
6633
+ }
6634
+ })());
6635
+ };
6636
+ const updateBackground = (skybox) => {
6637
+ const key = JSON.stringify(skybox);
6638
+ if (backgroundKey === key) return;
6639
+ backgroundKey = key;
6640
+ const generation = ++backgroundGeneration;
6641
+ backgroundLoad?.abort();
6642
+ const controller = new AbortController();
6643
+ backgroundLoad = controller;
6644
+ track((async () => {
6645
+ let next = null;
6646
+ try {
6647
+ if (skybox) {
6648
+ if (skybox.kind === "color") next = display(new Color(skybox.color), skybox);
6649
+ else if (!skybox.assetId) next = generatedSky(skybox);
6650
+ else {
6651
+ const asset = await loadAsset(skybox.assetId, controller.signal);
6652
+ if (controller.signal.aborted) return;
6653
+ next = await imageDisplay(asset, skybox);
6654
+ }
6655
+ }
6656
+ await release(() => {
6657
+ if (controller.signal.aborted || generation !== backgroundGeneration || disposed) {
6658
+ next?.dispose();
6659
+ next = null;
6660
+ return;
6661
+ }
6662
+ if (next) background.set(next.value, next.intensity, next.rotation);
6663
+ else background.clear();
6664
+ const previous = currentDisplay;
6665
+ currentDisplay = next;
6666
+ next = null;
6667
+ previous?.dispose();
6668
+ });
6669
+ } catch (error) {
6670
+ next?.dispose();
6671
+ if (!controller.signal.aborted && generation === backgroundGeneration && !disposed) {
6672
+ backgroundKey = null;
6673
+ report(`Scenery background could not load: ${messageOf(error)}`);
6674
+ }
6675
+ }
6676
+ })());
6677
+ };
6678
+ function dispose() {
6679
+ if (disposal) return disposal;
6680
+ disposed = true;
6681
+ backgroundGeneration++;
6682
+ backgroundLoad?.abort();
6683
+ background.dispose();
6684
+ const releases = [...models.values()].map(retire);
6685
+ models.clear();
6686
+ releases.push(release(() => {
6687
+ currentDisplay?.dispose();
6688
+ currentDisplay = null;
6689
+ }));
6690
+ disposal = (async () => {
6691
+ const results = await Promise.allSettled([...tasks, ...releases]);
6692
+ const failed = results.find((result) => result.status === "rejected");
6693
+ if (failed) throw failed.reason;
6694
+ })();
6695
+ return disposal;
6696
+ }
6697
+ return {
6698
+ update(value) {
6699
+ if (disposed) throw new Error("The scenery renderer has been disposed.");
6700
+ const layout = copyLayout(value);
6701
+ const next = new Map(layout.instances.map((instance) => [instance.instanceId, instance]));
6702
+ for (const [id, model] of models) if (next.get(id)?.assetId !== model.assetId) {
6703
+ models.delete(id);
6704
+ track(retire(model));
6705
+ }
6706
+ for (const instance of layout.instances) {
6707
+ const current = models.get(instance.instanceId);
6708
+ if (current) current.wrapper.position.set(instance.position.x, instance.position.y, instance.position.z);
6709
+ else mount(instance);
6710
+ }
6711
+ updateBackground(layout.skybox);
6712
+ },
6713
+ dispose
6714
+ };
6715
+ }
5733
6716
  var COLORS = {
5734
6717
  mapped: 4889258,
5735
6718
  unmapped: 5925498,
@@ -5856,8 +6839,8 @@ function createOctahedralBoneGeometry() {
5856
6839
  -w,
5857
6840
  MID_POSITION
5858
6841
  ]);
5859
- const geometry = new THREE10.BufferGeometry();
5860
- geometry.setAttribute("position", new THREE10.BufferAttribute(positions, 3));
6842
+ const geometry = new THREE11.BufferGeometry();
6843
+ geometry.setAttribute("position", new THREE11.BufferAttribute(positions, 3));
5861
6844
  geometry.computeVertexNormals();
5862
6845
  return geometry;
5863
6846
  }
@@ -5874,24 +6857,24 @@ function collectBones(root) {
5874
6857
  });
5875
6858
  return Array.from(bones);
5876
6859
  }
5877
- function isBone2(obj) {
6860
+ function isBone3(obj) {
5878
6861
  return !!obj && obj.isBone;
5879
6862
  }
5880
6863
  function countBoneSubtree(bone) {
5881
6864
  let count = 1;
5882
6865
  for (const child of bone.children) {
5883
- if (isBone2(child)) {
6866
+ if (isBone3(child)) {
5884
6867
  count += countBoneSubtree(child);
5885
6868
  }
5886
6869
  }
5887
6870
  return count;
5888
6871
  }
5889
6872
  function resolveTopLevelHelperRoot(root) {
5890
- const topLevelBones = collectBones(root).filter((bone) => !isBone2(bone.parent));
6873
+ const topLevelBones = collectBones(root).filter((bone) => !isBone3(bone.parent));
5891
6874
  if (topLevelBones.length !== 1) return null;
5892
6875
  let helperRoot = topLevelBones[0];
5893
6876
  while (ROOT_HELPER_NAME_PATTERN.test(helperRoot.name)) {
5894
- const boneChildren = helperRoot.children.filter(isBone2);
6877
+ const boneChildren = helperRoot.children.filter(isBone3);
5895
6878
  if (boneChildren.length !== 1) break;
5896
6879
  const onlyChild = boneChildren[0];
5897
6880
  if (!ROOT_HELPER_NAME_PATTERN.test(onlyChild.name)) break;
@@ -5902,7 +6885,7 @@ function resolveTopLevelHelperRoot(root) {
5902
6885
  function resolveBodyDisplayRoot(root) {
5903
6886
  const helperRoot = resolveTopLevelHelperRoot(root);
5904
6887
  if (!helperRoot) return null;
5905
- const boneChildren = helperRoot.children.filter(isBone2);
6888
+ const boneChildren = helperRoot.children.filter(isBone3);
5906
6889
  if (boneChildren.length === 0) return null;
5907
6890
  if (boneChildren.length === 1) return boneChildren[0];
5908
6891
  const subtreeSizes = boneChildren.map((child) => ({ child, size: countBoneSubtree(child) })).sort((left, right) => right.size - left.size);
@@ -5914,7 +6897,7 @@ function resolveBodyDisplayRoot(root) {
5914
6897
  return null;
5915
6898
  }
5916
6899
  function collectBoneChildren(bone) {
5917
- return bone.children.filter(isBone2);
6900
+ return bone.children.filter(isBone3);
5918
6901
  }
5919
6902
  function hasZeroLengthConnection(parentBone, childBone) {
5920
6903
  parentBone.getWorldPosition(_parentPos);
@@ -5923,7 +6906,7 @@ function hasZeroLengthConnection(parentBone, childBone) {
5923
6906
  }
5924
6907
  function hasRootHelperAncestor(bone) {
5925
6908
  let current = bone.parent;
5926
- while (isBone2(current)) {
6909
+ while (isBone3(current)) {
5927
6910
  if (ROOT_HELPER_NAME_PATTERN.test(current.name)) return true;
5928
6911
  current = current.parent;
5929
6912
  }
@@ -5933,7 +6916,7 @@ function shouldSkipRootHelperConnection(parentBone, boneChildren) {
5933
6916
  if (boneChildren.length !== 1) return false;
5934
6917
  const childBone = boneChildren[0];
5935
6918
  if (ROOT_HELPER_NAME_PATTERN.test(parentBone.name)) {
5936
- if (!isBone2(parentBone.parent)) return true;
6919
+ if (!isBone3(parentBone.parent)) return true;
5937
6920
  return collectBoneChildren(childBone).some((grandChild) => hasZeroLengthConnection(childBone, grandChild));
5938
6921
  }
5939
6922
  return hasRootHelperAncestor(parentBone) && HIP_NAME_PATTERN.test(parentBone.name) && PELVIS_NAME_PATTERN.test(childBone.name) && hasZeroLengthConnection(parentBone, childBone);
@@ -5946,20 +6929,20 @@ function setRayFromPointer(clientX, clientY, camera, domElement) {
5946
6929
  _raycaster.setFromCamera(_mouse, camera);
5947
6930
  return true;
5948
6931
  }
5949
- var _parentPos = new THREE10.Vector3();
5950
- var _childPos = new THREE10.Vector3();
5951
- var _direction = new THREE10.Vector3();
5952
- var _zAxis = new THREE10.Vector3(0, 0, 1);
5953
- var _quaternion = new THREE10.Quaternion();
5954
- var _bonePos = new THREE10.Vector3();
5955
- var _boneQuat = new THREE10.Quaternion();
5956
- var _boneEuler = new THREE10.Euler();
5957
- var _mouse = new THREE10.Vector2();
5958
- var _raycaster = new THREE10.Raycaster();
5959
- var _cameraSpacePosition = new THREE10.Vector3();
5960
- var _planeNormal = new THREE10.Vector3();
5961
- var _dragPlane = new THREE10.Plane();
5962
- var _planeHit = new THREE10.Vector3();
6932
+ var _parentPos = new THREE11.Vector3();
6933
+ var _childPos = new THREE11.Vector3();
6934
+ var _direction = new THREE11.Vector3();
6935
+ var _zAxis = new THREE11.Vector3(0, 0, 1);
6936
+ var _quaternion = new THREE11.Quaternion();
6937
+ var _bonePos = new THREE11.Vector3();
6938
+ var _boneQuat = new THREE11.Quaternion();
6939
+ var _boneEuler = new THREE11.Euler();
6940
+ var _mouse = new THREE11.Vector2();
6941
+ var _raycaster = new THREE11.Raycaster();
6942
+ var _cameraSpacePosition = new THREE11.Vector3();
6943
+ var _planeNormal = new THREE11.Vector3();
6944
+ var _dragPlane = new THREE11.Plane();
6945
+ var _planeHit = new THREE11.Vector3();
5963
6946
  function resolveSkeletonOverlayStartBoneName(root) {
5964
6947
  return resolveBodyDisplayRoot(root)?.name ?? null;
5965
6948
  }
@@ -6006,9 +6989,9 @@ var SkeletonOverlay = class {
6006
6989
  this.scene = scene;
6007
6990
  this.mappedBoneNames = mappedBoneNames;
6008
6991
  this.semanticKeyMap = semanticKeyMap;
6009
- this.opacityScale = THREE10.MathUtils.clamp(options.opacityScale ?? 1, 0, 1);
6992
+ this.opacityScale = THREE11.MathUtils.clamp(options.opacityScale ?? 1, 0, 1);
6010
6993
  this.focusSelectedBone = options.focusSelectedBone ?? false;
6011
- this.group = new THREE10.Group();
6994
+ this.group = new THREE11.Group();
6012
6995
  this.group.name = "SkeletonOverlay";
6013
6996
  this.group.renderOrder = 999;
6014
6997
  this.group.visible = false;
@@ -6017,50 +7000,50 @@ var SkeletonOverlay = class {
6017
7000
  transparent: true,
6018
7001
  depthTest: false,
6019
7002
  depthWrite: false,
6020
- side: THREE10.DoubleSide
7003
+ side: THREE11.DoubleSide
6021
7004
  };
6022
- this.mappedMaterial = new THREE10.MeshBasicMaterial({ ...materialOpts, color: COLORS.mapped, opacity: OPACITY.mapped });
6023
- this.unmappedMaterial = new THREE10.MeshBasicMaterial({ ...materialOpts, color: COLORS.unmapped, opacity: OPACITY.unmapped });
6024
- this.highlightMaterial = new THREE10.MeshBasicMaterial({ ...materialOpts, color: COLORS.highlighted, opacity: OPACITY.highlighted });
6025
- this.selectedMaterial = new THREE10.MeshBasicMaterial({ ...materialOpts, color: COLORS.selected, opacity: OPACITY.selected });
6026
- this.contextMaterial = new THREE10.MeshBasicMaterial({ ...materialOpts, color: COLORS.selected, opacity: OPACITY.context });
6027
- this.mutedMaterial = new THREE10.MeshBasicMaterial({ ...materialOpts, color: COLORS.unmapped, opacity: OPACITY.muted });
7005
+ this.mappedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.mapped, opacity: OPACITY.mapped });
7006
+ this.unmappedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.unmapped, opacity: OPACITY.unmapped });
7007
+ this.highlightMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.highlighted, opacity: OPACITY.highlighted });
7008
+ this.selectedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.selected, opacity: OPACITY.selected });
7009
+ this.contextMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.selected, opacity: OPACITY.context });
7010
+ this.mutedMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: COLORS.unmapped, opacity: OPACITY.muted });
6028
7011
  this.mappedMaterial.forceSinglePass = true;
6029
7012
  this.unmappedMaterial.forceSinglePass = true;
6030
7013
  this.highlightMaterial.forceSinglePass = true;
6031
7014
  this.selectedMaterial.forceSinglePass = true;
6032
7015
  this.contextMaterial.forceSinglePass = true;
6033
7016
  this.mutedMaterial.forceSinglePass = true;
6034
- this.axisRingGeometry = new THREE10.TorusGeometry(1, 0.032, 10, 120);
6035
- this.axisRingHitGeometry = new THREE10.TorusGeometry(1, AXIS_GIZMO_HIT_TUBE, 8, 64);
6036
- this.axisCenterGeometry = new THREE10.SphereGeometry(0.082, 12, 8);
6037
- this.pitchRingMaterial = new THREE10.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.pitch, opacity: AXIS_GIZMO_IDLE_OPACITY });
6038
- this.yawRingMaterial = new THREE10.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.yaw, opacity: AXIS_GIZMO_IDLE_OPACITY });
6039
- this.rollRingMaterial = new THREE10.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.roll, opacity: AXIS_GIZMO_IDLE_OPACITY });
6040
- this.axisHitMaterial = new THREE10.MeshBasicMaterial({ ...materialOpts, opacity: 0 });
7017
+ this.axisRingGeometry = new THREE11.TorusGeometry(1, 0.032, 10, 120);
7018
+ this.axisRingHitGeometry = new THREE11.TorusGeometry(1, AXIS_GIZMO_HIT_TUBE, 8, 64);
7019
+ this.axisCenterGeometry = new THREE11.SphereGeometry(0.082, 12, 8);
7020
+ this.pitchRingMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.pitch, opacity: AXIS_GIZMO_IDLE_OPACITY });
7021
+ this.yawRingMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.yaw, opacity: AXIS_GIZMO_IDLE_OPACITY });
7022
+ this.rollRingMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.roll, opacity: AXIS_GIZMO_IDLE_OPACITY });
7023
+ this.axisHitMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, opacity: 0 });
6041
7024
  this.axisHitMaterial.colorWrite = false;
6042
- this.axisCenterMaterial = new THREE10.MeshBasicMaterial({ ...materialOpts, color: 16119802, opacity: 0.92 });
6043
- this.axisGizmoGroup = new THREE10.Group();
7025
+ this.axisCenterMaterial = new THREE11.MeshBasicMaterial({ ...materialOpts, color: 16119802, opacity: 0.92 });
7026
+ this.axisGizmoGroup = new THREE11.Group();
6044
7027
  this.axisGizmoGroup.name = "SelectedBoneAxisGizmo";
6045
7028
  this.axisGizmoGroup.visible = false;
6046
7029
  this.axisGizmoGroup.renderOrder = 1e3;
6047
- const pitchRing = new THREE10.Mesh(this.axisRingGeometry, this.pitchRingMaterial);
7030
+ const pitchRing = new THREE11.Mesh(this.axisRingGeometry, this.pitchRingMaterial);
6048
7031
  pitchRing.renderOrder = 1e3;
6049
7032
  pitchRing.userData.axis = "pitch";
6050
- const pitchHit = new THREE10.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
7033
+ const pitchHit = new THREE11.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
6051
7034
  pitchHit.userData.axis = "pitch";
6052
- const yawRing = new THREE10.Mesh(this.axisRingGeometry, this.yawRingMaterial);
7035
+ const yawRing = new THREE11.Mesh(this.axisRingGeometry, this.yawRingMaterial);
6053
7036
  yawRing.renderOrder = 1e3;
6054
7037
  yawRing.userData.axis = "yaw";
6055
- const yawHit = new THREE10.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
7038
+ const yawHit = new THREE11.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
6056
7039
  yawHit.userData.axis = "yaw";
6057
- const rollRing = new THREE10.Mesh(this.axisRingGeometry, this.rollRingMaterial);
7040
+ const rollRing = new THREE11.Mesh(this.axisRingGeometry, this.rollRingMaterial);
6058
7041
  rollRing.renderOrder = 1e3;
6059
7042
  rollRing.userData.axis = "roll";
6060
- const rollHit = new THREE10.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
7043
+ const rollHit = new THREE11.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
6061
7044
  rollHit.rotation.copy(rollRing.rotation);
6062
7045
  rollHit.userData.axis = "roll";
6063
- const center = new THREE10.Mesh(this.axisCenterGeometry, this.axisCenterMaterial);
7046
+ const center = new THREE11.Mesh(this.axisCenterGeometry, this.axisCenterMaterial);
6064
7047
  center.renderOrder = 1001;
6065
7048
  this.axisGizmoMeshes = {
6066
7049
  pitch: pitchRing,
@@ -6100,11 +7083,11 @@ var SkeletonOverlay = class {
6100
7083
  this.boneTree = allBones.map((bone) => {
6101
7084
  const isMapped = this.mappedBoneNames.has(bone.name);
6102
7085
  const semanticKey = this.semanticKeyMap.get(bone.name) ?? null;
6103
- const boneChildren = bone.children.filter((c) => isBone2(c) && includedBones.has(c)).map((c) => c.name);
6104
- const parentName = bone.parent && isBone2(bone.parent) && includedBones.has(bone.parent) ? bone.parent.name : null;
7086
+ const boneChildren = bone.children.filter((c) => isBone3(c) && includedBones.has(c)).map((c) => c.name);
7087
+ const parentName = bone.parent && isBone3(bone.parent) && includedBones.has(bone.parent) ? bone.parent.name : null;
6105
7088
  let depth = 0;
6106
7089
  let current = bone.parent;
6107
- while (current && isBone2(current) && includedBones.has(current)) {
7090
+ while (current && isBone3(current) && includedBones.has(current)) {
6108
7091
  depth++;
6109
7092
  current = current.parent;
6110
7093
  }
@@ -6120,7 +7103,7 @@ var SkeletonOverlay = class {
6120
7103
  });
6121
7104
  for (const bone of allBones) {
6122
7105
  const boneChildren = bone.children.filter(
6123
- (c) => isBone2(c) && includedBones.has(c)
7106
+ (c) => isBone3(c) && includedBones.has(c)
6124
7107
  );
6125
7108
  if (boneChildren.length > 0) {
6126
7109
  if (shouldSkipRootHelperConnection(bone, boneChildren)) {
@@ -6134,15 +7117,15 @@ var SkeletonOverlay = class {
6134
7117
  }
6135
7118
  }
6136
7119
  this.group.add(this.axisGizmoGroup);
6137
- const sentinelGeo = new THREE10.SphereGeometry(1e-6, 4, 2);
6138
- const sentinelMat = new THREE10.MeshBasicMaterial({
7120
+ const sentinelGeo = new THREE11.SphereGeometry(1e-6, 4, 2);
7121
+ const sentinelMat = new THREE11.MeshBasicMaterial({
6139
7122
  transparent: true,
6140
7123
  opacity: 0,
6141
7124
  depthWrite: false,
6142
7125
  depthTest: false
6143
7126
  });
6144
7127
  sentinelMat.colorWrite = false;
6145
- const sentinel = new THREE10.Mesh(sentinelGeo, sentinelMat);
7128
+ const sentinel = new THREE11.Mesh(sentinelGeo, sentinelMat);
6146
7129
  sentinel.visible = true;
6147
7130
  sentinel.frustumCulled = false;
6148
7131
  sentinel.onBeforeRender = (renderer, _scene, camera) => this.update(camera, renderer.domElement);
@@ -6154,7 +7137,7 @@ var SkeletonOverlay = class {
6154
7137
  createBoneMesh(parentBone, childBone) {
6155
7138
  const isMapped = this.mappedBoneNames.has(parentBone.name);
6156
7139
  const material = isMapped ? this.mappedMaterial : this.unmappedMaterial;
6157
- const mesh = new THREE10.Mesh(this.sharedGeometry, material);
7140
+ const mesh = new THREE11.Mesh(this.sharedGeometry, material);
6158
7141
  mesh.name = `bone_vis_${parentBone.name}${childBone ? `_${childBone.name}` : "_leaf"}`;
6159
7142
  mesh.frustumCulled = false;
6160
7143
  mesh.renderOrder = 999;
@@ -6234,20 +7217,20 @@ var SkeletonOverlay = class {
6234
7217
  bone.getWorldPosition(_parentPos);
6235
7218
  let referenceLength = 0;
6236
7219
  for (const child of bone.children) {
6237
- if (!isBone2(child)) continue;
7220
+ if (!isBone3(child)) continue;
6238
7221
  child.getWorldPosition(_childPos);
6239
7222
  const nextDistance = _parentPos.distanceTo(_childPos);
6240
7223
  if (nextDistance <= 1e-4) continue;
6241
7224
  referenceLength = referenceLength === 0 ? nextDistance : Math.min(referenceLength, nextDistance);
6242
7225
  }
6243
- if (referenceLength <= 1e-4 && isBone2(bone.parent)) {
7226
+ if (referenceLength <= 1e-4 && isBone3(bone.parent)) {
6244
7227
  bone.parent.getWorldPosition(_childPos);
6245
7228
  referenceLength = _parentPos.distanceTo(_childPos);
6246
7229
  }
6247
7230
  if (referenceLength <= 1e-4) {
6248
7231
  referenceLength = LEAF_BONE_LENGTH;
6249
7232
  }
6250
- return THREE10.MathUtils.clamp(
7233
+ return THREE11.MathUtils.clamp(
6251
7234
  referenceLength * AXIS_GIZMO_RADIUS_FACTOR,
6252
7235
  AXIS_GIZMO_MIN_RADIUS,
6253
7236
  AXIS_GIZMO_MAX_RADIUS
@@ -6263,7 +7246,7 @@ var SkeletonOverlay = class {
6263
7246
  _cameraSpacePosition.copy(origin).applyMatrix4(perspectiveCamera.matrixWorldInverse);
6264
7247
  const depth = -_cameraSpacePosition.z;
6265
7248
  if (!Number.isFinite(depth) || depth <= perspectiveCamera.near) return null;
6266
- const visibleWorldHeight = 2 * depth * Math.tan(THREE10.MathUtils.degToRad(perspectiveCamera.getEffectiveFOV()) / 2);
7249
+ const visibleWorldHeight = 2 * depth * Math.tan(THREE11.MathUtils.degToRad(perspectiveCamera.getEffectiveFOV()) / 2);
6267
7250
  return visibleWorldHeight * (AXIS_GIZMO_SCREEN_RADIUS_PX / viewportHeight);
6268
7251
  }
6269
7252
  if (camera.isOrthographicCamera) {
@@ -6394,9 +7377,9 @@ var SkeletonOverlay = class {
6394
7377
  return {
6395
7378
  position: [_bonePos.x, _bonePos.y, _bonePos.z],
6396
7379
  rotation: [
6397
- THREE10.MathUtils.radToDeg(_boneEuler.x),
6398
- THREE10.MathUtils.radToDeg(_boneEuler.y),
6399
- THREE10.MathUtils.radToDeg(_boneEuler.z)
7380
+ THREE11.MathUtils.radToDeg(_boneEuler.x),
7381
+ THREE11.MathUtils.radToDeg(_boneEuler.y),
7382
+ THREE11.MathUtils.radToDeg(_boneEuler.z)
6400
7383
  ]
6401
7384
  };
6402
7385
  }
@@ -6666,7 +7649,7 @@ function buildMorphVertexPreviewRenderData(meshes, morphName, cache, options = {
6666
7649
  if (pointCount === 0) return null;
6667
7650
  const positions = previous?.positions.length === pointCount * 3 ? previous.positions : new Float32Array(pointCount * 3);
6668
7651
  const colors = previous?.colors.length === pointCount * 3 ? previous.colors : new Float32Array(pointCount * 3);
6669
- const vertex = new THREE10.Vector3();
7652
+ const vertex = new THREE11.Vector3();
6670
7653
  let cursor = 0;
6671
7654
  for (const { mesh, sample } of sampledMeshes) {
6672
7655
  mesh.updateWorldMatrix(true, false);
@@ -6760,7 +7743,7 @@ var MorphVertexPreview = class {
6760
7743
  this.points = null;
6761
7744
  }
6762
7745
  createPoints() {
6763
- const points = new THREE10.Points(new THREE10.BufferGeometry(), new THREE10.PointsMaterial({
7746
+ const points = new THREE11.Points(new THREE11.BufferGeometry(), new THREE11.PointsMaterial({
6764
7747
  size: 0.012,
6765
7748
  sizeAttenuation: true,
6766
7749
  transparent: true,
@@ -6798,8 +7781,8 @@ var MorphVertexPreview = class {
6798
7781
  const color = geometry.getAttribute("color");
6799
7782
  if (!position || !color || position.count !== data.pointCount || color.count !== data.pointCount) {
6800
7783
  if (position || color) geometry.dispose();
6801
- geometry.setAttribute("position", new THREE10.BufferAttribute(data.positions, 3));
6802
- geometry.setAttribute("color", new THREE10.BufferAttribute(data.colors, 3));
7784
+ geometry.setAttribute("position", new THREE11.BufferAttribute(data.positions, 3));
7785
+ geometry.setAttribute("color", new THREE11.BufferAttribute(data.colors, 3));
6803
7786
  return;
6804
7787
  }
6805
7788
  if (position.array !== data.positions) position.array.set(data.positions);
@@ -6859,7 +7842,7 @@ var transform = (obj) => ({
6859
7842
  rotation: { x: obj.quaternion.x, y: obj.quaternion.y, z: obj.quaternion.z, w: obj.quaternion.w },
6860
7843
  scale: { x: obj.scale.x, y: obj.scale.y, z: obj.scale.z }
6861
7844
  });
6862
- var snapshot = (obj, reference) => {
7845
+ var snapshot2 = (obj, reference) => {
6863
7846
  if (!reference) {
6864
7847
  return {
6865
7848
  obj,
@@ -6909,6 +7892,7 @@ var ThreeModelInspector = class {
6909
7892
  const allMeshes = [];
6910
7893
  const boneObjects = [];
6911
7894
  model.traverse((obj) => {
7895
+ if (highlightOverlays.has(obj)) return;
6912
7896
  objects.push(obj);
6913
7897
  if (obj.isMesh) allMeshes.push(obj);
6914
7898
  if (obj.isBone || obj.type === "Bone") boneObjects.push(obj);
@@ -6944,7 +7928,7 @@ var ThreeModelInspector = class {
6944
7928
  }
6945
7929
  const bones = {};
6946
7930
  for (const bone of boneObjects) {
6947
- if (bone.name) bones[bone.name] = snapshot(bone, references?.get(bone));
7931
+ if (bone.name) bones[bone.name] = snapshot2(bone, references?.get(bone));
6948
7932
  }
6949
7933
  const profile = options.profile;
6950
7934
  if (profile?.boneNodes) {
@@ -6953,7 +7937,7 @@ var ThreeModelInspector = class {
6953
7937
  const suffix = profile.boneSuffix ?? "";
6954
7938
  const name = `${base.startsWith(prefix) ? "" : prefix}${base}${base.endsWith(suffix) ? "" : suffix}`;
6955
7939
  const object = model.getObjectByName(name) ?? model.getObjectByName(base);
6956
- if (object) bones[key] = bones[object.name] ?? snapshot(object, references?.get(object));
7940
+ if (object) bones[key] = bones[object.name] ?? snapshot2(object, references?.get(object));
6957
7941
  }
6958
7942
  }
6959
7943
  return {
@@ -7135,15 +8119,17 @@ function serializeAnimationClips(model, clips, inspection) {
7135
8119
  });
7136
8120
  }
7137
8121
  var materials = (value) => value ? Array.isArray(value) ? value : [value] : [];
8122
+ var highlightOverlays = /* @__PURE__ */ new WeakSet();
7138
8123
  var ThreeFrameApplier = class {
7139
8124
  constructor() {
7140
8125
  __publicField(this, "meshes", /* @__PURE__ */ new Map());
7141
8126
  __publicField(this, "morphs", /* @__PURE__ */ new Map());
7142
8127
  __publicField(this, "bones", /* @__PURE__ */ new Map());
7143
8128
  __publicField(this, "objects", /* @__PURE__ */ new Map());
7144
- __publicField(this, "originalEmissive", /* @__PURE__ */ new Map());
8129
+ __publicField(this, "highlighted", /* @__PURE__ */ new Map());
7145
8130
  }
7146
8131
  setBindings(inspection) {
8132
+ this.clearMeshHighlights();
7147
8133
  this.meshes = new Map(inspection.meshBindings);
7148
8134
  this.morphs = new Map(inspection.morphBindings);
7149
8135
  this.bones = new Map(inspection.boneBindings);
@@ -7189,7 +8175,7 @@ var ThreeFrameApplier = class {
7189
8175
  const config = configs[object.name];
7190
8176
  if (!config) return;
7191
8177
  if (typeof config.visible === "boolean") object.visible = config.visible;
7192
- if (config.material) this.applyMaterial(object, config.material);
8178
+ if (config.material) this.applyHighlightedMeshMaterial(object, config.material);
7193
8179
  });
7194
8180
  }
7195
8181
  getMeshMaterialConfig(root, name) {
@@ -7197,38 +8183,97 @@ var ThreeFrameApplier = class {
7197
8183
  this.visitMesh(root, name, (mesh) => {
7198
8184
  const material = materials(mesh.material)[0];
7199
8185
  if (!material) return;
7200
- const blending = Object.entries(THREE_BLENDING_MODES).find(([, value]) => value === material.blending)?.[0] ?? "Normal";
8186
+ const blending = Object.keys(THREE_BLENDING_MODES).find((key) => THREE_BLENDING_MODES[key] === material.blending) ?? "Normal";
7201
8187
  result = { renderOrder: mesh.renderOrder, transparent: material.transparent, opacity: material.opacity, depthWrite: material.depthWrite, depthTest: material.depthTest, blending };
7202
8188
  });
7203
8189
  return result;
7204
8190
  }
7205
8191
  setMeshMaterialConfig(root, name, config) {
7206
- this.visitMesh(root, name, (mesh) => this.applyMaterial(mesh, config));
8192
+ this.visitMesh(root, name, (mesh) => this.applyHighlightedMeshMaterial(mesh, config));
8193
+ }
8194
+ applyHighlightedMeshMaterial(mesh, config) {
8195
+ this.applyMaterial(mesh, config);
8196
+ const original = this.highlighted.get(mesh)?.original;
8197
+ if (!original) return;
8198
+ const highlighted = mesh.material;
8199
+ try {
8200
+ mesh.material = original;
8201
+ this.applyMaterial(mesh, config);
8202
+ } finally {
8203
+ mesh.material = highlighted;
8204
+ }
7207
8205
  }
7208
8206
  setMeshVisible(root, name, visible) {
7209
8207
  this.visitMesh(root, name, (mesh) => {
7210
8208
  mesh.visible = visible;
7211
8209
  });
7212
8210
  }
7213
- highlightMesh(root, name, color = 65535, intensity2 = 0.5) {
7214
- root.traverse((object) => {
7215
- if (!object.isMesh) return;
7216
- for (const material of materials(object.material)) {
8211
+ /** Apply a complete Rust snapshot. Only native resources and ID bindings live here. */
8212
+ applyMeshHighlightFrame(frame) {
8213
+ if (frame.version !== 1) throw new Error(`Unsupported mesh highlight frame version: ${frame.version}`);
8214
+ const selected = new Set(frame.highlights.map((write) => this.meshes.get(write.meshId)));
8215
+ for (const mesh of this.highlighted.keys()) {
8216
+ if (!selected.has(mesh)) this.clearMeshHighlight(mesh);
8217
+ }
8218
+ for (const write of frame.highlights) {
8219
+ if (write.edgeMode !== "triangles") throw new Error(`Unsupported highlight edge mode: ${write.edgeMode}`);
8220
+ const mesh = this.meshes.get(write.meshId);
8221
+ if (!mesh) continue;
8222
+ let state = this.highlighted.get(mesh);
8223
+ if (!state) {
8224
+ const original = mesh.material;
8225
+ const copies = materials(original).map((material) => {
8226
+ const copy2 = material.clone();
8227
+ copy2.polygonOffset = true;
8228
+ copy2.polygonOffsetFactor = 1;
8229
+ copy2.polygonOffsetUnits = 1;
8230
+ return copy2;
8231
+ });
8232
+ mesh.material = Array.isArray(original) ? copies : copies[0];
8233
+ const overlay = mesh.clone(false);
8234
+ overlay.name = "";
8235
+ overlay.userData = {};
8236
+ overlay.position.set(0, 0, 0);
8237
+ overlay.quaternion.identity();
8238
+ overlay.scale.set(1, 1, 1);
8239
+ overlay.matrix.identity();
8240
+ overlay.matrixAutoUpdate = true;
8241
+ overlay.visible = true;
8242
+ overlay.castShadow = overlay.receiveShadow = false;
8243
+ overlay.raycast = () => {
8244
+ };
8245
+ overlay.material = new MeshBasicMaterial({ wireframe: write.edgeMode === "triangles", transparent: true, depthWrite: false, toneMapped: false });
8246
+ overlay.onBeforeRender = () => {
8247
+ overlay.geometry = mesh.geometry;
8248
+ overlay.morphTargetInfluences = mesh.morphTargetInfluences;
8249
+ };
8250
+ highlightOverlays.add(overlay);
8251
+ mesh.add(overlay);
8252
+ state = { original, overlay };
8253
+ this.highlighted.set(mesh, state);
8254
+ }
8255
+ for (const material of materials(mesh.material)) {
7217
8256
  if (!material.emissive) continue;
7218
- const key = `${object.uuid}:${material.uuid}`;
7219
- if (name === object.name) {
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
- }
8257
+ material.emissive.setRGB(...write.fillColor);
8258
+ material.emissiveIntensity = write.fillIntensity;
7230
8259
  }
7231
- });
8260
+ const edges = state.overlay.material;
8261
+ edges.color.setRGB(...write.edgeColor);
8262
+ edges.opacity = write.edgeOpacity;
8263
+ edges.side = materials(mesh.material)[0]?.side ?? edges.side;
8264
+ state.overlay.renderOrder = mesh.renderOrder + 1;
8265
+ }
8266
+ }
8267
+ clearMeshHighlights() {
8268
+ for (const mesh of this.highlighted.keys()) this.clearMeshHighlight(mesh);
8269
+ }
8270
+ clearMeshHighlight(mesh) {
8271
+ const state = this.highlighted.get(mesh);
8272
+ state.overlay.removeFromParent();
8273
+ state.overlay.material.dispose();
8274
+ for (const material of materials(mesh.material)) material.dispose();
8275
+ mesh.material = state.original;
8276
+ this.highlighted.delete(mesh);
7232
8277
  }
7233
8278
  applyHairAppearance(meshes, appearance) {
7234
8279
  for (const mesh of meshes) {
@@ -7389,11 +8434,11 @@ var ThreeFrameApplier = class {
7389
8434
  var collectMorphMeshes = (root) => {
7390
8435
  const meshes = [];
7391
8436
  root.traverse((object) => {
7392
- if (object.isMesh && morphEntries(object).length > 0) meshes.push(object);
8437
+ if (object.isMesh && !highlightOverlays.has(object) && morphEntries(object).length > 0) meshes.push(object);
7393
8438
  });
7394
8439
  return meshes;
7395
8440
  };
7396
8441
 
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 };
8442
+ export { BLENDING_MODES, CHARACTER_SCENE_TYPES, CHARACTER_SCENE_TYPE_IDS, ThreeAnnotationController as CameraController, CameraDOMControls, CameraDOMControls as Controls, DEFAULT_CHARACTER_LIGHTING_PRESETS, DEFAULT_CHARACTER_LIGHTING_PRESET_ID, DEFAULT_CHARACTER_LIGHTING_PRESET_IDS, DEFAULT_CHARACTER_LIGHTING_SETTINGS, DEFAULT_CHARACTER_SCENE_TYPE_ID, DEFAULT_MARKER_AUTO_HIDE_MS, DEFAULT_PREVIEW_AUTO_CLEAR_MS, Embody, HtmlAnnotationMarkers, ThreeAnnotationMarkers as Markers3D, HtmlAnnotationMarkers as MarkersHTML, MorphVertexPreview, HtmlAnnotationMarkers as OverlayAnnotationMarkers, RUNTIME_ANNOTATION_REGION_PREFIX, RustAnnotationCameraCore, SkeletonOverlay, THREE_BLENDING_MODES, ThreeAnnotationController, ThreeAnnotationMarkers, ThreeAnnotations, ThreeFrameApplier, ThreeModelInspector, ThreeNativePlaybackConnector, acquireCharacterSceneVideoCapture, analyzeModel, applyAUBoneBindingUpdate, applyBilateralAxisBindingUpdate, applyBoneAxisBindingUpdate, applyCharacterModelTransform, bindModelReferencePose, boneResolutionProfile, buildBoneAuOptions, buildMappedBoneInfo, buildMorphVertexPreviewRenderData, buildMorphVertexPreviewSample, buildRegionDisplayOptions, captureModelReferencePose, classifyAuAsJointControl, collectMorphMeshes, collectSkeletonBones, computeHumanoidSkeletonTemplateRestBounds, convertMixamoFbxToVrmAnimations, convertMixamoToVrmAnimation, createAnimationClipFromClipIR, createAnimationClipFromVrmAnimation, createBilateralBoneAxisAu, createBoneAxisAu, createCharacterHost, createDefaultCharacterLighting, createDefaultCharacterScene, createDefaultCharacterSceneAsync, createDefaultCharacterSceneRuntime, createMarkerLifecycleRevealOptions, createMarkerVisibilityLifecycle, createRuntimeAnnotationPreviewLifecycle, createRustAnnotationCameraCore, createRustEmbodyHost, createRustEmbodyHostUnbound, createScenePlaybackFrame, createSceneSceneryRenderer, createShadowPlane, createValidationSkeletonFromHumanoidTemplate, detectAnnotationLaterality, disposeCharacterModel, ensureBilateralBoneNodeKeys, ensureBoneNodeKey, extendCharacterConfigWithPreset, extendModelReferencePose, extractHumanoidSkeletonTemplateFromModel, extractModelData, extractProfileOverrides, filterCameraViewRegions, findModelRoot, findNodeKeyForBone, formatAxisDirectionLabel, formatAxisLabel, fuzzyNameMatch, getAUBoneBindingState, getAUBoneBindingStates, getAxisFromChannel, getBilateralAxisBindingState, getBoneAxisBindingState, getDefaultAnnotationLaterality, getHumanoidSkeletonTemplate, getHumanoidSkeletonTemplateBoneNames, getHumanoidSpecification, getMeshNamesForAUProfile, getModelLocalOrbitAngle, getPreset, getRuntimeAUMeshSideOffset, getRuntimeAUMorphAnchorPoint, getRuntimeAnnotationSide, getSemanticHorizontalSign, getSemanticHorizontalSignForSide, getWorldDirectionForCameraAngle, hasLeftRightMorphs, inferChiralBoneNamePair, inferEyeControlFamily, inferEyeControlScope, inferRuntimeAnnotationPreviewSide, inspectVrmAnimation, isJointControlAuInfo, isMaxDegreesOnlyAxisBindingUpdate, isMixedAU, isRuntimeAnnotationRegionName, loadCharacterModel, mergeCharacterRegionsByName, normalizeDefaultCharacterLightingSettings, normalizeHairColorAppearance, parseCharacterModel, passesMarkerCameraAngleGate, pickContinuumAnnotationAuId, renderScenePlayback, resolveAnnotationCharacterConfig, resolveBoneAxisChannel, resolveBoneNameForNodeKey, resolveBoneNames, resolveContinuumDisplayLabel, resolveFaceCenter, resolveHumanoidCharacterization, resolveMarkerAnchorRegion, resolveMorphMeshSelections, resolveMorphPreviewMeshes, resolveRegionCameraAngle, resolveRegionVisibilityCameraAngle, resolveSkeletonOverlayStartBoneName, resolveVisemeProfile, serializeAnimationClips, setAUMorphTargets, setHumanoidRoleBinding, shouldProjectMarkerAnchorToSurface, shouldUseFaceCenterForMarkerAnchor, stripConfiguredBoneAffixes, supportsRustEmbodyPreset, toWorldDirection, validateHumanoidCharacterization, validateMappings };
7398
8443
  //# sourceMappingURL=index.js.map
7399
8444
  //# sourceMappingURL=index.js.map