@lovelace_lol/embody 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -40
- package/dist/index.js +1760 -118
- package/dist/index.js.map +1 -1
- package/dist/three/authoring.d.ts +3 -3
- package/dist/three/authoringTypes.d.ts +0 -91
- package/dist/three/character.d.ts +2 -8
- package/dist/three/characterHost.d.ts +33 -5
- package/dist/three/characterTexturePalette.d.ts +6 -0
- package/dist/three/editor/SkeletonOverlay.d.ts +128 -0
- package/dist/three/editor/morphVertexPreview.d.ts +35 -0
- package/dist/three/index.d.ts +2 -0
- package/dist/three/modelMorphLayout.d.ts +13 -0
- package/dist/three/scene.d.ts +5 -0
- package/dist/three/sceneInkBackground.d.ts +11 -0
- package/dist/three/sceneInkBackgroundStyle.d.ts +5 -0
- package/dist/three.js +1760 -118
- package/dist/three.js.map +1 -1
- package/dist/wasm/README.md +37 -40
- package/dist/wasm/auDescriptors.d.ts +35 -0
- package/dist/wasm/embody_wasm.d.ts +52 -96
- package/dist/wasm/embody_wasm.js +12 -147
- package/dist/wasm/embody_wasm_bg.wasm +0 -0
- package/dist/wasm/embody_wasm_bg.wasm.d.ts +48 -56
- package/dist/wasm/index.d.ts +2 -0
- package/dist/wasm.js +288 -1
- package/dist/wasm.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as THREE3 from 'three';
|
|
|
2
2
|
import { NoBlending, MultiplyBlending, SubtractiveBlending, AdditiveBlending, NormalBlending, Vector3, Box3, Timer, Matrix4, AnimationMixer, AdditiveAnimationBlendMode, NormalAnimationBlendMode, LoopOnce, LoopRepeat, LoopPingPong, PropertyBinding, Euler, Quaternion, AnimationUtils, InterpolateDiscrete, InterpolateLinear, 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
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
|
-
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, VISEME_KEYS } from '@lovelace_lol/embody/wasm';
|
|
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
6
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
7
7
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
8
8
|
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
|
|
@@ -6583,9 +6583,6 @@ var resolveContinuumDisplayLabel = (profile, negativeAuId, positiveAuId) => requ
|
|
|
6583
6583
|
function inspectModel(model, meshes, profile) {
|
|
6584
6584
|
return new ThreeModelInspector().inspectModel(model, { meshes, profile });
|
|
6585
6585
|
}
|
|
6586
|
-
function getBodyControls(profile, model) {
|
|
6587
|
-
return request2("profile.getBodyControls", { profile, ...model ? { model: inspectModel(model, void 0, profile).descriptor } : {} });
|
|
6588
|
-
}
|
|
6589
6586
|
function extractModelData(model, meshes, animations = []) {
|
|
6590
6587
|
const inspection = inspectModel(model, meshes);
|
|
6591
6588
|
return JSON.parse(requireInitializedEmbodyCore().extract_model_data_json(json(inspection.descriptor), json(serializeAnimationClips(model, animations, inspection))));
|
|
@@ -7437,12 +7434,10 @@ var Embody = class _Embody {
|
|
|
7437
7434
|
return parse(this.core.build_clip(source.name, json2(source.input), json2(buildOptions)));
|
|
7438
7435
|
case "typed":
|
|
7439
7436
|
return parse(this.core.build_typed_clip(source.name, json2(source.input), json2(buildOptions)));
|
|
7440
|
-
case "semantic":
|
|
7441
|
-
return parse(this.core.build_semantic_clip(source.name, json2(source.input), json2(buildOptions)));
|
|
7442
7437
|
case "compiled":
|
|
7443
7438
|
return source.clip;
|
|
7444
7439
|
case "native":
|
|
7445
|
-
throw new Error("Native animation sources do not require
|
|
7440
|
+
throw new Error("Native animation sources do not require AU compilation.");
|
|
7446
7441
|
}
|
|
7447
7442
|
}
|
|
7448
7443
|
restValues(track) {
|
|
@@ -7881,7 +7876,8 @@ var Embody = class _Embody {
|
|
|
7881
7876
|
return this.handle(source, options, play);
|
|
7882
7877
|
}
|
|
7883
7878
|
buildClip(name, curves, options = {}) {
|
|
7884
|
-
|
|
7879
|
+
if (options.semanticAnimation) throw new Error("Decode legacy named animation tracks to numeric AU curves before building a clip.");
|
|
7880
|
+
return this.build({ name, kind: "curves", input: copy(curves), options: copy(options) }, options, false);
|
|
7885
7881
|
}
|
|
7886
7882
|
snippetToClip(name, curves, options = {}) {
|
|
7887
7883
|
return this.buildClip(name, curves, options);
|
|
@@ -7892,9 +7888,6 @@ var Embody = class _Embody {
|
|
|
7892
7888
|
typedSnippetToClip(name, channels, options = {}) {
|
|
7893
7889
|
return this.buildTypedClip(name, channels, options);
|
|
7894
7890
|
}
|
|
7895
|
-
semanticAnimationToClip(name, animation, options = {}) {
|
|
7896
|
-
return this.build({ name, kind: "semantic", input: copy(animation), options: copy(options) }, options, false);
|
|
7897
|
-
}
|
|
7898
7891
|
playSnippet(snippet, curvesOrOptions = {}, options = {}) {
|
|
7899
7892
|
return typeof snippet === "string" ? this.buildClip(snippet, curvesOrOptions, options).play() : this.buildClip(snippet.name, snippet.curves, curvesOrOptions).play();
|
|
7900
7893
|
}
|
|
@@ -8104,26 +8097,8 @@ var Embody = class _Embody {
|
|
|
8104
8097
|
getAUBalance(id) {
|
|
8105
8098
|
return this.core.get_au_balance(id);
|
|
8106
8099
|
}
|
|
8107
|
-
|
|
8108
|
-
return parse(this.core.
|
|
8109
|
-
}
|
|
8110
|
-
getSemanticPoseCatalog() {
|
|
8111
|
-
return parse(this.core.get_semantic_pose_catalog_json());
|
|
8112
|
-
}
|
|
8113
|
-
captureSemanticPose() {
|
|
8114
|
-
return parse(this.core.capture_semantic_pose_json());
|
|
8115
|
-
}
|
|
8116
|
-
validateSemanticPose(pose) {
|
|
8117
|
-
return parse(this.core.validate_semantic_pose_json(json2(pose)));
|
|
8118
|
-
}
|
|
8119
|
-
applySemanticPose(pose) {
|
|
8120
|
-
this.mutateLive(() => this.core.apply_semantic_pose_json(json2(pose)));
|
|
8121
|
-
}
|
|
8122
|
-
validateSemanticAnimation(animation, options = {}) {
|
|
8123
|
-
this.core.validate_semantic_animation_json(json2(animation), json2(options));
|
|
8124
|
-
}
|
|
8125
|
-
resetBodyControls() {
|
|
8126
|
-
this.mutateLive(() => this.core.reset_body_controls());
|
|
8100
|
+
getAUDescriptors() {
|
|
8101
|
+
return parse(this.core.get_au_descriptors_json());
|
|
8127
8102
|
}
|
|
8128
8103
|
setContinuum(negative, positive, value, balance = NaN) {
|
|
8129
8104
|
this.mutateLive(() => this.core.set_continuum(negative, positive, value, balance));
|
|
@@ -8403,6 +8378,262 @@ async function createRustEmbodyHost(model, config = {}) {
|
|
|
8403
8378
|
throw error;
|
|
8404
8379
|
}
|
|
8405
8380
|
}
|
|
8381
|
+
|
|
8382
|
+
// three/sceneInkBackgroundStyle.ts
|
|
8383
|
+
var SCENE_INK_BASE_GRADIENT = "radial-gradient(ellipse at 50% 42%, #46535c 0%, #35414a 42%, #29333b 76%, #252e35 100%)";
|
|
8384
|
+
var DEFAULT_PALETTE = ["#7cb8b2", "#627888", "#8b989f"];
|
|
8385
|
+
function createSceneInkBackgroundCss(palette = DEFAULT_PALETTE) {
|
|
8386
|
+
const [a, b, c] = palette;
|
|
8387
|
+
return [
|
|
8388
|
+
`radial-gradient(ellipse 46% 62% at calc(20% + var(--scene-ink-shift-x, 0%)) 30%, color-mix(in srgb, ${a} 24%, transparent), transparent 78%)`,
|
|
8389
|
+
`radial-gradient(ellipse 32% 44% at 34% calc(52% + var(--scene-ink-shift-y, 0%)), color-mix(in srgb, ${a} 12%, transparent), transparent 82%)`,
|
|
8390
|
+
`radial-gradient(ellipse 48% 60% at calc(82% - var(--scene-ink-shift-x, 0%)) 62%, color-mix(in srgb, ${b} 22%, transparent), transparent 80%)`,
|
|
8391
|
+
`radial-gradient(ellipse 28% 38% at 68% calc(26% - var(--scene-ink-shift-y, 0%)), color-mix(in srgb, ${b} 10%, transparent), transparent 82%)`,
|
|
8392
|
+
`radial-gradient(ellipse 50% 38% at 38% calc(78% + var(--scene-ink-shift-y, 0%)), color-mix(in srgb, ${c} 18%, transparent), transparent 82%)`,
|
|
8393
|
+
SCENE_INK_BASE_GRADIENT
|
|
8394
|
+
].join(", ");
|
|
8395
|
+
}
|
|
8396
|
+
createSceneInkBackgroundCss();
|
|
8397
|
+
|
|
8398
|
+
// three/sceneInkBackground.ts
|
|
8399
|
+
var toCssColor = (tone) => `#${new THREE3.Color(tone).getHexString()}`;
|
|
8400
|
+
function createSceneInkBackground(onBackgroundChange) {
|
|
8401
|
+
let currentBackground = "";
|
|
8402
|
+
let disposed = false;
|
|
8403
|
+
const applyBackground = (palette) => {
|
|
8404
|
+
if (disposed) return;
|
|
8405
|
+
const colors = palette ? [toCssColor(palette[0]), toCssColor(palette[1]), toCssColor(palette[2])] : void 0;
|
|
8406
|
+
const background = createSceneInkBackgroundCss(colors);
|
|
8407
|
+
if (background === currentBackground) return;
|
|
8408
|
+
currentBackground = background;
|
|
8409
|
+
onBackgroundChange?.(background);
|
|
8410
|
+
};
|
|
8411
|
+
applyBackground();
|
|
8412
|
+
return {
|
|
8413
|
+
setPalette: applyBackground,
|
|
8414
|
+
dispose: () => {
|
|
8415
|
+
if (disposed) return;
|
|
8416
|
+
disposed = true;
|
|
8417
|
+
}
|
|
8418
|
+
};
|
|
8419
|
+
}
|
|
8420
|
+
var paletteCache = /* @__PURE__ */ new Map();
|
|
8421
|
+
var MAX_TEXTURE_SAMPLE_SIZE = 10;
|
|
8422
|
+
var MIN_SAMPLE_SATURATION = 0.04;
|
|
8423
|
+
var MIN_CANDIDATE_DISTANCE = 0.12;
|
|
8424
|
+
function resolveCharacterMaterialPalette(model, cacheKey) {
|
|
8425
|
+
const cached = getCachedPalette(cacheKey);
|
|
8426
|
+
if (cached.materialPalette !== void 0) {
|
|
8427
|
+
return cached.materialPalette;
|
|
8428
|
+
}
|
|
8429
|
+
const samples = collectPaletteSamples(model, false);
|
|
8430
|
+
const palette = buildPalette(samples);
|
|
8431
|
+
cached.materialPalette = palette;
|
|
8432
|
+
return palette;
|
|
8433
|
+
}
|
|
8434
|
+
function resolveCharacterTexturePalette(model, cacheKey) {
|
|
8435
|
+
const cached = getCachedPalette(cacheKey);
|
|
8436
|
+
if (cached.texturePalette !== void 0) {
|
|
8437
|
+
return Promise.resolve(cached.texturePalette);
|
|
8438
|
+
}
|
|
8439
|
+
if (cached.texturePalettePromise) {
|
|
8440
|
+
return cached.texturePalettePromise;
|
|
8441
|
+
}
|
|
8442
|
+
cached.texturePalettePromise = schedulePaletteExtraction(() => {
|
|
8443
|
+
const samples = collectPaletteSamples(model, true);
|
|
8444
|
+
const palette = buildPalette(samples);
|
|
8445
|
+
cached.texturePalette = palette;
|
|
8446
|
+
return palette;
|
|
8447
|
+
});
|
|
8448
|
+
return cached.texturePalettePromise;
|
|
8449
|
+
}
|
|
8450
|
+
function getCachedPalette(cacheKey) {
|
|
8451
|
+
const cached = paletteCache.get(cacheKey) ?? {};
|
|
8452
|
+
paletteCache.set(cacheKey, cached);
|
|
8453
|
+
return cached;
|
|
8454
|
+
}
|
|
8455
|
+
function schedulePaletteExtraction(extractPalette) {
|
|
8456
|
+
return new Promise((resolve) => {
|
|
8457
|
+
const run = () => resolve(extractPalette());
|
|
8458
|
+
if (typeof window !== "undefined" && "requestIdleCallback" in window) {
|
|
8459
|
+
window.requestIdleCallback(run, { timeout: 700 });
|
|
8460
|
+
return;
|
|
8461
|
+
}
|
|
8462
|
+
globalThis.setTimeout(run, 0);
|
|
8463
|
+
});
|
|
8464
|
+
}
|
|
8465
|
+
function collectPaletteSamples(model, includeTextures) {
|
|
8466
|
+
const samples = [];
|
|
8467
|
+
const seenMaterials = /* @__PURE__ */ new Set();
|
|
8468
|
+
const seenTextures = /* @__PURE__ */ new Set();
|
|
8469
|
+
model.traverse((object) => {
|
|
8470
|
+
const mesh = object;
|
|
8471
|
+
if (!mesh.isMesh || !mesh.material) {
|
|
8472
|
+
return;
|
|
8473
|
+
}
|
|
8474
|
+
const materials2 = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
|
|
8475
|
+
for (const material of materials2) {
|
|
8476
|
+
if (!material || seenMaterials.has(material.uuid)) {
|
|
8477
|
+
continue;
|
|
8478
|
+
}
|
|
8479
|
+
seenMaterials.add(material.uuid);
|
|
8480
|
+
collectMaterialSamples(material, samples, seenTextures, includeTextures);
|
|
8481
|
+
}
|
|
8482
|
+
});
|
|
8483
|
+
return samples;
|
|
8484
|
+
}
|
|
8485
|
+
function collectMaterialSamples(material, samples, seenTextures, includeTextures) {
|
|
8486
|
+
pushColorSample(samples, material.color, 1.25);
|
|
8487
|
+
pushColorSample(samples, material.emissive, 0.8);
|
|
8488
|
+
if (!includeTextures) {
|
|
8489
|
+
return;
|
|
8490
|
+
}
|
|
8491
|
+
if (material.map && !seenTextures.has(material.map.uuid)) {
|
|
8492
|
+
seenTextures.add(material.map.uuid);
|
|
8493
|
+
samples.push(...sampleTexture(material.map));
|
|
8494
|
+
}
|
|
8495
|
+
if (material.emissiveMap && !seenTextures.has(material.emissiveMap.uuid)) {
|
|
8496
|
+
seenTextures.add(material.emissiveMap.uuid);
|
|
8497
|
+
samples.push(...sampleTexture(material.emissiveMap).map(({ color, score }) => ({
|
|
8498
|
+
color,
|
|
8499
|
+
score: score * 0.65
|
|
8500
|
+
})));
|
|
8501
|
+
}
|
|
8502
|
+
}
|
|
8503
|
+
function pushColorSample(samples, color, score) {
|
|
8504
|
+
if (!color || !isUsableColor(color)) {
|
|
8505
|
+
return;
|
|
8506
|
+
}
|
|
8507
|
+
samples.push({
|
|
8508
|
+
color: color.clone(),
|
|
8509
|
+
score
|
|
8510
|
+
});
|
|
8511
|
+
}
|
|
8512
|
+
function sampleTexture(texture) {
|
|
8513
|
+
if (typeof document === "undefined") {
|
|
8514
|
+
return [];
|
|
8515
|
+
}
|
|
8516
|
+
const image = texture.image;
|
|
8517
|
+
const imageSize = getImageSize(image);
|
|
8518
|
+
if (!image || !imageSize) {
|
|
8519
|
+
return [];
|
|
8520
|
+
}
|
|
8521
|
+
const canvas = document.createElement("canvas");
|
|
8522
|
+
const width = Math.min(MAX_TEXTURE_SAMPLE_SIZE, imageSize.width);
|
|
8523
|
+
const height = Math.min(MAX_TEXTURE_SAMPLE_SIZE, imageSize.height);
|
|
8524
|
+
canvas.width = width;
|
|
8525
|
+
canvas.height = height;
|
|
8526
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
8527
|
+
if (!context) {
|
|
8528
|
+
return [];
|
|
8529
|
+
}
|
|
8530
|
+
try {
|
|
8531
|
+
context.drawImage(image, 0, 0, width, height);
|
|
8532
|
+
const data = context.getImageData(0, 0, width, height).data;
|
|
8533
|
+
const samples = [];
|
|
8534
|
+
for (let index = 0; index < data.length; index += 4) {
|
|
8535
|
+
const alpha = data[index + 3] / 255;
|
|
8536
|
+
if (alpha < 0.18) {
|
|
8537
|
+
continue;
|
|
8538
|
+
}
|
|
8539
|
+
const color = new THREE3.Color(
|
|
8540
|
+
data[index] / 255,
|
|
8541
|
+
data[index + 1] / 255,
|
|
8542
|
+
data[index + 2] / 255
|
|
8543
|
+
);
|
|
8544
|
+
if (!isUsableColor(color)) {
|
|
8545
|
+
continue;
|
|
8546
|
+
}
|
|
8547
|
+
const hsl = { h: 0, s: 0, l: 0 };
|
|
8548
|
+
color.getHSL(hsl);
|
|
8549
|
+
samples.push({
|
|
8550
|
+
color,
|
|
8551
|
+
score: alpha * (0.4 + hsl.s * 1.3)
|
|
8552
|
+
});
|
|
8553
|
+
}
|
|
8554
|
+
return samples;
|
|
8555
|
+
} catch {
|
|
8556
|
+
return [];
|
|
8557
|
+
}
|
|
8558
|
+
}
|
|
8559
|
+
function getImageSize(image) {
|
|
8560
|
+
if (!image || !("width" in image) || !("height" in image)) {
|
|
8561
|
+
return null;
|
|
8562
|
+
}
|
|
8563
|
+
const width = Number(image.width);
|
|
8564
|
+
const height = Number(image.height);
|
|
8565
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
8566
|
+
return null;
|
|
8567
|
+
}
|
|
8568
|
+
return { width, height };
|
|
8569
|
+
}
|
|
8570
|
+
function isUsableColor(color) {
|
|
8571
|
+
const hsl = { h: 0, s: 0, l: 0 };
|
|
8572
|
+
color.getHSL(hsl);
|
|
8573
|
+
return hsl.l > 0.05 && hsl.l < 0.94 && hsl.s >= MIN_SAMPLE_SATURATION;
|
|
8574
|
+
}
|
|
8575
|
+
function buildPalette(samples) {
|
|
8576
|
+
if (samples.length === 0) {
|
|
8577
|
+
return null;
|
|
8578
|
+
}
|
|
8579
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
8580
|
+
for (const sample of samples) {
|
|
8581
|
+
const hsl = { h: 0, s: 0, l: 0 };
|
|
8582
|
+
sample.color.getHSL(hsl);
|
|
8583
|
+
const bucketId = Math.floor(hsl.h * 18);
|
|
8584
|
+
const bucket = buckets.get(bucketId) ?? { color: new THREE3.Color(0, 0, 0), score: 0 };
|
|
8585
|
+
bucket.color.r += sample.color.r * sample.score;
|
|
8586
|
+
bucket.color.g += sample.color.g * sample.score;
|
|
8587
|
+
bucket.color.b += sample.color.b * sample.score;
|
|
8588
|
+
bucket.score += sample.score;
|
|
8589
|
+
buckets.set(bucketId, bucket);
|
|
8590
|
+
}
|
|
8591
|
+
const candidates = Array.from(buckets.values()).filter((bucket) => bucket.score > 0).map((bucket) => ({
|
|
8592
|
+
color: massageBackgroundTone(bucket.color.multiplyScalar(1 / bucket.score)),
|
|
8593
|
+
score: bucket.score
|
|
8594
|
+
})).sort((a, b) => b.score - a.score);
|
|
8595
|
+
const selected = [];
|
|
8596
|
+
for (const candidate of candidates) {
|
|
8597
|
+
const distinctEnough = selected.every((color) => colorDistance(color, candidate.color) >= MIN_CANDIDATE_DISTANCE);
|
|
8598
|
+
if (!distinctEnough) {
|
|
8599
|
+
continue;
|
|
8600
|
+
}
|
|
8601
|
+
selected.push(candidate.color);
|
|
8602
|
+
if (selected.length === 3) {
|
|
8603
|
+
break;
|
|
8604
|
+
}
|
|
8605
|
+
}
|
|
8606
|
+
if (selected.length === 0) {
|
|
8607
|
+
return null;
|
|
8608
|
+
}
|
|
8609
|
+
while (selected.length < 3) {
|
|
8610
|
+
selected.push(createCompanionTone(selected[0], selected.length));
|
|
8611
|
+
}
|
|
8612
|
+
return [selected[0], selected[1], selected[2]];
|
|
8613
|
+
}
|
|
8614
|
+
function massageBackgroundTone(color) {
|
|
8615
|
+
const hsl = { h: 0, s: 0, l: 0 };
|
|
8616
|
+
color.getHSL(hsl);
|
|
8617
|
+
const saturation = THREE3.MathUtils.clamp(Math.max(hsl.s * 1.35, 0.46), 0.46, 0.82);
|
|
8618
|
+
const lightness = THREE3.MathUtils.clamp(hsl.l + 0.08, 0.42, 0.66);
|
|
8619
|
+
return new THREE3.Color().setHSL(hsl.h, saturation, lightness);
|
|
8620
|
+
}
|
|
8621
|
+
function createCompanionTone(source, index) {
|
|
8622
|
+
const hsl = { h: 0, s: 0, l: 0 };
|
|
8623
|
+
source.getHSL(hsl);
|
|
8624
|
+
const hueOffset = index === 1 ? 0.075 : -0.065;
|
|
8625
|
+
return new THREE3.Color().setHSL(
|
|
8626
|
+
THREE3.MathUtils.euclideanModulo(hsl.h + hueOffset, 1),
|
|
8627
|
+
THREE3.MathUtils.clamp(hsl.s * (index === 1 ? 0.92 : 0.82), 0.38, 0.76),
|
|
8628
|
+
THREE3.MathUtils.clamp(hsl.l + (index === 1 ? 0.04 : -0.03), 0.38, 0.68)
|
|
8629
|
+
);
|
|
8630
|
+
}
|
|
8631
|
+
function colorDistance(a, b) {
|
|
8632
|
+
const dr = a.r - b.r;
|
|
8633
|
+
const dg = a.g - b.g;
|
|
8634
|
+
const db = a.b - b.b;
|
|
8635
|
+
return Math.sqrt(dr * dr + dg * dg + db * db);
|
|
8636
|
+
}
|
|
8406
8637
|
var draco = null;
|
|
8407
8638
|
var dracoPath = "";
|
|
8408
8639
|
var gltfLoader = null;
|
|
@@ -8477,6 +8708,154 @@ function parseCharacterModel(data, resourcePath = "", options = {}) {
|
|
|
8477
8708
|
const loader = getGltfLoader(options.dracoDecoderPath);
|
|
8478
8709
|
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));
|
|
8479
8710
|
}
|
|
8711
|
+
|
|
8712
|
+
// three/modelMorphLayout.ts
|
|
8713
|
+
var THREE_RENDERED_MORPH_SEMANTICS = ["position", "normal", "color"];
|
|
8714
|
+
function resolveMorphTargetCount(mesh) {
|
|
8715
|
+
const morphAttributes = mesh.geometry.morphAttributes;
|
|
8716
|
+
const attributeCount = THREE_RENDERED_MORPH_SEMANTICS.reduce(
|
|
8717
|
+
(count, semantic) => Math.max(count, morphAttributes[semantic]?.length ?? 0),
|
|
8718
|
+
0
|
|
8719
|
+
);
|
|
8720
|
+
const influenceCount = mesh.morphTargetInfluences?.length ?? 0;
|
|
8721
|
+
const dictionaryCount = Object.values(mesh.morphTargetDictionary ?? {}).reduce((count, index) => {
|
|
8722
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
8723
|
+
throw new Error(
|
|
8724
|
+
`Mesh "${mesh.name || mesh.uuid}" has an invalid morph target dictionary index: ${index}.`
|
|
8725
|
+
);
|
|
8726
|
+
}
|
|
8727
|
+
return Math.max(count, index + 1);
|
|
8728
|
+
}, 0);
|
|
8729
|
+
return Math.max(attributeCount, influenceCount, dictionaryCount);
|
|
8730
|
+
}
|
|
8731
|
+
function createNeutralMorphAttribute(mesh, semantic, existingAttributes, targetIndex) {
|
|
8732
|
+
const geometry = mesh.geometry;
|
|
8733
|
+
const baseAttribute = geometry.getAttribute(semantic);
|
|
8734
|
+
const template = baseAttribute ?? existingAttributes.find(Boolean);
|
|
8735
|
+
if (!template) {
|
|
8736
|
+
throw new Error(
|
|
8737
|
+
`Mesh "${mesh.name || mesh.uuid}" cannot repair morph ${semantic}[${targetIndex}] because it has no base or existing attribute to copy.`
|
|
8738
|
+
);
|
|
8739
|
+
}
|
|
8740
|
+
const neutral = template.clone();
|
|
8741
|
+
if (geometry.morphTargetsRelative || !baseAttribute) {
|
|
8742
|
+
for (let vertexIndex = 0; vertexIndex < neutral.count; vertexIndex += 1) {
|
|
8743
|
+
for (let component = 0; component < neutral.itemSize; component += 1) {
|
|
8744
|
+
neutral.setComponent(vertexIndex, component, 0);
|
|
8745
|
+
}
|
|
8746
|
+
}
|
|
8747
|
+
}
|
|
8748
|
+
const targetName = Object.entries(mesh.morphTargetDictionary ?? {}).find(([, index]) => index === targetIndex)?.[0];
|
|
8749
|
+
neutral.name = targetName ?? "";
|
|
8750
|
+
return neutral;
|
|
8751
|
+
}
|
|
8752
|
+
function normalizeRenderableCharacterMorphAttributes(model) {
|
|
8753
|
+
const repairs = [];
|
|
8754
|
+
model.traverse((object) => {
|
|
8755
|
+
const mesh = object;
|
|
8756
|
+
if (!mesh.isMesh || !mesh.geometry) return;
|
|
8757
|
+
const morphAttributes = mesh.geometry.morphAttributes;
|
|
8758
|
+
const removedEmptyChannels = [];
|
|
8759
|
+
for (const semantic of THREE_RENDERED_MORPH_SEMANTICS) {
|
|
8760
|
+
if (semantic !== "position" && morphAttributes[semantic]?.length === 0) {
|
|
8761
|
+
delete morphAttributes[semantic];
|
|
8762
|
+
removedEmptyChannels.push(semantic);
|
|
8763
|
+
}
|
|
8764
|
+
}
|
|
8765
|
+
const targetCount = resolveMorphTargetCount(mesh);
|
|
8766
|
+
if (targetCount === 0) {
|
|
8767
|
+
if (morphAttributes.position?.length === 0) {
|
|
8768
|
+
delete morphAttributes.position;
|
|
8769
|
+
removedEmptyChannels.push("position");
|
|
8770
|
+
}
|
|
8771
|
+
if (removedEmptyChannels.length > 0) {
|
|
8772
|
+
repairs.push({
|
|
8773
|
+
meshName: mesh.name || mesh.uuid,
|
|
8774
|
+
paddedChannels: {},
|
|
8775
|
+
removedEmptyChannels,
|
|
8776
|
+
paddedInfluences: 0
|
|
8777
|
+
});
|
|
8778
|
+
}
|
|
8779
|
+
return;
|
|
8780
|
+
}
|
|
8781
|
+
const positionCount = mesh.geometry.getAttribute("position")?.count;
|
|
8782
|
+
if (!positionCount) {
|
|
8783
|
+
throw new Error(
|
|
8784
|
+
`Mesh "${mesh.name || mesh.uuid}" has morph targets but no base position attribute.`
|
|
8785
|
+
);
|
|
8786
|
+
}
|
|
8787
|
+
const paddedChannels = {};
|
|
8788
|
+
for (const semantic of THREE_RENDERED_MORPH_SEMANTICS) {
|
|
8789
|
+
let attributes = morphAttributes[semantic];
|
|
8790
|
+
if (!attributes) {
|
|
8791
|
+
if (semantic !== "position") continue;
|
|
8792
|
+
attributes = [];
|
|
8793
|
+
morphAttributes.position = attributes;
|
|
8794
|
+
}
|
|
8795
|
+
const baseAttribute = mesh.geometry.getAttribute(semantic);
|
|
8796
|
+
const referenceAttribute = baseAttribute ?? attributes.find(Boolean);
|
|
8797
|
+
const expectedItemSize = referenceAttribute?.itemSize;
|
|
8798
|
+
for (let targetIndex = 0; targetIndex < targetCount; targetIndex += 1) {
|
|
8799
|
+
const attribute = attributes[targetIndex];
|
|
8800
|
+
if (!attribute) {
|
|
8801
|
+
attributes[targetIndex] = createNeutralMorphAttribute(
|
|
8802
|
+
mesh,
|
|
8803
|
+
semantic,
|
|
8804
|
+
attributes,
|
|
8805
|
+
targetIndex
|
|
8806
|
+
);
|
|
8807
|
+
paddedChannels[semantic] = (paddedChannels[semantic] ?? 0) + 1;
|
|
8808
|
+
continue;
|
|
8809
|
+
}
|
|
8810
|
+
if (attribute.count !== positionCount || attribute.itemSize !== expectedItemSize) {
|
|
8811
|
+
throw new Error(
|
|
8812
|
+
`Mesh "${mesh.name || mesh.uuid}" has an invalid morph ${semantic}[${targetIndex}] (count=${attribute.count}, itemSize=${attribute.itemSize}; expected count=${positionCount}, itemSize=${expectedItemSize}).`
|
|
8813
|
+
);
|
|
8814
|
+
}
|
|
8815
|
+
}
|
|
8816
|
+
}
|
|
8817
|
+
const influences = mesh.morphTargetInfluences ?? [];
|
|
8818
|
+
const paddedInfluences = targetCount - influences.length;
|
|
8819
|
+
while (influences.length < targetCount) {
|
|
8820
|
+
influences.push(0);
|
|
8821
|
+
}
|
|
8822
|
+
mesh.morphTargetInfluences = influences;
|
|
8823
|
+
if (removedEmptyChannels.length > 0 || Object.keys(paddedChannels).length > 0 || paddedInfluences > 0) {
|
|
8824
|
+
repairs.push({
|
|
8825
|
+
meshName: mesh.name || mesh.uuid,
|
|
8826
|
+
paddedChannels,
|
|
8827
|
+
removedEmptyChannels,
|
|
8828
|
+
paddedInfluences
|
|
8829
|
+
});
|
|
8830
|
+
}
|
|
8831
|
+
});
|
|
8832
|
+
return repairs;
|
|
8833
|
+
}
|
|
8834
|
+
function assertRenderableCharacterMorphAttributes(model) {
|
|
8835
|
+
model.traverse((object) => {
|
|
8836
|
+
const mesh = object;
|
|
8837
|
+
if (!mesh.isMesh) return;
|
|
8838
|
+
const morphAttributes = mesh.geometry?.morphAttributes;
|
|
8839
|
+
const primary = morphAttributes?.position ?? morphAttributes?.normal ?? morphAttributes?.color;
|
|
8840
|
+
const targetCount = primary?.length ?? 0;
|
|
8841
|
+
if (targetCount === 0) return;
|
|
8842
|
+
const positionCount = mesh.geometry.getAttribute("position")?.count;
|
|
8843
|
+
const invalidSemantics = THREE_RENDERED_MORPH_SEMANTICS.filter((semantic) => {
|
|
8844
|
+
const attributes = morphAttributes[semantic];
|
|
8845
|
+
if (semantic === "position" && !attributes) return true;
|
|
8846
|
+
if (!attributes) return false;
|
|
8847
|
+
const expectedItemSize = mesh.geometry.getAttribute(semantic)?.itemSize ?? attributes.find(Boolean)?.itemSize;
|
|
8848
|
+
return attributes.length !== targetCount || Array.from({ length: targetCount }, (_, index) => attributes[index]).some((attribute) => !attribute || attribute.count !== positionCount || attribute.itemSize !== expectedItemSize);
|
|
8849
|
+
});
|
|
8850
|
+
const influenceCount = mesh.morphTargetInfluences?.length ?? 0;
|
|
8851
|
+
if (invalidSemantics.length > 0 || influenceCount !== targetCount) {
|
|
8852
|
+
const channelCounts = THREE_RENDERED_MORPH_SEMANTICS.map((semantic) => `${semantic}=${morphAttributes[semantic]?.length ?? 0}`).join(", ");
|
|
8853
|
+
throw new Error(
|
|
8854
|
+
`Mesh "${mesh.name || mesh.uuid}" has an invalid Three.js morph layout (${channelCounts}, influences=${influenceCount}, expected=${targetCount}).`
|
|
8855
|
+
);
|
|
8856
|
+
}
|
|
8857
|
+
});
|
|
8858
|
+
}
|
|
8480
8859
|
var DEFAULT_CHARACTER_LIGHTING_PRESETS = {
|
|
8481
8860
|
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 } },
|
|
8482
8861
|
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 } },
|
|
@@ -8710,6 +9089,7 @@ function constructDefaultCharacterScene(container, options, renderer, disposeRen
|
|
|
8710
9089
|
lifetime.add(disposeRenderer);
|
|
8711
9090
|
try {
|
|
8712
9091
|
const sceneType = CHARACTER_SCENE_TYPES[options.type] ?? CHARACTER_SCENE_TYPES.studio;
|
|
9092
|
+
let currentSceneType = sceneType.id;
|
|
8713
9093
|
const width = Math.max(1, container.clientWidth || globalThis.innerWidth || 1);
|
|
8714
9094
|
const height = Math.max(1, container.clientHeight || globalThis.innerHeight || 1);
|
|
8715
9095
|
const ratio = () => Math.min(globalThis.devicePixelRatio || 1, options.pixelRatioCap ?? 1.5);
|
|
@@ -8725,12 +9105,32 @@ function constructDefaultCharacterScene(container, options, renderer, disposeRen
|
|
|
8725
9105
|
const preset = DEFAULT_CHARACTER_LIGHTING_PRESETS[options.lightingPreset ?? sceneType.lightingPreset]?.settings ?? DEFAULT_CHARACTER_LIGHTING_SETTINGS;
|
|
8726
9106
|
const lighting = createLighting(scene, { ...preset, ...options.lighting });
|
|
8727
9107
|
lifetime.add(() => lighting.dispose());
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
lifetime.add(() =>
|
|
8733
|
-
|
|
9108
|
+
let shadowPlane = null;
|
|
9109
|
+
const ensureShadowPlane = () => {
|
|
9110
|
+
if (shadowPlane) return shadowPlane;
|
|
9111
|
+
const plane = createShadowPlane(scene, { opacity: lighting.getSettings().shadowOpacity });
|
|
9112
|
+
lifetime.add(() => plane.material.dispose());
|
|
9113
|
+
lifetime.add(() => plane.geometry.dispose());
|
|
9114
|
+
lifetime.add(() => scene.remove(plane));
|
|
9115
|
+
shadowPlane = plane;
|
|
9116
|
+
return plane;
|
|
9117
|
+
};
|
|
9118
|
+
if (options.shadowPlane ?? sceneType.shadowPlane) ensureShadowPlane();
|
|
9119
|
+
const setSceneSettings = (value) => {
|
|
9120
|
+
if (lifetime.isDisposed()) return;
|
|
9121
|
+
const type = CHARACTER_SCENE_TYPES[value.type ?? DEFAULT_CHARACTER_SCENE_TYPE_ID] ?? CHARACTER_SCENE_TYPES[DEFAULT_CHARACTER_SCENE_TYPE_ID];
|
|
9122
|
+
currentSceneType = type.id;
|
|
9123
|
+
const background2 = value.background === void 0 ? type.background : value.background;
|
|
9124
|
+
scene.background = background2 == null ? null : new Color(background2);
|
|
9125
|
+
camera.fov = value.cameraFov ?? 45;
|
|
9126
|
+
camera.updateProjectionMatrix();
|
|
9127
|
+
const preset2 = DEFAULT_CHARACTER_LIGHTING_PRESETS[value.lightingPreset ?? type.lightingPreset] ?? DEFAULT_CHARACTER_LIGHTING_PRESETS[DEFAULT_CHARACTER_LIGHTING_PRESET_ID];
|
|
9128
|
+
lighting.setSettings({ ...preset2.settings, ...value.lighting });
|
|
9129
|
+
renderer.shadowMap.enabled = value.shadows ?? true;
|
|
9130
|
+
const showPlane = value.shadowPlane ?? type.shadowPlane;
|
|
9131
|
+
if (showPlane) ensureShadowPlane();
|
|
9132
|
+
if (shadowPlane) shadowPlane.visible = showPlane;
|
|
9133
|
+
};
|
|
8734
9134
|
const resize = () => {
|
|
8735
9135
|
if (lifetime.isDisposed()) return;
|
|
8736
9136
|
const w = Math.max(1, container.clientWidth || globalThis.innerWidth || 1);
|
|
@@ -8740,7 +9140,11 @@ function constructDefaultCharacterScene(container, options, renderer, disposeRen
|
|
|
8740
9140
|
renderer.setPixelRatio(ratio());
|
|
8741
9141
|
renderer.setSize(w, h, false);
|
|
8742
9142
|
};
|
|
8743
|
-
const handle = { container, scene, renderer, camera, lighting, shadowPlane
|
|
9143
|
+
const handle = { container, scene, renderer, camera, lighting, get shadowPlane() {
|
|
9144
|
+
return shadowPlane;
|
|
9145
|
+
}, get sceneType() {
|
|
9146
|
+
return currentSceneType;
|
|
9147
|
+
}, ownsScene: true, resize, setSceneSettings, dispose: lifetime.dispose };
|
|
8744
9148
|
const attach = () => {
|
|
8745
9149
|
lifetime.add(() => {
|
|
8746
9150
|
if (renderer.domElement.parentElement === container) container.removeChild(renderer.domElement);
|
|
@@ -8873,6 +9277,7 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
8873
9277
|
const lifetime = createDisposer();
|
|
8874
9278
|
try {
|
|
8875
9279
|
const sceneType = CHARACTER_SCENE_TYPES[options.type] ?? CHARACTER_SCENE_TYPES.studio;
|
|
9280
|
+
let currentSceneType = sceneType.id;
|
|
8876
9281
|
const scene = new Scene();
|
|
8877
9282
|
const background = options.background === void 0 ? sceneType.background : options.background;
|
|
8878
9283
|
scene.background = background == null ? null : new Color(background);
|
|
@@ -8880,12 +9285,18 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
8880
9285
|
const preset = DEFAULT_CHARACTER_LIGHTING_PRESETS[options.lightingPreset ?? sceneType.lightingPreset]?.settings ?? DEFAULT_CHARACTER_LIGHTING_SETTINGS;
|
|
8881
9286
|
const lighting = createMutableCharacterLighting(scene, { ...preset, ...options.lighting });
|
|
8882
9287
|
lifetime.add(lighting.controller.dispose);
|
|
8883
|
-
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
9288
|
+
let shadowsEnabled = options.shadows ?? true;
|
|
9289
|
+
let shadowPlane = null;
|
|
9290
|
+
const ensureShadowPlane = () => {
|
|
9291
|
+
if (shadowPlane) return shadowPlane;
|
|
9292
|
+
const plane = createShadowPlane(scene, { opacity: lighting.controller.getSettings().shadowOpacity });
|
|
9293
|
+
lifetime.add(() => plane.material.dispose());
|
|
9294
|
+
lifetime.add(() => plane.geometry.dispose());
|
|
9295
|
+
lifetime.add(() => scene.remove(plane));
|
|
9296
|
+
shadowPlane = plane;
|
|
9297
|
+
return plane;
|
|
9298
|
+
};
|
|
9299
|
+
if (options.shadowPlane ?? sceneType.shadowPlane) ensureShadowPlane();
|
|
8889
9300
|
let settings = { preference: rendererPreference(options.rendering?.preference ?? options.renderer ?? "auto") };
|
|
8890
9301
|
let active = null;
|
|
8891
9302
|
let candidateInUse = null;
|
|
@@ -8990,7 +9401,7 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
8990
9401
|
candidateInUse = candidate;
|
|
8991
9402
|
const renderer = candidate.renderer;
|
|
8992
9403
|
sizeRenderer(renderer, true);
|
|
8993
|
-
renderer.shadowMap.enabled =
|
|
9404
|
+
renderer.shadowMap.enabled = shadowsEnabled;
|
|
8994
9405
|
renderer.shadowMap.type = candidate.backend === "webgl" ? PCFShadowMap : PCFSoftShadowMap;
|
|
8995
9406
|
Object.assign(renderer.domElement.style, { display: "block", width: "100%", height: "100%" });
|
|
8996
9407
|
if (leaseCount) await leaseBarrier;
|
|
@@ -9197,14 +9608,34 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
9197
9608
|
else cleanup();
|
|
9198
9609
|
return disposal;
|
|
9199
9610
|
};
|
|
9611
|
+
const setSceneSettings = (value) => {
|
|
9612
|
+
if (disposed) return;
|
|
9613
|
+
shadowsEnabled = value.shadows ?? true;
|
|
9614
|
+
const type = CHARACTER_SCENE_TYPES[value.type ?? DEFAULT_CHARACTER_SCENE_TYPE_ID] ?? CHARACTER_SCENE_TYPES[DEFAULT_CHARACTER_SCENE_TYPE_ID];
|
|
9615
|
+
currentSceneType = type.id;
|
|
9616
|
+
const background2 = value.background === void 0 ? type.background : value.background;
|
|
9617
|
+
scene.background = background2 == null ? null : new Color(background2);
|
|
9618
|
+
camera.fov = value.cameraFov ?? 45;
|
|
9619
|
+
camera.updateProjectionMatrix();
|
|
9620
|
+
const preset2 = DEFAULT_CHARACTER_LIGHTING_PRESETS[value.lightingPreset ?? type.lightingPreset] ?? DEFAULT_CHARACTER_LIGHTING_PRESETS[DEFAULT_CHARACTER_LIGHTING_PRESET_ID];
|
|
9621
|
+
lighting.controller.setSettings({ ...preset2.settings, ...value.lighting });
|
|
9622
|
+
if (active) active.renderer.shadowMap.enabled = value.shadows ?? true;
|
|
9623
|
+
const showPlane = value.shadowPlane ?? type.shadowPlane;
|
|
9624
|
+
if (showPlane) ensureShadowPlane();
|
|
9625
|
+
if (shadowPlane) shadowPlane.visible = showPlane;
|
|
9626
|
+
};
|
|
9200
9627
|
const ready = setSettings(settings);
|
|
9201
9628
|
return {
|
|
9202
9629
|
container,
|
|
9203
9630
|
scene,
|
|
9204
9631
|
camera,
|
|
9205
9632
|
lighting: lighting.controller,
|
|
9206
|
-
shadowPlane
|
|
9207
|
-
|
|
9633
|
+
get shadowPlane() {
|
|
9634
|
+
return shadowPlane;
|
|
9635
|
+
},
|
|
9636
|
+
get sceneType() {
|
|
9637
|
+
return currentSceneType;
|
|
9638
|
+
},
|
|
9208
9639
|
ownsScene: true,
|
|
9209
9640
|
rendering,
|
|
9210
9641
|
ready,
|
|
@@ -9215,6 +9646,7 @@ function createDefaultCharacterSceneRuntime(container, options = {}) {
|
|
|
9215
9646
|
return active?.backend ?? null;
|
|
9216
9647
|
},
|
|
9217
9648
|
resize,
|
|
9649
|
+
setSceneSettings,
|
|
9218
9650
|
dispose
|
|
9219
9651
|
};
|
|
9220
9652
|
} catch (failure) {
|
|
@@ -9230,7 +9662,9 @@ async function createDefaultCharacterSceneAsync(container, options = {}) {
|
|
|
9230
9662
|
const handle = snapshot2.backend === "webgl" ? { ...runtime, renderer: snapshot2.renderer, backend: "webgl" } : { ...runtime, renderer: snapshot2.renderer, backend: "webgpu" };
|
|
9231
9663
|
Object.defineProperties(handle, {
|
|
9232
9664
|
renderer: { get: () => runtime.renderer },
|
|
9233
|
-
backend: { get: () => runtime.backend }
|
|
9665
|
+
backend: { get: () => runtime.backend },
|
|
9666
|
+
shadowPlane: { get: () => runtime.shadowPlane },
|
|
9667
|
+
sceneType: { get: () => runtime.sceneType }
|
|
9234
9668
|
});
|
|
9235
9669
|
return handle;
|
|
9236
9670
|
} catch (failure) {
|
|
@@ -9250,24 +9684,94 @@ async function createCharacterHost(options) {
|
|
|
9250
9684
|
let state;
|
|
9251
9685
|
let engine;
|
|
9252
9686
|
let controller;
|
|
9687
|
+
let host;
|
|
9253
9688
|
let disposed = false;
|
|
9254
9689
|
let disposal;
|
|
9255
9690
|
let switching = Promise.resolve();
|
|
9691
|
+
let activeChange;
|
|
9692
|
+
let request3 = 0;
|
|
9256
9693
|
let mountedCanvas;
|
|
9257
|
-
const
|
|
9258
|
-
|
|
9259
|
-
|
|
9694
|
+
const previousBackground = options.container.style?.background ?? "";
|
|
9695
|
+
let backgroundRevision = 0;
|
|
9696
|
+
const applyCharacterBackground = (value, character) => {
|
|
9697
|
+
const revision = ++backgroundRevision;
|
|
9698
|
+
const settings = sceneSettings(character);
|
|
9699
|
+
const usePalette = !options.external && settings.background === void 0 && (settings.type === void 0 || settings.type === "studio");
|
|
9700
|
+
if (options.container.style) options.container.style.background = previousBackground;
|
|
9701
|
+
if (!usePalette) return;
|
|
9702
|
+
const background = createSceneInkBackground((css) => {
|
|
9703
|
+
if (!disposed && revision === backgroundRevision && options.container.style) options.container.style.background = css;
|
|
9704
|
+
});
|
|
9705
|
+
const key = character.modelUrl;
|
|
9706
|
+
const materialPalette = resolveCharacterMaterialPalette(value.model, key);
|
|
9707
|
+
background.setPalette(materialPalette);
|
|
9708
|
+
void resolveCharacterTexturePalette(value.model, key).then((palette) => {
|
|
9709
|
+
background.setPalette(palette ?? materialPalette);
|
|
9710
|
+
background.dispose();
|
|
9711
|
+
});
|
|
9712
|
+
};
|
|
9713
|
+
const visibility = /* @__PURE__ */ new WeakMap();
|
|
9714
|
+
const lifetime = new AbortController();
|
|
9715
|
+
const initial = new AbortController();
|
|
9716
|
+
activeChange = initial;
|
|
9717
|
+
const aborted = () => new DOMException("Character host request was cancelled", "AbortError");
|
|
9718
|
+
const cancel = () => {
|
|
9719
|
+
lifetime.abort();
|
|
9720
|
+
activeChange?.abort();
|
|
9721
|
+
};
|
|
9722
|
+
const cancelCreation = () => initial.abort();
|
|
9723
|
+
options.signal?.addEventListener("abort", cancelCreation, { once: true });
|
|
9724
|
+
if (options.signal?.aborted) cancelCreation();
|
|
9725
|
+
const check = (signal) => {
|
|
9726
|
+
if (disposed || lifetime.signal.aborted || signal.aborted) throw aborted();
|
|
9260
9727
|
};
|
|
9261
9728
|
const releaseModel = async (value) => {
|
|
9262
9729
|
const release = () => disposeCharacterModel(state?.scene ?? null, value.model);
|
|
9263
9730
|
if (state?.rendering) await state.rendering.releaseSceneResources(release);
|
|
9264
9731
|
else release();
|
|
9265
9732
|
};
|
|
9733
|
+
const acquireModel = (character, loader, signal) => (options.loadModel ?? loadCharacterModel)(character.modelUrl, { ...loader, signal });
|
|
9734
|
+
const sceneSettings = (character) => {
|
|
9735
|
+
const { background, ...value } = { ...options.scene, ...character.scene };
|
|
9736
|
+
return { ...value, ...background === void 0 ? {} : {
|
|
9737
|
+
background: background && typeof background === "object" ? background.getHex() : background
|
|
9738
|
+
} };
|
|
9739
|
+
};
|
|
9740
|
+
const acquireRenderer = async (signal) => {
|
|
9741
|
+
const rendering = state?.rendering;
|
|
9742
|
+
if (!rendering) return void 0;
|
|
9743
|
+
try {
|
|
9744
|
+
await rendering.setSettings(rendering.getSettings());
|
|
9745
|
+
} catch (error) {
|
|
9746
|
+
if (!rendering.getSnapshot().renderer) throw error;
|
|
9747
|
+
}
|
|
9748
|
+
check(signal);
|
|
9749
|
+
return rendering.acquireRenderer();
|
|
9750
|
+
};
|
|
9266
9751
|
const makeEngine = (value, character) => {
|
|
9267
|
-
|
|
9752
|
+
let profile = character.profile ?? options.embody?.profile;
|
|
9753
|
+
let annotationConfig = character.annotationConfig;
|
|
9754
|
+
if (profile) {
|
|
9755
|
+
const bones = [];
|
|
9756
|
+
value.model.traverse((object) => {
|
|
9757
|
+
if (object.isBone && object.name) bones.push({ name: object.name });
|
|
9758
|
+
});
|
|
9759
|
+
const validation = validateMappings(value.meshes, { bones }, profile, { suggestCorrections: true, useResolvedNames: true, minConfidence: 0.8 });
|
|
9760
|
+
if (validation.suggestedConfig && validation.corrections?.some((correction) => correction.applied)) {
|
|
9761
|
+
profile = { ...profile, ...validation.suggestedConfig };
|
|
9762
|
+
if (annotationConfig) annotationConfig = { ...annotationConfig, ...validation.suggestedConfig };
|
|
9763
|
+
}
|
|
9764
|
+
}
|
|
9765
|
+
const resolvedCharacter = { ...character, profile, annotationConfig };
|
|
9766
|
+
const next = new Embody({ ...options.embody, presetType: character.presetType ?? options.embody?.presetType, profile });
|
|
9268
9767
|
try {
|
|
9269
|
-
next.onReady(
|
|
9270
|
-
|
|
9768
|
+
next.onReady({
|
|
9769
|
+
model: value.model,
|
|
9770
|
+
meshes: value.meshes,
|
|
9771
|
+
...options.registerEmbeddedAnimations === false ? {} : { animations: value.animations }
|
|
9772
|
+
});
|
|
9773
|
+
next.stop();
|
|
9774
|
+
return { next, character: resolvedCharacter };
|
|
9271
9775
|
} catch (error) {
|
|
9272
9776
|
try {
|
|
9273
9777
|
next.dispose();
|
|
@@ -9279,6 +9783,10 @@ async function createCharacterHost(options) {
|
|
|
9279
9783
|
const dispose = () => {
|
|
9280
9784
|
if (disposal) return disposal;
|
|
9281
9785
|
disposed = true;
|
|
9786
|
+
backgroundRevision += 1;
|
|
9787
|
+
if (options.container.style) options.container.style.background = previousBackground;
|
|
9788
|
+
cancel();
|
|
9789
|
+
options.signal?.removeEventListener("abort", cancelCreation);
|
|
9282
9790
|
disposal = (async () => {
|
|
9283
9791
|
let failure;
|
|
9284
9792
|
const attempt = async (release) => {
|
|
@@ -9294,24 +9802,57 @@ async function createCharacterHost(options) {
|
|
|
9294
9802
|
await attempt(() => engine?.dispose());
|
|
9295
9803
|
const ownedScene = !options.external && state && "dispose" in state ? state : void 0;
|
|
9296
9804
|
if (ownedScene) await attempt(() => ownedScene.dispose());
|
|
9297
|
-
|
|
9298
|
-
if (lastModel) await attempt(() => releaseModel(lastModel));
|
|
9805
|
+
if (loaded) await attempt(() => releaseModel(loaded));
|
|
9299
9806
|
mountedCanvas?.remove();
|
|
9300
9807
|
if (failure !== void 0) throw failure;
|
|
9301
9808
|
})();
|
|
9302
9809
|
return disposal;
|
|
9303
9810
|
};
|
|
9811
|
+
const prepare2 = async (value, next, character, signal) => {
|
|
9812
|
+
check(signal);
|
|
9813
|
+
state?.setSceneSettings?.(sceneSettings(character));
|
|
9814
|
+
visibility.set(value.model, value.model.visible);
|
|
9815
|
+
value.model.visible = false;
|
|
9816
|
+
value.model.userData.embodyCharacterModel = true;
|
|
9817
|
+
await options.prepareCharacter?.({ ...value, engine: next, character, signal });
|
|
9818
|
+
check(signal);
|
|
9819
|
+
normalizeRenderableCharacterMorphAttributes(value.model);
|
|
9820
|
+
assertRenderableCharacterMorphAttributes(value.model);
|
|
9821
|
+
if (options.prepareCharacter) next.bindModel();
|
|
9822
|
+
value.model.updateMatrixWorld(true);
|
|
9823
|
+
state.scene.add(value.model);
|
|
9824
|
+
if (controller) await controller.prepareModelForRender(value.model, { signal });
|
|
9825
|
+
check(signal);
|
|
9826
|
+
};
|
|
9827
|
+
const frameCharacter = async (value, character) => {
|
|
9828
|
+
if (!controller) return;
|
|
9829
|
+
controller.setModel(value.model);
|
|
9830
|
+
if (character.annotationConfig) controller.prepareRegionsForReveal(character.annotationConfig);
|
|
9831
|
+
else if (options.autoFrame !== false) await controller.focusFullBody(0);
|
|
9832
|
+
};
|
|
9833
|
+
const ready = async (value, next, character, signal) => {
|
|
9834
|
+
await frameCharacter(value, character);
|
|
9835
|
+
check(signal);
|
|
9836
|
+
await options.onCharacterReady?.({
|
|
9837
|
+
...value,
|
|
9838
|
+
engine: next,
|
|
9839
|
+
character,
|
|
9840
|
+
signal,
|
|
9841
|
+
scene: state.scene,
|
|
9842
|
+
camera: state.camera,
|
|
9843
|
+
cameraController: controller ?? null,
|
|
9844
|
+
rendering: state.rendering ?? null
|
|
9845
|
+
});
|
|
9846
|
+
check(signal);
|
|
9847
|
+
};
|
|
9304
9848
|
try {
|
|
9305
|
-
|
|
9306
|
-
loaded = await
|
|
9307
|
-
|
|
9308
|
-
state = options.external ?? await createDefaultCharacterSceneAsync(options.container, { ...options.scene, signal:
|
|
9309
|
-
|
|
9849
|
+
check(initial.signal);
|
|
9850
|
+
loaded = await acquireModel(options.character, options.loader, initial.signal);
|
|
9851
|
+
check(initial.signal);
|
|
9852
|
+
state = options.external ?? await createDefaultCharacterSceneAsync(options.container, { ...options.scene, ...options.character.scene, signal: initial.signal });
|
|
9853
|
+
check(initial.signal);
|
|
9310
9854
|
await initEmbodyCore();
|
|
9311
|
-
|
|
9312
|
-
applyCharacterModelTransform(loaded.model, options.character);
|
|
9313
|
-
state.scene.add(loaded.model);
|
|
9314
|
-
engine = makeEngine(loaded, options.character);
|
|
9855
|
+
check(initial.signal);
|
|
9315
9856
|
if (options.external?.mountCanvas && !state.renderer.domElement.parentElement) {
|
|
9316
9857
|
mountedCanvas = state.renderer.domElement;
|
|
9317
9858
|
options.container.appendChild(mountedCanvas);
|
|
@@ -9319,25 +9860,36 @@ async function createCharacterHost(options) {
|
|
|
9319
9860
|
if (options.renderLoop ?? !options.external) {
|
|
9320
9861
|
if (!(state.camera instanceof PerspectiveCamera)) throw new Error("Character host rendering requires a PerspectiveCamera");
|
|
9321
9862
|
controller = new DPthreeCameraController({
|
|
9863
|
+
...options.camera,
|
|
9322
9864
|
scene: state.scene,
|
|
9323
9865
|
camera: state.camera,
|
|
9324
9866
|
domElement: state.renderer.domElement,
|
|
9325
9867
|
...state.rendering ? { rendering: state.rendering } : { renderer: state.renderer },
|
|
9326
|
-
showDOMControls: false
|
|
9868
|
+
showDOMControls: options.camera?.showDOMControls ?? false
|
|
9327
9869
|
});
|
|
9328
|
-
controller.setModel(loaded.model);
|
|
9329
|
-
await controller.prepareModelForRender(loaded.model, { signal: options.signal });
|
|
9330
|
-
if (options.autoFrame !== false) await controller.focusFullBody(0);
|
|
9331
9870
|
}
|
|
9332
|
-
|
|
9333
|
-
|
|
9334
|
-
|
|
9871
|
+
applyCharacterModelTransform(loaded.model, options.character);
|
|
9872
|
+
loaded.model.updateMatrixWorld(true);
|
|
9873
|
+
const first = makeEngine(loaded, options.character);
|
|
9874
|
+
engine = first.next;
|
|
9875
|
+
let currentCharacter = first.character;
|
|
9876
|
+
const initialLease = await acquireRenderer(initial.signal);
|
|
9877
|
+
try {
|
|
9878
|
+
await prepare2(loaded, engine, first.character, initial.signal);
|
|
9879
|
+
await ready(loaded, engine, first.character, initial.signal);
|
|
9880
|
+
loaded.model.visible = visibility.get(loaded.model) ?? true;
|
|
9881
|
+
applyCharacterBackground(loaded, first.character);
|
|
9882
|
+
} finally {
|
|
9883
|
+
initialLease?.release();
|
|
9884
|
+
}
|
|
9885
|
+
if (options.autoStart !== false) engine.start();
|
|
9335
9886
|
const sceneState = state;
|
|
9336
9887
|
const rendering = state.rendering ?? null;
|
|
9337
|
-
|
|
9888
|
+
host = {
|
|
9338
9889
|
engine,
|
|
9339
9890
|
scene: state.scene,
|
|
9340
9891
|
camera: state.camera,
|
|
9892
|
+
cameraController: controller ?? null,
|
|
9341
9893
|
get renderer() {
|
|
9342
9894
|
return rendering ? rendering.getSnapshot().renderer : sceneState.renderer;
|
|
9343
9895
|
},
|
|
@@ -9348,68 +9900,92 @@ async function createCharacterHost(options) {
|
|
|
9348
9900
|
model: loaded.model,
|
|
9349
9901
|
meshes: loaded.meshes,
|
|
9350
9902
|
animations: loaded.animations,
|
|
9903
|
+
gltf: loaded.gltf,
|
|
9351
9904
|
lighting: "lighting" in state ? state.lighting : null,
|
|
9352
9905
|
ownsScene: !options.external,
|
|
9353
9906
|
container: options.container,
|
|
9354
9907
|
resize() {
|
|
9355
9908
|
if ("resize" in sceneState) sceneState.resize();
|
|
9356
9909
|
},
|
|
9357
|
-
setCharacter(character, loader = options.loader) {
|
|
9358
|
-
if (disposed) return Promise.reject(
|
|
9910
|
+
setCharacter(character, loader = options.loader, signal) {
|
|
9911
|
+
if (disposed || lifetime.signal.aborted) return Promise.reject(aborted());
|
|
9912
|
+
if (!character.modelUrl) return Promise.reject(new Error("setCharacter requires character.modelUrl"));
|
|
9913
|
+
activeChange?.abort();
|
|
9914
|
+
const operation = new AbortController();
|
|
9915
|
+
const abort = () => operation.abort();
|
|
9916
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
9917
|
+
if (signal?.aborted) abort();
|
|
9918
|
+
activeChange = operation;
|
|
9919
|
+
const generation = ++request3;
|
|
9359
9920
|
const change = switching.catch(() => {
|
|
9360
9921
|
}).then(async () => {
|
|
9361
|
-
|
|
9362
|
-
|
|
9922
|
+
check(operation.signal);
|
|
9923
|
+
if (generation !== request3) throw aborted();
|
|
9924
|
+
const next = await acquireModel(character, loader, operation.signal);
|
|
9363
9925
|
let nextEngine;
|
|
9926
|
+
let lease = void 0;
|
|
9927
|
+
let cameraChanged = false;
|
|
9928
|
+
let previous;
|
|
9364
9929
|
try {
|
|
9365
|
-
|
|
9930
|
+
check(operation.signal);
|
|
9931
|
+
lease = await acquireRenderer(operation.signal);
|
|
9366
9932
|
applyCharacterModelTransform(next.model, character);
|
|
9367
|
-
|
|
9368
|
-
|
|
9369
|
-
|
|
9370
|
-
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
} catch {
|
|
9376
|
-
}
|
|
9377
|
-
try {
|
|
9378
|
-
await releaseModel(next);
|
|
9379
|
-
} catch {
|
|
9380
|
-
}
|
|
9381
|
-
throw error;
|
|
9382
|
-
}
|
|
9383
|
-
const previous = loaded;
|
|
9384
|
-
try {
|
|
9933
|
+
next.model.updateMatrixWorld(true);
|
|
9934
|
+
const prepared = makeEngine(next, character);
|
|
9935
|
+
nextEngine = prepared.next;
|
|
9936
|
+
await prepare2(next, nextEngine, prepared.character, operation.signal);
|
|
9937
|
+
cameraChanged = true;
|
|
9938
|
+
await ready(next, nextEngine, prepared.character, operation.signal);
|
|
9939
|
+
check(operation.signal);
|
|
9940
|
+
previous = loaded;
|
|
9385
9941
|
engine.dispose();
|
|
9942
|
+
engine = nextEngine;
|
|
9943
|
+
loaded = next;
|
|
9944
|
+
currentCharacter = prepared.character;
|
|
9945
|
+
applyCharacterBackground(next, currentCharacter);
|
|
9946
|
+
host.engine = engine;
|
|
9947
|
+
host.model = next.model;
|
|
9948
|
+
host.meshes = next.meshes;
|
|
9949
|
+
host.animations = next.animations;
|
|
9950
|
+
host.gltf = next.gltf;
|
|
9951
|
+
previous.model.visible = false;
|
|
9952
|
+
next.model.visible = visibility.get(next.model) ?? true;
|
|
9953
|
+
if (options.autoStart !== false) engine.start();
|
|
9386
9954
|
} catch (error) {
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9955
|
+
if (loaded !== next) {
|
|
9956
|
+
try {
|
|
9957
|
+
state?.setSceneSettings?.(sceneSettings(currentCharacter));
|
|
9958
|
+
} catch {
|
|
9959
|
+
}
|
|
9960
|
+
if (cameraChanged && !disposed && controller && loaded) {
|
|
9961
|
+
try {
|
|
9962
|
+
await frameCharacter(loaded, currentCharacter);
|
|
9963
|
+
} catch {
|
|
9964
|
+
}
|
|
9965
|
+
}
|
|
9966
|
+
try {
|
|
9967
|
+
nextEngine?.dispose();
|
|
9968
|
+
} catch {
|
|
9969
|
+
}
|
|
9970
|
+
lease?.release();
|
|
9971
|
+
lease = void 0;
|
|
9972
|
+
try {
|
|
9973
|
+
await releaseModel(next);
|
|
9974
|
+
} catch {
|
|
9975
|
+
}
|
|
9394
9976
|
}
|
|
9395
9977
|
throw error;
|
|
9978
|
+
} finally {
|
|
9979
|
+
lease?.release();
|
|
9396
9980
|
}
|
|
9397
|
-
|
|
9398
|
-
loaded = next;
|
|
9399
|
-
host.engine = engine;
|
|
9400
|
-
host.model = next.model;
|
|
9401
|
-
host.meshes = next.meshes;
|
|
9402
|
-
host.animations = next.animations;
|
|
9403
|
-
controller?.setModel(next.model);
|
|
9404
|
-
if (options.autoStart !== false) engine.start();
|
|
9405
|
-
await releaseModel(previous);
|
|
9406
|
-
if (controller && options.autoFrame !== false) await controller.focusFullBody(0);
|
|
9981
|
+
if (previous) await releaseModel(previous);
|
|
9407
9982
|
});
|
|
9408
|
-
switching = change;
|
|
9409
|
-
return
|
|
9983
|
+
switching = change.finally(() => signal?.removeEventListener("abort", abort));
|
|
9984
|
+
return switching;
|
|
9410
9985
|
},
|
|
9411
9986
|
dispose
|
|
9412
9987
|
};
|
|
9988
|
+
options.signal?.removeEventListener("abort", cancelCreation);
|
|
9413
9989
|
return host;
|
|
9414
9990
|
} catch (error) {
|
|
9415
9991
|
try {
|
|
@@ -9419,6 +9995,1072 @@ async function createCharacterHost(options) {
|
|
|
9419
9995
|
throw error;
|
|
9420
9996
|
}
|
|
9421
9997
|
}
|
|
9998
|
+
var COLORS = {
|
|
9999
|
+
mapped: 4889258,
|
|
10000
|
+
unmapped: 5925498,
|
|
10001
|
+
highlighted: 13934656,
|
|
10002
|
+
selected: 6534125
|
|
10003
|
+
};
|
|
10004
|
+
var OPACITY = {
|
|
10005
|
+
mapped: 0.58,
|
|
10006
|
+
unmapped: 0.38,
|
|
10007
|
+
highlighted: 0.7,
|
|
10008
|
+
selected: 0.8,
|
|
10009
|
+
context: 0.46,
|
|
10010
|
+
muted: 0.16
|
|
10011
|
+
};
|
|
10012
|
+
var AXIS_GIZMO_COLORS = {
|
|
10013
|
+
pitch: 16743004,
|
|
10014
|
+
yaw: 6410389,
|
|
10015
|
+
roll: 6924543
|
|
10016
|
+
};
|
|
10017
|
+
var AXIS_GIZMO_IDLE_OPACITY = 0.72;
|
|
10018
|
+
var AXIS_GIZMO_MUTED_OPACITY = 0.26;
|
|
10019
|
+
var AXIS_GIZMO_ACTIVE_OPACITY = 0.96;
|
|
10020
|
+
var AXIS_GIZMO_RADIUS_FACTOR = 0.48;
|
|
10021
|
+
var AXIS_GIZMO_MIN_RADIUS = 0.025;
|
|
10022
|
+
var AXIS_GIZMO_MAX_RADIUS = 0.14;
|
|
10023
|
+
var AXIS_GIZMO_SCREEN_RADIUS_PX = 54;
|
|
10024
|
+
var AXIS_GIZMO_ACTIVE_SCALE = 1.05;
|
|
10025
|
+
var AXIS_GIZMO_HIT_TUBE = 0.15;
|
|
10026
|
+
var VISIBILITY_FADE_MS = 180;
|
|
10027
|
+
var VISIBILITY_EPSILON = 0.01;
|
|
10028
|
+
var LEAF_BONE_LENGTH = 0.02;
|
|
10029
|
+
var WIDTH_RATIO = 0.1;
|
|
10030
|
+
var MID_POSITION = 0.2;
|
|
10031
|
+
var ROOT_HELPER_NAME_PATTERN = /armature|bone.?root|rootjoint|(?:^|[_\W])root(?:$|[_\W])/i;
|
|
10032
|
+
var HIP_NAME_PATTERN = /(?:^|[_\W])hip(?:$|[_\W]|\d)/i;
|
|
10033
|
+
var PELVIS_NAME_PATTERN = /(?:^|[_\W])pelvis(?:$|[_\W]|\d)/i;
|
|
10034
|
+
var POSITION_EPSILON = 1e-4;
|
|
10035
|
+
var DEFAULT_AXIS_CHANNELS = {
|
|
10036
|
+
pitch: "rx",
|
|
10037
|
+
yaw: "ry",
|
|
10038
|
+
roll: "rz"
|
|
10039
|
+
};
|
|
10040
|
+
function createOctahedralBoneGeometry() {
|
|
10041
|
+
const w = WIDTH_RATIO;
|
|
10042
|
+
const positions = new Float32Array([
|
|
10043
|
+
// Face 0: root → mid1 → mid2
|
|
10044
|
+
0,
|
|
10045
|
+
0,
|
|
10046
|
+
0,
|
|
10047
|
+
w,
|
|
10048
|
+
w,
|
|
10049
|
+
MID_POSITION,
|
|
10050
|
+
-w,
|
|
10051
|
+
w,
|
|
10052
|
+
MID_POSITION,
|
|
10053
|
+
// Face 1: root → mid2 → mid3
|
|
10054
|
+
0,
|
|
10055
|
+
0,
|
|
10056
|
+
0,
|
|
10057
|
+
-w,
|
|
10058
|
+
w,
|
|
10059
|
+
MID_POSITION,
|
|
10060
|
+
-w,
|
|
10061
|
+
-w,
|
|
10062
|
+
MID_POSITION,
|
|
10063
|
+
// Face 2: root → mid3 → mid4
|
|
10064
|
+
0,
|
|
10065
|
+
0,
|
|
10066
|
+
0,
|
|
10067
|
+
-w,
|
|
10068
|
+
-w,
|
|
10069
|
+
MID_POSITION,
|
|
10070
|
+
w,
|
|
10071
|
+
-w,
|
|
10072
|
+
MID_POSITION,
|
|
10073
|
+
// Face 3: root → mid4 → mid1
|
|
10074
|
+
0,
|
|
10075
|
+
0,
|
|
10076
|
+
0,
|
|
10077
|
+
w,
|
|
10078
|
+
-w,
|
|
10079
|
+
MID_POSITION,
|
|
10080
|
+
w,
|
|
10081
|
+
w,
|
|
10082
|
+
MID_POSITION,
|
|
10083
|
+
// Face 4: tip → mid2 → mid1
|
|
10084
|
+
0,
|
|
10085
|
+
0,
|
|
10086
|
+
1,
|
|
10087
|
+
-w,
|
|
10088
|
+
w,
|
|
10089
|
+
MID_POSITION,
|
|
10090
|
+
w,
|
|
10091
|
+
w,
|
|
10092
|
+
MID_POSITION,
|
|
10093
|
+
// Face 5: tip → mid3 → mid2
|
|
10094
|
+
0,
|
|
10095
|
+
0,
|
|
10096
|
+
1,
|
|
10097
|
+
-w,
|
|
10098
|
+
-w,
|
|
10099
|
+
MID_POSITION,
|
|
10100
|
+
-w,
|
|
10101
|
+
w,
|
|
10102
|
+
MID_POSITION,
|
|
10103
|
+
// Face 6: tip → mid4 → mid3
|
|
10104
|
+
0,
|
|
10105
|
+
0,
|
|
10106
|
+
1,
|
|
10107
|
+
w,
|
|
10108
|
+
-w,
|
|
10109
|
+
MID_POSITION,
|
|
10110
|
+
-w,
|
|
10111
|
+
-w,
|
|
10112
|
+
MID_POSITION,
|
|
10113
|
+
// Face 7: tip → mid1 → mid4
|
|
10114
|
+
0,
|
|
10115
|
+
0,
|
|
10116
|
+
1,
|
|
10117
|
+
w,
|
|
10118
|
+
w,
|
|
10119
|
+
MID_POSITION,
|
|
10120
|
+
w,
|
|
10121
|
+
-w,
|
|
10122
|
+
MID_POSITION
|
|
10123
|
+
]);
|
|
10124
|
+
const geometry = new THREE3.BufferGeometry();
|
|
10125
|
+
geometry.setAttribute("position", new THREE3.BufferAttribute(positions, 3));
|
|
10126
|
+
geometry.computeVertexNormals();
|
|
10127
|
+
return geometry;
|
|
10128
|
+
}
|
|
10129
|
+
function collectBones(root) {
|
|
10130
|
+
const bones = /* @__PURE__ */ new Set();
|
|
10131
|
+
root.traverse((obj) => {
|
|
10132
|
+
if (obj.isBone) {
|
|
10133
|
+
bones.add(obj);
|
|
10134
|
+
}
|
|
10135
|
+
if (obj.isSkinnedMesh) {
|
|
10136
|
+
const skinned = obj;
|
|
10137
|
+
skinned.skeleton?.bones?.forEach((bone) => bones.add(bone));
|
|
10138
|
+
}
|
|
10139
|
+
});
|
|
10140
|
+
return Array.from(bones);
|
|
10141
|
+
}
|
|
10142
|
+
function isBone2(obj) {
|
|
10143
|
+
return !!obj && obj.isBone;
|
|
10144
|
+
}
|
|
10145
|
+
function countBoneSubtree(bone) {
|
|
10146
|
+
let count = 1;
|
|
10147
|
+
for (const child of bone.children) {
|
|
10148
|
+
if (isBone2(child)) {
|
|
10149
|
+
count += countBoneSubtree(child);
|
|
10150
|
+
}
|
|
10151
|
+
}
|
|
10152
|
+
return count;
|
|
10153
|
+
}
|
|
10154
|
+
function resolveTopLevelHelperRoot(root) {
|
|
10155
|
+
const topLevelBones = collectBones(root).filter((bone) => !isBone2(bone.parent));
|
|
10156
|
+
if (topLevelBones.length !== 1) return null;
|
|
10157
|
+
let helperRoot = topLevelBones[0];
|
|
10158
|
+
while (ROOT_HELPER_NAME_PATTERN.test(helperRoot.name)) {
|
|
10159
|
+
const boneChildren = helperRoot.children.filter(isBone2);
|
|
10160
|
+
if (boneChildren.length !== 1) break;
|
|
10161
|
+
const onlyChild = boneChildren[0];
|
|
10162
|
+
if (!ROOT_HELPER_NAME_PATTERN.test(onlyChild.name)) break;
|
|
10163
|
+
helperRoot = onlyChild;
|
|
10164
|
+
}
|
|
10165
|
+
return ROOT_HELPER_NAME_PATTERN.test(helperRoot.name) ? helperRoot : null;
|
|
10166
|
+
}
|
|
10167
|
+
function resolveBodyDisplayRoot(root) {
|
|
10168
|
+
const helperRoot = resolveTopLevelHelperRoot(root);
|
|
10169
|
+
if (!helperRoot) return null;
|
|
10170
|
+
const boneChildren = helperRoot.children.filter(isBone2);
|
|
10171
|
+
if (boneChildren.length === 0) return null;
|
|
10172
|
+
if (boneChildren.length === 1) return boneChildren[0];
|
|
10173
|
+
const subtreeSizes = boneChildren.map((child) => ({ child, size: countBoneSubtree(child) })).sort((left, right) => right.size - left.size);
|
|
10174
|
+
const [largest, secondLargest] = subtreeSizes;
|
|
10175
|
+
if (!largest || largest.size < 3) return null;
|
|
10176
|
+
if (!secondLargest || secondLargest.size <= 2 || largest.size >= secondLargest.size * 3) {
|
|
10177
|
+
return largest.child;
|
|
10178
|
+
}
|
|
10179
|
+
return null;
|
|
10180
|
+
}
|
|
10181
|
+
function collectBoneChildren(bone) {
|
|
10182
|
+
return bone.children.filter(isBone2);
|
|
10183
|
+
}
|
|
10184
|
+
function hasZeroLengthConnection(parentBone, childBone) {
|
|
10185
|
+
parentBone.getWorldPosition(_parentPos);
|
|
10186
|
+
childBone.getWorldPosition(_childPos);
|
|
10187
|
+
return _parentPos.distanceToSquared(_childPos) <= POSITION_EPSILON * POSITION_EPSILON;
|
|
10188
|
+
}
|
|
10189
|
+
function hasRootHelperAncestor(bone) {
|
|
10190
|
+
let current = bone.parent;
|
|
10191
|
+
while (isBone2(current)) {
|
|
10192
|
+
if (ROOT_HELPER_NAME_PATTERN.test(current.name)) return true;
|
|
10193
|
+
current = current.parent;
|
|
10194
|
+
}
|
|
10195
|
+
return false;
|
|
10196
|
+
}
|
|
10197
|
+
function shouldSkipRootHelperConnection(parentBone, boneChildren) {
|
|
10198
|
+
if (boneChildren.length !== 1) return false;
|
|
10199
|
+
const childBone = boneChildren[0];
|
|
10200
|
+
if (ROOT_HELPER_NAME_PATTERN.test(parentBone.name)) {
|
|
10201
|
+
if (!isBone2(parentBone.parent)) return true;
|
|
10202
|
+
return collectBoneChildren(childBone).some((grandChild) => hasZeroLengthConnection(childBone, grandChild));
|
|
10203
|
+
}
|
|
10204
|
+
return hasRootHelperAncestor(parentBone) && HIP_NAME_PATTERN.test(parentBone.name) && PELVIS_NAME_PATTERN.test(childBone.name) && hasZeroLengthConnection(parentBone, childBone);
|
|
10205
|
+
}
|
|
10206
|
+
function setRayFromPointer(clientX, clientY, camera, domElement) {
|
|
10207
|
+
const rect = domElement.getBoundingClientRect();
|
|
10208
|
+
if (rect.width <= 0 || rect.height <= 0) return false;
|
|
10209
|
+
_mouse.x = (clientX - rect.left) / rect.width * 2 - 1;
|
|
10210
|
+
_mouse.y = -((clientY - rect.top) / rect.height) * 2 + 1;
|
|
10211
|
+
_raycaster.setFromCamera(_mouse, camera);
|
|
10212
|
+
return true;
|
|
10213
|
+
}
|
|
10214
|
+
var _parentPos = new THREE3.Vector3();
|
|
10215
|
+
var _childPos = new THREE3.Vector3();
|
|
10216
|
+
var _direction = new THREE3.Vector3();
|
|
10217
|
+
var _zAxis = new THREE3.Vector3(0, 0, 1);
|
|
10218
|
+
var _quaternion = new THREE3.Quaternion();
|
|
10219
|
+
var _bonePos = new THREE3.Vector3();
|
|
10220
|
+
var _boneQuat = new THREE3.Quaternion();
|
|
10221
|
+
var _boneEuler = new THREE3.Euler();
|
|
10222
|
+
var _mouse = new THREE3.Vector2();
|
|
10223
|
+
var _raycaster = new THREE3.Raycaster();
|
|
10224
|
+
var _cameraSpacePosition = new THREE3.Vector3();
|
|
10225
|
+
var _planeNormal = new THREE3.Vector3();
|
|
10226
|
+
var _dragPlane = new THREE3.Plane();
|
|
10227
|
+
var _planeHit = new THREE3.Vector3();
|
|
10228
|
+
function resolveSkeletonOverlayStartBoneName(root) {
|
|
10229
|
+
return resolveBodyDisplayRoot(root)?.name ?? null;
|
|
10230
|
+
}
|
|
10231
|
+
var SkeletonOverlay = class {
|
|
10232
|
+
constructor(scene, mappedBoneNames, semanticKeyMap, options = {}) {
|
|
10233
|
+
__publicField(this, "scene");
|
|
10234
|
+
__publicField(this, "group");
|
|
10235
|
+
__publicField(this, "visualizations", []);
|
|
10236
|
+
__publicField(this, "boneTree", []);
|
|
10237
|
+
__publicField(this, "mappedBoneNames");
|
|
10238
|
+
__publicField(this, "semanticKeyMap");
|
|
10239
|
+
// boneName → semanticKey
|
|
10240
|
+
__publicField(this, "boneLookup", /* @__PURE__ */ new Map());
|
|
10241
|
+
// Shared geometry and materials
|
|
10242
|
+
__publicField(this, "sharedGeometry");
|
|
10243
|
+
__publicField(this, "mappedMaterial");
|
|
10244
|
+
__publicField(this, "unmappedMaterial");
|
|
10245
|
+
__publicField(this, "highlightMaterial");
|
|
10246
|
+
__publicField(this, "selectedMaterial");
|
|
10247
|
+
__publicField(this, "contextMaterial");
|
|
10248
|
+
__publicField(this, "mutedMaterial");
|
|
10249
|
+
__publicField(this, "axisRingGeometry");
|
|
10250
|
+
__publicField(this, "axisRingHitGeometry");
|
|
10251
|
+
__publicField(this, "axisCenterGeometry");
|
|
10252
|
+
__publicField(this, "pitchRingMaterial");
|
|
10253
|
+
__publicField(this, "yawRingMaterial");
|
|
10254
|
+
__publicField(this, "rollRingMaterial");
|
|
10255
|
+
__publicField(this, "axisHitMaterial");
|
|
10256
|
+
__publicField(this, "axisCenterMaterial");
|
|
10257
|
+
__publicField(this, "axisGizmoGroup");
|
|
10258
|
+
__publicField(this, "axisGizmoMeshes");
|
|
10259
|
+
__publicField(this, "axisGizmoHitMeshes");
|
|
10260
|
+
// Sentinel mesh for per-frame updates (stored for disposal)
|
|
10261
|
+
__publicField(this, "sentinelMesh", null);
|
|
10262
|
+
// State
|
|
10263
|
+
__publicField(this, "highlightedBone", null);
|
|
10264
|
+
__publicField(this, "selectedBone", null);
|
|
10265
|
+
__publicField(this, "activeAxis", null);
|
|
10266
|
+
__publicField(this, "opacityScale");
|
|
10267
|
+
__publicField(this, "focusSelectedBone");
|
|
10268
|
+
__publicField(this, "visibilityAlpha", 0);
|
|
10269
|
+
__publicField(this, "visibilityTarget", 0);
|
|
10270
|
+
__publicField(this, "visibilityLastFrameAt", 0);
|
|
10271
|
+
this.scene = scene;
|
|
10272
|
+
this.mappedBoneNames = mappedBoneNames;
|
|
10273
|
+
this.semanticKeyMap = semanticKeyMap;
|
|
10274
|
+
this.opacityScale = THREE3.MathUtils.clamp(options.opacityScale ?? 1, 0, 1);
|
|
10275
|
+
this.focusSelectedBone = options.focusSelectedBone ?? false;
|
|
10276
|
+
this.group = new THREE3.Group();
|
|
10277
|
+
this.group.name = "SkeletonOverlay";
|
|
10278
|
+
this.group.renderOrder = 999;
|
|
10279
|
+
this.group.visible = false;
|
|
10280
|
+
this.sharedGeometry = createOctahedralBoneGeometry();
|
|
10281
|
+
const materialOpts = {
|
|
10282
|
+
transparent: true,
|
|
10283
|
+
depthTest: false,
|
|
10284
|
+
depthWrite: false,
|
|
10285
|
+
side: THREE3.DoubleSide
|
|
10286
|
+
};
|
|
10287
|
+
this.mappedMaterial = new THREE3.MeshBasicMaterial({ ...materialOpts, color: COLORS.mapped, opacity: OPACITY.mapped });
|
|
10288
|
+
this.unmappedMaterial = new THREE3.MeshBasicMaterial({ ...materialOpts, color: COLORS.unmapped, opacity: OPACITY.unmapped });
|
|
10289
|
+
this.highlightMaterial = new THREE3.MeshBasicMaterial({ ...materialOpts, color: COLORS.highlighted, opacity: OPACITY.highlighted });
|
|
10290
|
+
this.selectedMaterial = new THREE3.MeshBasicMaterial({ ...materialOpts, color: COLORS.selected, opacity: OPACITY.selected });
|
|
10291
|
+
this.contextMaterial = new THREE3.MeshBasicMaterial({ ...materialOpts, color: COLORS.selected, opacity: OPACITY.context });
|
|
10292
|
+
this.mutedMaterial = new THREE3.MeshBasicMaterial({ ...materialOpts, color: COLORS.unmapped, opacity: OPACITY.muted });
|
|
10293
|
+
this.mappedMaterial.forceSinglePass = true;
|
|
10294
|
+
this.unmappedMaterial.forceSinglePass = true;
|
|
10295
|
+
this.highlightMaterial.forceSinglePass = true;
|
|
10296
|
+
this.selectedMaterial.forceSinglePass = true;
|
|
10297
|
+
this.contextMaterial.forceSinglePass = true;
|
|
10298
|
+
this.mutedMaterial.forceSinglePass = true;
|
|
10299
|
+
this.axisRingGeometry = new THREE3.TorusGeometry(1, 0.032, 10, 120);
|
|
10300
|
+
this.axisRingHitGeometry = new THREE3.TorusGeometry(1, AXIS_GIZMO_HIT_TUBE, 8, 64);
|
|
10301
|
+
this.axisCenterGeometry = new THREE3.SphereGeometry(0.082, 12, 8);
|
|
10302
|
+
this.pitchRingMaterial = new THREE3.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.pitch, opacity: AXIS_GIZMO_IDLE_OPACITY });
|
|
10303
|
+
this.yawRingMaterial = new THREE3.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.yaw, opacity: AXIS_GIZMO_IDLE_OPACITY });
|
|
10304
|
+
this.rollRingMaterial = new THREE3.MeshBasicMaterial({ ...materialOpts, color: AXIS_GIZMO_COLORS.roll, opacity: AXIS_GIZMO_IDLE_OPACITY });
|
|
10305
|
+
this.axisHitMaterial = new THREE3.MeshBasicMaterial({ ...materialOpts, opacity: 0 });
|
|
10306
|
+
this.axisHitMaterial.colorWrite = false;
|
|
10307
|
+
this.axisCenterMaterial = new THREE3.MeshBasicMaterial({ ...materialOpts, color: 16119802, opacity: 0.92 });
|
|
10308
|
+
this.axisGizmoGroup = new THREE3.Group();
|
|
10309
|
+
this.axisGizmoGroup.name = "SelectedBoneAxisGizmo";
|
|
10310
|
+
this.axisGizmoGroup.visible = false;
|
|
10311
|
+
this.axisGizmoGroup.renderOrder = 1e3;
|
|
10312
|
+
const pitchRing = new THREE3.Mesh(this.axisRingGeometry, this.pitchRingMaterial);
|
|
10313
|
+
pitchRing.renderOrder = 1e3;
|
|
10314
|
+
pitchRing.userData.axis = "pitch";
|
|
10315
|
+
const pitchHit = new THREE3.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
|
|
10316
|
+
pitchHit.userData.axis = "pitch";
|
|
10317
|
+
const yawRing = new THREE3.Mesh(this.axisRingGeometry, this.yawRingMaterial);
|
|
10318
|
+
yawRing.renderOrder = 1e3;
|
|
10319
|
+
yawRing.userData.axis = "yaw";
|
|
10320
|
+
const yawHit = new THREE3.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
|
|
10321
|
+
yawHit.userData.axis = "yaw";
|
|
10322
|
+
const rollRing = new THREE3.Mesh(this.axisRingGeometry, this.rollRingMaterial);
|
|
10323
|
+
rollRing.renderOrder = 1e3;
|
|
10324
|
+
rollRing.userData.axis = "roll";
|
|
10325
|
+
const rollHit = new THREE3.Mesh(this.axisRingHitGeometry, this.axisHitMaterial);
|
|
10326
|
+
rollHit.rotation.copy(rollRing.rotation);
|
|
10327
|
+
rollHit.userData.axis = "roll";
|
|
10328
|
+
const center = new THREE3.Mesh(this.axisCenterGeometry, this.axisCenterMaterial);
|
|
10329
|
+
center.renderOrder = 1001;
|
|
10330
|
+
this.axisGizmoMeshes = {
|
|
10331
|
+
pitch: pitchRing,
|
|
10332
|
+
yaw: yawRing,
|
|
10333
|
+
roll: rollRing
|
|
10334
|
+
};
|
|
10335
|
+
this.axisGizmoHitMeshes = {
|
|
10336
|
+
pitch: pitchHit,
|
|
10337
|
+
yaw: yawHit,
|
|
10338
|
+
roll: rollHit
|
|
10339
|
+
};
|
|
10340
|
+
Object.values(this.axisGizmoMeshes).forEach((mesh) => {
|
|
10341
|
+
mesh.frustumCulled = false;
|
|
10342
|
+
this.axisGizmoGroup.add(mesh);
|
|
10343
|
+
});
|
|
10344
|
+
Object.values(this.axisGizmoHitMeshes).forEach((mesh) => {
|
|
10345
|
+
mesh.frustumCulled = false;
|
|
10346
|
+
this.axisGizmoGroup.add(mesh);
|
|
10347
|
+
});
|
|
10348
|
+
center.frustumCulled = false;
|
|
10349
|
+
this.axisGizmoGroup.add(center);
|
|
10350
|
+
this.syncAxisGizmoChannels();
|
|
10351
|
+
this.syncVisibilityOpacity();
|
|
10352
|
+
this.updateAxisGizmoAppearance();
|
|
10353
|
+
}
|
|
10354
|
+
/**
|
|
10355
|
+
* Build the skeleton visualization from a scene root.
|
|
10356
|
+
* Traverses to find all THREE.Bone instances and creates octahedral meshes.
|
|
10357
|
+
*/
|
|
10358
|
+
loadSkeleton(root) {
|
|
10359
|
+
this.clear();
|
|
10360
|
+
root.updateWorldMatrix(true, true);
|
|
10361
|
+
const allBones = collectSkeletonBones(root);
|
|
10362
|
+
const includedBones = new Set(allBones);
|
|
10363
|
+
this.boneLookup = new Map(allBones.map((bone) => [bone.name, bone]));
|
|
10364
|
+
if (allBones.length === 0) return;
|
|
10365
|
+
this.boneTree = allBones.map((bone) => {
|
|
10366
|
+
const isMapped = this.mappedBoneNames.has(bone.name);
|
|
10367
|
+
const semanticKey = this.semanticKeyMap.get(bone.name) ?? null;
|
|
10368
|
+
const boneChildren = bone.children.filter((c) => isBone2(c) && includedBones.has(c)).map((c) => c.name);
|
|
10369
|
+
const parentName = bone.parent && isBone2(bone.parent) && includedBones.has(bone.parent) ? bone.parent.name : null;
|
|
10370
|
+
let depth = 0;
|
|
10371
|
+
let current = bone.parent;
|
|
10372
|
+
while (current && isBone2(current) && includedBones.has(current)) {
|
|
10373
|
+
depth++;
|
|
10374
|
+
current = current.parent;
|
|
10375
|
+
}
|
|
10376
|
+
return {
|
|
10377
|
+
name: bone.name,
|
|
10378
|
+
depth,
|
|
10379
|
+
parent: parentName,
|
|
10380
|
+
children: boneChildren,
|
|
10381
|
+
isMapped,
|
|
10382
|
+
semanticKey,
|
|
10383
|
+
boneObject: bone
|
|
10384
|
+
};
|
|
10385
|
+
});
|
|
10386
|
+
for (const bone of allBones) {
|
|
10387
|
+
const boneChildren = bone.children.filter(
|
|
10388
|
+
(c) => isBone2(c) && includedBones.has(c)
|
|
10389
|
+
);
|
|
10390
|
+
if (boneChildren.length > 0) {
|
|
10391
|
+
if (shouldSkipRootHelperConnection(bone, boneChildren)) {
|
|
10392
|
+
continue;
|
|
10393
|
+
}
|
|
10394
|
+
for (const child of boneChildren) {
|
|
10395
|
+
this.createBoneMesh(bone, child);
|
|
10396
|
+
}
|
|
10397
|
+
} else {
|
|
10398
|
+
this.createBoneMesh(bone, null);
|
|
10399
|
+
}
|
|
10400
|
+
}
|
|
10401
|
+
this.group.add(this.axisGizmoGroup);
|
|
10402
|
+
const sentinelGeo = new THREE3.SphereGeometry(1e-6, 4, 2);
|
|
10403
|
+
const sentinelMat = new THREE3.MeshBasicMaterial({
|
|
10404
|
+
transparent: true,
|
|
10405
|
+
opacity: 0,
|
|
10406
|
+
depthWrite: false,
|
|
10407
|
+
depthTest: false
|
|
10408
|
+
});
|
|
10409
|
+
sentinelMat.colorWrite = false;
|
|
10410
|
+
const sentinel = new THREE3.Mesh(sentinelGeo, sentinelMat);
|
|
10411
|
+
sentinel.visible = true;
|
|
10412
|
+
sentinel.frustumCulled = false;
|
|
10413
|
+
sentinel.onBeforeRender = (renderer, _scene, camera) => this.update(camera, renderer.domElement);
|
|
10414
|
+
this.sentinelMesh = sentinel;
|
|
10415
|
+
this.group.add(sentinel);
|
|
10416
|
+
this.scene.add(this.group);
|
|
10417
|
+
this.update();
|
|
10418
|
+
}
|
|
10419
|
+
createBoneMesh(parentBone, childBone) {
|
|
10420
|
+
const isMapped = this.mappedBoneNames.has(parentBone.name);
|
|
10421
|
+
const material = isMapped ? this.mappedMaterial : this.unmappedMaterial;
|
|
10422
|
+
const mesh = new THREE3.Mesh(this.sharedGeometry, material);
|
|
10423
|
+
mesh.name = `bone_vis_${parentBone.name}${childBone ? `_${childBone.name}` : "_leaf"}`;
|
|
10424
|
+
mesh.frustumCulled = false;
|
|
10425
|
+
mesh.renderOrder = 999;
|
|
10426
|
+
mesh.userData.boneName = parentBone.name;
|
|
10427
|
+
this.visualizations.push({
|
|
10428
|
+
mesh,
|
|
10429
|
+
parentBone,
|
|
10430
|
+
childBone,
|
|
10431
|
+
boneName: parentBone.name,
|
|
10432
|
+
isMapped
|
|
10433
|
+
});
|
|
10434
|
+
this.group.add(mesh);
|
|
10435
|
+
}
|
|
10436
|
+
/** Called each frame via onBeforeRender. Updates all bone mesh transforms. */
|
|
10437
|
+
update(camera, domElement) {
|
|
10438
|
+
this.advanceVisibility();
|
|
10439
|
+
for (const vis of this.visualizations) {
|
|
10440
|
+
vis.parentBone.getWorldPosition(_parentPos);
|
|
10441
|
+
if (vis.childBone) {
|
|
10442
|
+
vis.childBone.getWorldPosition(_childPos);
|
|
10443
|
+
} else {
|
|
10444
|
+
_childPos.set(0, LEAF_BONE_LENGTH, 0);
|
|
10445
|
+
vis.parentBone.localToWorld(_childPos);
|
|
10446
|
+
}
|
|
10447
|
+
_direction.subVectors(_childPos, _parentPos);
|
|
10448
|
+
const length = _direction.length();
|
|
10449
|
+
if (length < 1e-4) {
|
|
10450
|
+
vis.mesh.visible = false;
|
|
10451
|
+
continue;
|
|
10452
|
+
}
|
|
10453
|
+
vis.mesh.visible = this.visibilityAlpha > VISIBILITY_EPSILON;
|
|
10454
|
+
_direction.normalize();
|
|
10455
|
+
_quaternion.setFromUnitVectors(_zAxis, _direction);
|
|
10456
|
+
vis.mesh.position.copy(_parentPos);
|
|
10457
|
+
vis.mesh.quaternion.copy(_quaternion);
|
|
10458
|
+
vis.mesh.scale.set(length, length, length);
|
|
10459
|
+
}
|
|
10460
|
+
this.updateAxisGizmo(camera, domElement);
|
|
10461
|
+
}
|
|
10462
|
+
syncVisibilityOpacity() {
|
|
10463
|
+
const alpha = this.visibilityAlpha * this.opacityScale;
|
|
10464
|
+
this.mappedMaterial.opacity = OPACITY.mapped * alpha;
|
|
10465
|
+
this.unmappedMaterial.opacity = OPACITY.unmapped * alpha;
|
|
10466
|
+
this.highlightMaterial.opacity = OPACITY.highlighted * alpha;
|
|
10467
|
+
this.selectedMaterial.opacity = OPACITY.selected * alpha;
|
|
10468
|
+
this.contextMaterial.opacity = OPACITY.context * alpha;
|
|
10469
|
+
this.mutedMaterial.opacity = OPACITY.muted * alpha;
|
|
10470
|
+
}
|
|
10471
|
+
syncGroupVisibility() {
|
|
10472
|
+
this.group.visible = this.visualizations.length > 0 && (this.visibilityTarget > 0 || this.visibilityAlpha > VISIBILITY_EPSILON || this.selectedBone != null);
|
|
10473
|
+
}
|
|
10474
|
+
advanceVisibility() {
|
|
10475
|
+
const now = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
10476
|
+
const deltaMs = this.visibilityLastFrameAt > 0 ? now - this.visibilityLastFrameAt : 16;
|
|
10477
|
+
this.visibilityLastFrameAt = now;
|
|
10478
|
+
if (Math.abs(this.visibilityAlpha - this.visibilityTarget) > VISIBILITY_EPSILON) {
|
|
10479
|
+
const step = deltaMs / VISIBILITY_FADE_MS;
|
|
10480
|
+
if (this.visibilityTarget > this.visibilityAlpha) {
|
|
10481
|
+
this.visibilityAlpha = Math.min(this.visibilityTarget, this.visibilityAlpha + step);
|
|
10482
|
+
} else {
|
|
10483
|
+
this.visibilityAlpha = Math.max(this.visibilityTarget, this.visibilityAlpha - step);
|
|
10484
|
+
}
|
|
10485
|
+
} else {
|
|
10486
|
+
this.visibilityAlpha = this.visibilityTarget;
|
|
10487
|
+
}
|
|
10488
|
+
if (this.visibilityAlpha <= VISIBILITY_EPSILON && this.visibilityTarget === 0) {
|
|
10489
|
+
this.visibilityAlpha = 0;
|
|
10490
|
+
}
|
|
10491
|
+
this.syncVisibilityOpacity();
|
|
10492
|
+
this.syncGroupVisibility();
|
|
10493
|
+
}
|
|
10494
|
+
resolveSelectedBoneObject() {
|
|
10495
|
+
if (!this.selectedBone) return null;
|
|
10496
|
+
return this.boneLookup.get(this.selectedBone) ?? null;
|
|
10497
|
+
}
|
|
10498
|
+
resolveBoneRelativeAxisGizmoRadius(bone) {
|
|
10499
|
+
bone.getWorldPosition(_parentPos);
|
|
10500
|
+
let referenceLength = 0;
|
|
10501
|
+
for (const child of bone.children) {
|
|
10502
|
+
if (!isBone2(child)) continue;
|
|
10503
|
+
child.getWorldPosition(_childPos);
|
|
10504
|
+
const nextDistance = _parentPos.distanceTo(_childPos);
|
|
10505
|
+
if (nextDistance <= 1e-4) continue;
|
|
10506
|
+
referenceLength = referenceLength === 0 ? nextDistance : Math.min(referenceLength, nextDistance);
|
|
10507
|
+
}
|
|
10508
|
+
if (referenceLength <= 1e-4 && isBone2(bone.parent)) {
|
|
10509
|
+
bone.parent.getWorldPosition(_childPos);
|
|
10510
|
+
referenceLength = _parentPos.distanceTo(_childPos);
|
|
10511
|
+
}
|
|
10512
|
+
if (referenceLength <= 1e-4) {
|
|
10513
|
+
referenceLength = LEAF_BONE_LENGTH;
|
|
10514
|
+
}
|
|
10515
|
+
return THREE3.MathUtils.clamp(
|
|
10516
|
+
referenceLength * AXIS_GIZMO_RADIUS_FACTOR,
|
|
10517
|
+
AXIS_GIZMO_MIN_RADIUS,
|
|
10518
|
+
AXIS_GIZMO_MAX_RADIUS
|
|
10519
|
+
);
|
|
10520
|
+
}
|
|
10521
|
+
resolveScreenSpaceAxisGizmoRadius(origin, camera, domElement) {
|
|
10522
|
+
if (!camera || !domElement) return null;
|
|
10523
|
+
const rect = domElement.getBoundingClientRect();
|
|
10524
|
+
const viewportHeight = domElement.clientHeight || rect.height;
|
|
10525
|
+
if (viewportHeight <= 0) return null;
|
|
10526
|
+
if (camera.isPerspectiveCamera) {
|
|
10527
|
+
const perspectiveCamera = camera;
|
|
10528
|
+
_cameraSpacePosition.copy(origin).applyMatrix4(perspectiveCamera.matrixWorldInverse);
|
|
10529
|
+
const depth = -_cameraSpacePosition.z;
|
|
10530
|
+
if (!Number.isFinite(depth) || depth <= perspectiveCamera.near) return null;
|
|
10531
|
+
const visibleWorldHeight = 2 * depth * Math.tan(THREE3.MathUtils.degToRad(perspectiveCamera.getEffectiveFOV()) / 2);
|
|
10532
|
+
return visibleWorldHeight * (AXIS_GIZMO_SCREEN_RADIUS_PX / viewportHeight);
|
|
10533
|
+
}
|
|
10534
|
+
if (camera.isOrthographicCamera) {
|
|
10535
|
+
const orthographicCamera = camera;
|
|
10536
|
+
const visibleWorldHeight = (orthographicCamera.top - orthographicCamera.bottom) / orthographicCamera.zoom;
|
|
10537
|
+
return visibleWorldHeight * (AXIS_GIZMO_SCREEN_RADIUS_PX / viewportHeight);
|
|
10538
|
+
}
|
|
10539
|
+
return null;
|
|
10540
|
+
}
|
|
10541
|
+
resolveAxisGizmoRadius(bone, origin, camera, domElement) {
|
|
10542
|
+
return this.resolveScreenSpaceAxisGizmoRadius(origin, camera, domElement) ?? this.resolveBoneRelativeAxisGizmoRadius(bone);
|
|
10543
|
+
}
|
|
10544
|
+
updateAxisGizmoAppearance() {
|
|
10545
|
+
const hasSelection = !!this.selectedBone;
|
|
10546
|
+
this.axisGizmoGroup.visible = hasSelection && this.group.visible;
|
|
10547
|
+
const meshes = [
|
|
10548
|
+
["pitch", this.axisGizmoMeshes.pitch, this.axisGizmoHitMeshes.pitch],
|
|
10549
|
+
["yaw", this.axisGizmoMeshes.yaw, this.axisGizmoHitMeshes.yaw],
|
|
10550
|
+
["roll", this.axisGizmoMeshes.roll, this.axisGizmoHitMeshes.roll]
|
|
10551
|
+
];
|
|
10552
|
+
meshes.forEach(([axis, mesh, hitMesh]) => {
|
|
10553
|
+
const material = mesh.material;
|
|
10554
|
+
const isActive = this.activeAxis === axis;
|
|
10555
|
+
const baseOpacity = this.activeAxis == null ? AXIS_GIZMO_IDLE_OPACITY : isActive ? AXIS_GIZMO_ACTIVE_OPACITY : AXIS_GIZMO_MUTED_OPACITY;
|
|
10556
|
+
material.opacity = hasSelection ? baseOpacity : 0;
|
|
10557
|
+
const scale = isActive ? AXIS_GIZMO_ACTIVE_SCALE : 1;
|
|
10558
|
+
mesh.scale.setScalar(scale);
|
|
10559
|
+
hitMesh.scale.setScalar(scale);
|
|
10560
|
+
});
|
|
10561
|
+
this.axisCenterMaterial.opacity = hasSelection ? 0.92 : 0;
|
|
10562
|
+
}
|
|
10563
|
+
updateAxisGizmo(camera, domElement) {
|
|
10564
|
+
const selectedBone = this.resolveSelectedBoneObject();
|
|
10565
|
+
if (!selectedBone || !this.group.visible) {
|
|
10566
|
+
this.axisGizmoGroup.visible = false;
|
|
10567
|
+
return;
|
|
10568
|
+
}
|
|
10569
|
+
selectedBone.getWorldPosition(_bonePos);
|
|
10570
|
+
selectedBone.getWorldQuaternion(_boneQuat);
|
|
10571
|
+
const radius = this.resolveAxisGizmoRadius(selectedBone, _bonePos, camera, domElement);
|
|
10572
|
+
this.axisGizmoGroup.visible = true;
|
|
10573
|
+
this.axisGizmoGroup.position.copy(_bonePos);
|
|
10574
|
+
this.axisGizmoGroup.quaternion.copy(_boneQuat);
|
|
10575
|
+
this.axisGizmoGroup.scale.setScalar(radius);
|
|
10576
|
+
this.updateAxisGizmoAppearance();
|
|
10577
|
+
this.axisGizmoGroup.updateWorldMatrix(true, true);
|
|
10578
|
+
}
|
|
10579
|
+
applyAxisRingChannel(mesh, hitMesh, axis) {
|
|
10580
|
+
const channel = DEFAULT_AXIS_CHANNELS[axis];
|
|
10581
|
+
mesh.rotation.set(0, 0, 0);
|
|
10582
|
+
hitMesh.rotation.set(0, 0, 0);
|
|
10583
|
+
if (channel === "rx") {
|
|
10584
|
+
mesh.rotation.y = Math.PI / 2;
|
|
10585
|
+
hitMesh.rotation.y = Math.PI / 2;
|
|
10586
|
+
return;
|
|
10587
|
+
}
|
|
10588
|
+
if (channel === "ry") {
|
|
10589
|
+
mesh.rotation.x = Math.PI / 2;
|
|
10590
|
+
hitMesh.rotation.x = Math.PI / 2;
|
|
10591
|
+
}
|
|
10592
|
+
}
|
|
10593
|
+
syncAxisGizmoChannels() {
|
|
10594
|
+
Object.keys(this.axisGizmoMeshes).forEach((axis) => {
|
|
10595
|
+
this.applyAxisRingChannel(
|
|
10596
|
+
this.axisGizmoMeshes[axis],
|
|
10597
|
+
this.axisGizmoHitMeshes[axis],
|
|
10598
|
+
axis
|
|
10599
|
+
);
|
|
10600
|
+
});
|
|
10601
|
+
}
|
|
10602
|
+
getSelectedBonePointerData() {
|
|
10603
|
+
const bone = this.resolveSelectedBoneObject();
|
|
10604
|
+
if (!bone || !this.selectedBone) return null;
|
|
10605
|
+
bone.getWorldPosition(_bonePos);
|
|
10606
|
+
bone.getWorldQuaternion(_boneQuat);
|
|
10607
|
+
return {
|
|
10608
|
+
boneName: this.selectedBone,
|
|
10609
|
+
origin: _bonePos.clone(),
|
|
10610
|
+
quaternion: _boneQuat.clone()
|
|
10611
|
+
};
|
|
10612
|
+
}
|
|
10613
|
+
getAxisPlaneNormal(axis, quaternion) {
|
|
10614
|
+
const channel = DEFAULT_AXIS_CHANNELS[axis];
|
|
10615
|
+
if (channel === "rx") {
|
|
10616
|
+
return _planeNormal.set(1, 0, 0).applyQuaternion(quaternion).normalize();
|
|
10617
|
+
}
|
|
10618
|
+
if (channel === "ry") {
|
|
10619
|
+
return _planeNormal.set(0, 1, 0).applyQuaternion(quaternion).normalize();
|
|
10620
|
+
}
|
|
10621
|
+
return _planeNormal.set(0, 0, 1).applyQuaternion(quaternion).normalize();
|
|
10622
|
+
}
|
|
10623
|
+
// ── Public API ─────────────────────────────────────────────────────────
|
|
10624
|
+
getBoneTree() {
|
|
10625
|
+
return this.boneTree;
|
|
10626
|
+
}
|
|
10627
|
+
setVisible(visible) {
|
|
10628
|
+
this.visibilityTarget = visible ? 1 : 0;
|
|
10629
|
+
this.visibilityLastFrameAt = 0;
|
|
10630
|
+
this.syncGroupVisibility();
|
|
10631
|
+
this.advanceVisibility();
|
|
10632
|
+
this.updateAxisGizmoAppearance();
|
|
10633
|
+
}
|
|
10634
|
+
isVisible() {
|
|
10635
|
+
return this.visibilityTarget > 0;
|
|
10636
|
+
}
|
|
10637
|
+
setHighlighted(boneName) {
|
|
10638
|
+
this.highlightedBone = boneName;
|
|
10639
|
+
this.refreshBoneMaterials();
|
|
10640
|
+
}
|
|
10641
|
+
setSelected(boneName) {
|
|
10642
|
+
this.selectedBone = boneName;
|
|
10643
|
+
this.refreshBoneMaterials();
|
|
10644
|
+
this.syncGroupVisibility();
|
|
10645
|
+
this.updateAxisGizmo();
|
|
10646
|
+
}
|
|
10647
|
+
setActiveAxis(axis) {
|
|
10648
|
+
this.activeAxis = axis;
|
|
10649
|
+
this.updateAxisGizmoAppearance();
|
|
10650
|
+
}
|
|
10651
|
+
/** Get world position and rotation for a bone by name */
|
|
10652
|
+
getBoneTransform(boneName) {
|
|
10653
|
+
const node = this.boneTree.find((b) => b.name === boneName);
|
|
10654
|
+
if (!node) return null;
|
|
10655
|
+
const bone = node.boneObject;
|
|
10656
|
+
bone.getWorldPosition(_bonePos);
|
|
10657
|
+
bone.getWorldQuaternion(_boneQuat);
|
|
10658
|
+
_boneEuler.setFromQuaternion(_boneQuat);
|
|
10659
|
+
return {
|
|
10660
|
+
position: [_bonePos.x, _bonePos.y, _bonePos.z],
|
|
10661
|
+
rotation: [
|
|
10662
|
+
THREE3.MathUtils.radToDeg(_boneEuler.x),
|
|
10663
|
+
THREE3.MathUtils.radToDeg(_boneEuler.y),
|
|
10664
|
+
THREE3.MathUtils.radToDeg(_boneEuler.z)
|
|
10665
|
+
]
|
|
10666
|
+
};
|
|
10667
|
+
}
|
|
10668
|
+
pickBone(clientX, clientY, camera, domElement) {
|
|
10669
|
+
if (!this.group.visible || this.visibilityTarget <= 0 || this.visibilityAlpha <= 0.35) {
|
|
10670
|
+
return null;
|
|
10671
|
+
}
|
|
10672
|
+
if (!setRayFromPointer(clientX, clientY, camera, domElement)) {
|
|
10673
|
+
return null;
|
|
10674
|
+
}
|
|
10675
|
+
const hits = _raycaster.intersectObjects(
|
|
10676
|
+
this.visualizations.map((visualization) => visualization.mesh),
|
|
10677
|
+
false
|
|
10678
|
+
);
|
|
10679
|
+
const hit = hits[0];
|
|
10680
|
+
if (!hit) return null;
|
|
10681
|
+
return typeof hit.object.userData.boneName === "string" ? hit.object.userData.boneName : null;
|
|
10682
|
+
}
|
|
10683
|
+
pickAxisGizmo(clientX, clientY, camera, domElement) {
|
|
10684
|
+
if (!this.axisGizmoGroup.visible || !this.selectedBone) return null;
|
|
10685
|
+
this.updateAxisGizmo(camera, domElement);
|
|
10686
|
+
if (!this.axisGizmoGroup.visible) return null;
|
|
10687
|
+
if (!setRayFromPointer(clientX, clientY, camera, domElement)) {
|
|
10688
|
+
return null;
|
|
10689
|
+
}
|
|
10690
|
+
const hits = _raycaster.intersectObjects(Object.values(this.axisGizmoHitMeshes), false);
|
|
10691
|
+
const hit = hits[0];
|
|
10692
|
+
if (!hit) return null;
|
|
10693
|
+
const axis = hit.object.userData.axis;
|
|
10694
|
+
if (axis !== "pitch" && axis !== "yaw" && axis !== "roll") {
|
|
10695
|
+
return null;
|
|
10696
|
+
}
|
|
10697
|
+
const base = this.getSelectedBonePointerData();
|
|
10698
|
+
if (!base) return null;
|
|
10699
|
+
return {
|
|
10700
|
+
...base,
|
|
10701
|
+
axis,
|
|
10702
|
+
channel: DEFAULT_AXIS_CHANNELS[axis],
|
|
10703
|
+
point: hit.point.clone()
|
|
10704
|
+
};
|
|
10705
|
+
}
|
|
10706
|
+
projectPointerToAxisPlane(clientX, clientY, camera, domElement, axis) {
|
|
10707
|
+
if (!this.axisGizmoGroup.visible || !this.selectedBone) {
|
|
10708
|
+
return null;
|
|
10709
|
+
}
|
|
10710
|
+
this.updateAxisGizmo(camera, domElement);
|
|
10711
|
+
if (!this.axisGizmoGroup.visible) return null;
|
|
10712
|
+
const base = this.getSelectedBonePointerData();
|
|
10713
|
+
if (!base) return null;
|
|
10714
|
+
if (!setRayFromPointer(clientX, clientY, camera, domElement)) {
|
|
10715
|
+
return null;
|
|
10716
|
+
}
|
|
10717
|
+
const normal = this.getAxisPlaneNormal(axis, base.quaternion);
|
|
10718
|
+
_dragPlane.setFromNormalAndCoplanarPoint(normal, base.origin);
|
|
10719
|
+
const hit = _raycaster.ray.intersectPlane(_dragPlane, _planeHit);
|
|
10720
|
+
if (!hit) return null;
|
|
10721
|
+
return {
|
|
10722
|
+
...base,
|
|
10723
|
+
axis,
|
|
10724
|
+
channel: DEFAULT_AXIS_CHANNELS[axis],
|
|
10725
|
+
point: hit.clone()
|
|
10726
|
+
};
|
|
10727
|
+
}
|
|
10728
|
+
isSelectedContextBone(boneName) {
|
|
10729
|
+
if (!this.selectedBone || boneName === this.selectedBone) return false;
|
|
10730
|
+
const selectedBone = this.boneLookup.get(this.selectedBone);
|
|
10731
|
+
const bone = this.boneLookup.get(boneName);
|
|
10732
|
+
if (!selectedBone || !bone) return false;
|
|
10733
|
+
return bone.parent === selectedBone || selectedBone.parent === bone;
|
|
10734
|
+
}
|
|
10735
|
+
resolveBoneMaterial(boneName, isMapped) {
|
|
10736
|
+
if (boneName === this.selectedBone) {
|
|
10737
|
+
return this.selectedMaterial;
|
|
10738
|
+
}
|
|
10739
|
+
if (boneName === this.highlightedBone) {
|
|
10740
|
+
return this.highlightMaterial;
|
|
10741
|
+
}
|
|
10742
|
+
if (this.focusSelectedBone && this.selectedBone) {
|
|
10743
|
+
return this.isSelectedContextBone(boneName) ? this.contextMaterial : this.mutedMaterial;
|
|
10744
|
+
}
|
|
10745
|
+
return isMapped ? this.mappedMaterial : this.unmappedMaterial;
|
|
10746
|
+
}
|
|
10747
|
+
refreshBoneMaterials() {
|
|
10748
|
+
for (const vis of this.visualizations) {
|
|
10749
|
+
vis.mesh.material = this.resolveBoneMaterial(vis.boneName, vis.isMapped);
|
|
10750
|
+
}
|
|
10751
|
+
}
|
|
10752
|
+
clear() {
|
|
10753
|
+
if (this.sentinelMesh) {
|
|
10754
|
+
this.sentinelMesh.geometry.dispose();
|
|
10755
|
+
this.sentinelMesh.material.dispose();
|
|
10756
|
+
this.sentinelMesh = null;
|
|
10757
|
+
}
|
|
10758
|
+
while (this.group.children.length > 0) {
|
|
10759
|
+
this.group.remove(this.group.children[0]);
|
|
10760
|
+
}
|
|
10761
|
+
this.visualizations = [];
|
|
10762
|
+
this.boneTree = [];
|
|
10763
|
+
this.boneLookup.clear();
|
|
10764
|
+
this.axisGizmoGroup.visible = false;
|
|
10765
|
+
this.group.visible = false;
|
|
10766
|
+
}
|
|
10767
|
+
dispose() {
|
|
10768
|
+
this.scene.remove(this.group);
|
|
10769
|
+
this.clear();
|
|
10770
|
+
this.sharedGeometry.dispose();
|
|
10771
|
+
this.mappedMaterial.dispose();
|
|
10772
|
+
this.unmappedMaterial.dispose();
|
|
10773
|
+
this.highlightMaterial.dispose();
|
|
10774
|
+
this.selectedMaterial.dispose();
|
|
10775
|
+
this.contextMaterial.dispose();
|
|
10776
|
+
this.mutedMaterial.dispose();
|
|
10777
|
+
this.axisRingGeometry.dispose();
|
|
10778
|
+
this.axisRingHitGeometry.dispose();
|
|
10779
|
+
this.axisCenterGeometry.dispose();
|
|
10780
|
+
this.pitchRingMaterial.dispose();
|
|
10781
|
+
this.yawRingMaterial.dispose();
|
|
10782
|
+
this.rollRingMaterial.dispose();
|
|
10783
|
+
this.axisHitMaterial.dispose();
|
|
10784
|
+
this.axisCenterMaterial.dispose();
|
|
10785
|
+
}
|
|
10786
|
+
};
|
|
10787
|
+
function collectSkeletonBones(root) {
|
|
10788
|
+
const startBone = resolveBodyDisplayRoot(root);
|
|
10789
|
+
return collectBones(startBone ?? root);
|
|
10790
|
+
}
|
|
10791
|
+
function findModelRoot(scene) {
|
|
10792
|
+
const roots = {};
|
|
10793
|
+
scene.traverse((obj) => {
|
|
10794
|
+
if (!roots.tagged && (obj.userData?.embodyCharacterModel === true || obj.userData?.loomlargeCharacterModel === true)) {
|
|
10795
|
+
roots.tagged = obj;
|
|
10796
|
+
}
|
|
10797
|
+
if (!roots.bone && obj.isBone) {
|
|
10798
|
+
roots.bone = obj;
|
|
10799
|
+
}
|
|
10800
|
+
if (!roots.skinned && obj.isSkinnedMesh) {
|
|
10801
|
+
roots.skinned = obj;
|
|
10802
|
+
}
|
|
10803
|
+
});
|
|
10804
|
+
if (roots.tagged) return roots.tagged;
|
|
10805
|
+
const anchor = roots.bone ?? roots.skinned;
|
|
10806
|
+
if (!anchor) return null;
|
|
10807
|
+
let current = anchor;
|
|
10808
|
+
while (current && current.parent && current.parent !== scene) {
|
|
10809
|
+
current = current.parent;
|
|
10810
|
+
}
|
|
10811
|
+
return current;
|
|
10812
|
+
}
|
|
10813
|
+
function buildMappedBoneInfo(profile, actualBoneNames = []) {
|
|
10814
|
+
const mappedNames = /* @__PURE__ */ new Set();
|
|
10815
|
+
const semanticKeyMap = /* @__PURE__ */ new Map();
|
|
10816
|
+
const semanticKeysMap = /* @__PURE__ */ new Map();
|
|
10817
|
+
const actualNames = Array.from(new Set(actualBoneNames));
|
|
10818
|
+
const boneNodes = profile.boneNodes || {};
|
|
10819
|
+
const prefix = profile.bonePrefix || "";
|
|
10820
|
+
const suffix = profile.boneSuffix || "";
|
|
10821
|
+
const addSemanticKey = (boneName, semanticKey) => {
|
|
10822
|
+
mappedNames.add(boneName);
|
|
10823
|
+
if (!semanticKeyMap.has(boneName)) {
|
|
10824
|
+
semanticKeyMap.set(boneName, semanticKey);
|
|
10825
|
+
}
|
|
10826
|
+
const existing = semanticKeysMap.get(boneName) ?? [];
|
|
10827
|
+
if (!existing.includes(semanticKey)) {
|
|
10828
|
+
semanticKeysMap.set(boneName, [...existing, semanticKey]);
|
|
10829
|
+
}
|
|
10830
|
+
};
|
|
10831
|
+
for (const [semanticKey, nodeName] of Object.entries(boneNodes)) {
|
|
10832
|
+
const fullName = prefix + nodeName + suffix;
|
|
10833
|
+
addSemanticKey(fullName, semanticKey);
|
|
10834
|
+
if (fullName !== nodeName) {
|
|
10835
|
+
addSemanticKey(nodeName, semanticKey);
|
|
10836
|
+
}
|
|
10837
|
+
actualNames.forEach((actualName) => {
|
|
10838
|
+
if (fuzzyNameMatch(actualName, fullName, profile.suffixPattern) || fuzzyNameMatch(actualName, nodeName, profile.suffixPattern)) {
|
|
10839
|
+
addSemanticKey(actualName, semanticKey);
|
|
10840
|
+
}
|
|
10841
|
+
});
|
|
10842
|
+
}
|
|
10843
|
+
return { mappedNames, semanticKeyMap, semanticKeysMap };
|
|
10844
|
+
}
|
|
10845
|
+
var DEFAULT_THRESHOLD = 1e-4;
|
|
10846
|
+
var DEFAULT_MAX_POINTS = 12e3;
|
|
10847
|
+
var COLOR_LOW = { r: 1, g: 0.78, b: 0.28 };
|
|
10848
|
+
var COLOR_HIGH = { r: 1, g: 0.36, b: 0.16 };
|
|
10849
|
+
function getMorphPositionAttribute2(mesh, morphName) {
|
|
10850
|
+
const morphIndex = mesh.morphTargetDictionary?.[morphName];
|
|
10851
|
+
if (morphIndex == null) return null;
|
|
10852
|
+
const morphPositions = mesh.geometry.morphAttributes.position;
|
|
10853
|
+
return morphPositions?.[morphIndex] ?? null;
|
|
10854
|
+
}
|
|
10855
|
+
function getDeltaMagnitudeAtIndex(mesh, morphAttribute, index) {
|
|
10856
|
+
const geometry = mesh.geometry;
|
|
10857
|
+
const relative = geometry.morphTargetsRelative;
|
|
10858
|
+
const basePositions = geometry.getAttribute("position");
|
|
10859
|
+
if (!basePositions) return 0;
|
|
10860
|
+
const dx = morphAttribute.getX(index) - (relative ? 0 : basePositions.getX(index));
|
|
10861
|
+
const dy = morphAttribute.getY(index) - (relative ? 0 : basePositions.getY(index));
|
|
10862
|
+
const dz = morphAttribute.getZ(index) - (relative ? 0 : basePositions.getZ(index));
|
|
10863
|
+
return Math.hypot(dx, dy, dz);
|
|
10864
|
+
}
|
|
10865
|
+
function resolveMorphPreviewMeshes(root, morphName, meshNames) {
|
|
10866
|
+
const nameFilter = meshNames?.length ? new Set(meshNames) : null;
|
|
10867
|
+
const meshes = [];
|
|
10868
|
+
root.traverse((obj) => {
|
|
10869
|
+
const mesh = obj;
|
|
10870
|
+
if (!mesh.isMesh) return;
|
|
10871
|
+
if (nameFilter && !nameFilter.has(mesh.name)) return;
|
|
10872
|
+
if (mesh.morphTargetDictionary?.[morphName] == null) return;
|
|
10873
|
+
meshes.push(mesh);
|
|
10874
|
+
});
|
|
10875
|
+
return meshes;
|
|
10876
|
+
}
|
|
10877
|
+
function buildMorphVertexPreviewSample(mesh, morphName, options = {}) {
|
|
10878
|
+
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
10879
|
+
const maxPoints = options.maxPoints ?? DEFAULT_MAX_POINTS;
|
|
10880
|
+
const morphAttribute = getMorphPositionAttribute2(mesh, morphName);
|
|
10881
|
+
if (!morphAttribute) return null;
|
|
10882
|
+
const candidateIndices = [];
|
|
10883
|
+
const magnitudes = [];
|
|
10884
|
+
for (let index = 0; index < morphAttribute.count; index += 1) {
|
|
10885
|
+
const magnitude = getDeltaMagnitudeAtIndex(mesh, morphAttribute, index);
|
|
10886
|
+
if (magnitude <= threshold) continue;
|
|
10887
|
+
candidateIndices.push(index);
|
|
10888
|
+
magnitudes.push(magnitude);
|
|
10889
|
+
}
|
|
10890
|
+
if (candidateIndices.length === 0) return null;
|
|
10891
|
+
const stride = candidateIndices.length > maxPoints ? Math.ceil(candidateIndices.length / maxPoints) : 1;
|
|
10892
|
+
const sampleCount = Math.ceil(candidateIndices.length / stride);
|
|
10893
|
+
const indices = new Uint32Array(sampleCount);
|
|
10894
|
+
const intensities = new Float32Array(sampleCount);
|
|
10895
|
+
let maxMagnitude = 0;
|
|
10896
|
+
let cursor = 0;
|
|
10897
|
+
for (let i = 0; i < candidateIndices.length; i += stride) {
|
|
10898
|
+
const magnitude = magnitudes[i] ?? 0;
|
|
10899
|
+
indices[cursor] = candidateIndices[i] ?? 0;
|
|
10900
|
+
intensities[cursor] = magnitude;
|
|
10901
|
+
maxMagnitude = Math.max(maxMagnitude, magnitude);
|
|
10902
|
+
cursor += 1;
|
|
10903
|
+
}
|
|
10904
|
+
const normalizer = maxMagnitude > 0 ? 1 / maxMagnitude : 1;
|
|
10905
|
+
for (let i = 0; i < intensities.length; i += 1) {
|
|
10906
|
+
intensities[i] *= normalizer;
|
|
10907
|
+
}
|
|
10908
|
+
return { indices, intensities };
|
|
10909
|
+
}
|
|
10910
|
+
function writePointColor(colors, offset, intensity2) {
|
|
10911
|
+
colors[offset] = COLOR_LOW.r + (COLOR_HIGH.r - COLOR_LOW.r) * intensity2;
|
|
10912
|
+
colors[offset + 1] = COLOR_LOW.g + (COLOR_HIGH.g - COLOR_LOW.g) * intensity2;
|
|
10913
|
+
colors[offset + 2] = COLOR_LOW.b + (COLOR_HIGH.b - COLOR_LOW.b) * intensity2;
|
|
10914
|
+
}
|
|
10915
|
+
function buildMorphVertexPreviewRenderData(meshes, morphName, cache, options = {}) {
|
|
10916
|
+
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
10917
|
+
const maxPoints = options.maxPoints ?? DEFAULT_MAX_POINTS;
|
|
10918
|
+
const sampledMeshes = [];
|
|
10919
|
+
let pointCount = 0;
|
|
10920
|
+
for (const mesh of meshes) {
|
|
10921
|
+
const cacheKey = `${mesh.uuid}:${morphName}:${threshold}:${maxPoints}`;
|
|
10922
|
+
let sample = cache.get(cacheKey) ?? null;
|
|
10923
|
+
if (!sample) {
|
|
10924
|
+
sample = buildMorphVertexPreviewSample(mesh, morphName, { threshold, maxPoints });
|
|
10925
|
+
if (sample) cache.set(cacheKey, sample);
|
|
10926
|
+
}
|
|
10927
|
+
if (!sample) continue;
|
|
10928
|
+
sampledMeshes.push({ mesh, sample });
|
|
10929
|
+
pointCount += sample.indices.length;
|
|
10930
|
+
}
|
|
10931
|
+
if (pointCount === 0) return null;
|
|
10932
|
+
const positions = new Float32Array(pointCount * 3);
|
|
10933
|
+
const colors = new Float32Array(pointCount * 3);
|
|
10934
|
+
const vertex = new THREE3.Vector3();
|
|
10935
|
+
let cursor = 0;
|
|
10936
|
+
for (const { mesh, sample } of sampledMeshes) {
|
|
10937
|
+
mesh.updateWorldMatrix(true, false);
|
|
10938
|
+
const skinnedMesh = mesh;
|
|
10939
|
+
if (skinnedMesh.isSkinnedMesh && skinnedMesh.skeleton) {
|
|
10940
|
+
skinnedMesh.skeleton.update();
|
|
10941
|
+
}
|
|
10942
|
+
for (let i = 0; i < sample.indices.length; i += 1) {
|
|
10943
|
+
mesh.getVertexPosition(sample.indices[i] ?? 0, vertex);
|
|
10944
|
+
vertex.applyMatrix4(mesh.matrixWorld);
|
|
10945
|
+
const offset = cursor * 3;
|
|
10946
|
+
positions[offset] = vertex.x;
|
|
10947
|
+
positions[offset + 1] = vertex.y;
|
|
10948
|
+
positions[offset + 2] = vertex.z;
|
|
10949
|
+
writePointColor(colors, offset, sample.intensities[i] ?? 0);
|
|
10950
|
+
cursor += 1;
|
|
10951
|
+
}
|
|
10952
|
+
}
|
|
10953
|
+
return {
|
|
10954
|
+
positions,
|
|
10955
|
+
colors,
|
|
10956
|
+
pointCount,
|
|
10957
|
+
meshCount: sampledMeshes.length
|
|
10958
|
+
};
|
|
10959
|
+
}
|
|
10960
|
+
var previewRenderBindings = /* @__PURE__ */ new WeakMap();
|
|
10961
|
+
function bindPreviewRender(scene, update) {
|
|
10962
|
+
let binding = previewRenderBindings.get(scene);
|
|
10963
|
+
if (!binding) {
|
|
10964
|
+
const previous = scene.onBeforeRender;
|
|
10965
|
+
const callbacks = /* @__PURE__ */ new Set();
|
|
10966
|
+
const render = function(...args) {
|
|
10967
|
+
previous.apply(scene, args);
|
|
10968
|
+
callbacks.forEach((callback) => callback());
|
|
10969
|
+
};
|
|
10970
|
+
binding = { callbacks, previous, render };
|
|
10971
|
+
scene.onBeforeRender = render;
|
|
10972
|
+
previewRenderBindings.set(scene, binding);
|
|
10973
|
+
}
|
|
10974
|
+
binding.callbacks.add(update);
|
|
10975
|
+
const current = binding;
|
|
10976
|
+
return () => {
|
|
10977
|
+
current.callbacks.delete(update);
|
|
10978
|
+
if (current.callbacks.size > 0) return;
|
|
10979
|
+
if (scene.onBeforeRender === current.render) scene.onBeforeRender = current.previous;
|
|
10980
|
+
if (previewRenderBindings.get(scene) === current) previewRenderBindings.delete(scene);
|
|
10981
|
+
};
|
|
10982
|
+
}
|
|
10983
|
+
var MorphVertexPreview = class {
|
|
10984
|
+
constructor(scene, options = {}) {
|
|
10985
|
+
this.scene = scene;
|
|
10986
|
+
this.options = options;
|
|
10987
|
+
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
10988
|
+
__publicField(this, "points", null);
|
|
10989
|
+
__publicField(this, "active", null);
|
|
10990
|
+
__publicField(this, "disposed", false);
|
|
10991
|
+
__publicField(this, "unbindRender", null);
|
|
10992
|
+
}
|
|
10993
|
+
show(morphName, meshNames) {
|
|
10994
|
+
if (this.disposed) return;
|
|
10995
|
+
if (meshNames?.length === 0) {
|
|
10996
|
+
this.hide();
|
|
10997
|
+
return;
|
|
10998
|
+
}
|
|
10999
|
+
this.active = { morphName, meshNames: meshNames?.slice() };
|
|
11000
|
+
this.unbindRender ?? (this.unbindRender = bindPreviewRender(this.scene, () => this.update()));
|
|
11001
|
+
this.update();
|
|
11002
|
+
}
|
|
11003
|
+
hide() {
|
|
11004
|
+
this.active = null;
|
|
11005
|
+
this.unbindRender?.();
|
|
11006
|
+
this.unbindRender = null;
|
|
11007
|
+
if (this.points) this.points.visible = false;
|
|
11008
|
+
}
|
|
11009
|
+
dispose() {
|
|
11010
|
+
if (this.disposed) return;
|
|
11011
|
+
this.disposed = true;
|
|
11012
|
+
this.hide();
|
|
11013
|
+
this.cache.clear();
|
|
11014
|
+
if (!this.points) return;
|
|
11015
|
+
this.points.removeFromParent();
|
|
11016
|
+
this.points.geometry.dispose();
|
|
11017
|
+
this.points.material.dispose();
|
|
11018
|
+
this.points = null;
|
|
11019
|
+
}
|
|
11020
|
+
createPoints() {
|
|
11021
|
+
const points = new THREE3.Points(new THREE3.BufferGeometry(), new THREE3.PointsMaterial({
|
|
11022
|
+
size: 0.012,
|
|
11023
|
+
sizeAttenuation: true,
|
|
11024
|
+
transparent: true,
|
|
11025
|
+
opacity: 0.9,
|
|
11026
|
+
depthWrite: false,
|
|
11027
|
+
vertexColors: true,
|
|
11028
|
+
toneMapped: false
|
|
11029
|
+
}));
|
|
11030
|
+
points.name = "MorphVertexPreviewPoints";
|
|
11031
|
+
points.renderOrder = 500;
|
|
11032
|
+
points.frustumCulled = false;
|
|
11033
|
+
points.visible = false;
|
|
11034
|
+
this.scene.add(points);
|
|
11035
|
+
this.points = points;
|
|
11036
|
+
return points;
|
|
11037
|
+
}
|
|
11038
|
+
update() {
|
|
11039
|
+
if (this.disposed || !this.active) return;
|
|
11040
|
+
const { morphName, meshNames } = this.active;
|
|
11041
|
+
const meshes = resolveMorphPreviewMeshes(this.scene, morphName, meshNames);
|
|
11042
|
+
const data = buildMorphVertexPreviewRenderData(meshes, morphName, this.cache, this.options);
|
|
11043
|
+
if (!data) {
|
|
11044
|
+
if (this.points) this.points.visible = false;
|
|
11045
|
+
return;
|
|
11046
|
+
}
|
|
11047
|
+
const points = this.points ?? this.createPoints();
|
|
11048
|
+
this.updateAttribute(points.geometry, "position", data.positions, data.pointCount);
|
|
11049
|
+
this.updateAttribute(points.geometry, "color", data.colors, data.pointCount);
|
|
11050
|
+
points.geometry.computeBoundingSphere();
|
|
11051
|
+
points.material.size = data.pointCount > 6e3 ? 9e-3 : 0.012;
|
|
11052
|
+
points.visible = true;
|
|
11053
|
+
}
|
|
11054
|
+
updateAttribute(geometry, name, values, pointCount) {
|
|
11055
|
+
const attribute = geometry.getAttribute(name);
|
|
11056
|
+
if (!attribute || attribute.count !== pointCount) {
|
|
11057
|
+
geometry.setAttribute(name, new THREE3.BufferAttribute(values, 3));
|
|
11058
|
+
return;
|
|
11059
|
+
}
|
|
11060
|
+
attribute.array.set(values);
|
|
11061
|
+
attribute.needsUpdate = true;
|
|
11062
|
+
}
|
|
11063
|
+
};
|
|
9422
11064
|
|
|
9423
11065
|
// three/index.ts
|
|
9424
11066
|
var THREE_BLENDING_MODES = {
|
|
@@ -9430,7 +11072,7 @@ var THREE_BLENDING_MODES = {
|
|
|
9430
11072
|
};
|
|
9431
11073
|
var BLENDING_MODES = THREE_BLENDING_MODES;
|
|
9432
11074
|
var MORPH_ATTRIBUTE_SEMANTICS = ["position", "normal", "tangent", "color"];
|
|
9433
|
-
var
|
|
11075
|
+
var createNeutralMorphAttribute2 = (geometry, semantic, name) => {
|
|
9434
11076
|
const base = geometry.getAttribute(semantic);
|
|
9435
11077
|
if (!base) throw new Error(`Cannot create ${semantic} morph data without a base attribute`);
|
|
9436
11078
|
const values = new Float32Array(base.count * base.itemSize);
|
|
@@ -9941,7 +11583,7 @@ var ThreeFrameApplier = class {
|
|
|
9941
11583
|
sourceIsRelative
|
|
9942
11584
|
);
|
|
9943
11585
|
} else if (!attributes[targetIndex] || targetIndex === index) {
|
|
9944
|
-
attributes[targetIndex] =
|
|
11586
|
+
attributes[targetIndex] = createNeutralMorphAttribute2(
|
|
9945
11587
|
geometry,
|
|
9946
11588
|
semantic,
|
|
9947
11589
|
targetIndex === index ? target.name : `morph_${targetIndex}`
|
|
@@ -9984,6 +11626,6 @@ var collectMorphMeshes = (root) => {
|
|
|
9984
11626
|
return meshes;
|
|
9985
11627
|
};
|
|
9986
11628
|
|
|
9987
|
-
export { BLENDING_MODES, CHARACTER_SCENE_TYPES, CHARACTER_SCENE_TYPE_IDS, DPthreeCameraController 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, DPthree, DPthree3DMarkers, DPthreeCameraController, DPthreeHTMLMarkers, DPthreeMarkers, Embody, DPthree3DMarkers as Markers3D, DPthreeHTMLMarkers as MarkersHTML, RUNTIME_ANNOTATION_REGION_PREFIX, RustAnnotationCameraCore, THREE_BLENDING_MODES, ThreeFrameApplier, ThreeModelInspector, ThreeNativePlaybackConnector, analyzeModel, applyAUBoneBindingUpdate, applyBilateralAxisBindingUpdate, applyBoneAxisBindingUpdate, applyCharacterModelTransform, bindModelReferencePose, boneResolutionProfile, buildBoneAuOptions, buildRegionDisplayOptions, captureModelReferencePose, classifyAuAsJointControl, collectMorphMeshes, 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, findNodeKeyForBone, formatAxisDirectionLabel, formatAxisLabel, fuzzyNameMatch, getAUBoneBindingState, getAxisFromChannel, getBilateralAxisBindingState,
|
|
11629
|
+
export { BLENDING_MODES, CHARACTER_SCENE_TYPES, CHARACTER_SCENE_TYPE_IDS, DPthreeCameraController 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, DPthree, DPthree3DMarkers, DPthreeCameraController, DPthreeHTMLMarkers, DPthreeMarkers, Embody, DPthree3DMarkers as Markers3D, DPthreeHTMLMarkers as MarkersHTML, MorphVertexPreview, RUNTIME_ANNOTATION_REGION_PREFIX, RustAnnotationCameraCore, SkeletonOverlay, THREE_BLENDING_MODES, ThreeFrameApplier, ThreeModelInspector, ThreeNativePlaybackConnector, analyzeModel, applyAUBoneBindingUpdate, applyBilateralAxisBindingUpdate, applyBoneAxisBindingUpdate, applyCharacterModelTransform, bindModelReferencePose, boneResolutionProfile, buildBoneAuOptions, buildMappedBoneInfo, buildMorphVertexPreviewRenderData, buildMorphVertexPreviewSample, buildRegionDisplayOptions, captureModelReferencePose, classifyAuAsJointControl, collectMorphMeshes, collectSkeletonBones, computeHumanoidSkeletonTemplateRestBounds, createAnimationClipFromClipIR, createBilateralBoneAxisAu, createBoneAxisAu, createCharacterHost, createDefaultCharacterLighting, createDefaultCharacterScene, createDefaultCharacterSceneAsync, createDefaultCharacterSceneRuntime, createMarkerLifecycleRevealOptions, createMarkerVisibilityLifecycle, createRuntimeAnnotationPreviewLifecycle, createRustAnnotationCameraCore, createRustEmbodyHost, createRustEmbodyHostUnbound, createScenePlaybackFrame, createShadowPlane, createValidationSkeletonFromHumanoidTemplate, detectAnnotationLaterality, disposeCharacterModel, ensureBilateralBoneNodeKeys, ensureBoneNodeKey, extendCharacterConfigWithPreset, extendModelReferencePose, extractHumanoidSkeletonTemplateFromModel, extractModelData, extractProfileOverrides, filterCameraViewRegions, findModelRoot, findNodeKeyForBone, formatAxisDirectionLabel, formatAxisLabel, fuzzyNameMatch, getAUBoneBindingState, getAxisFromChannel, getBilateralAxisBindingState, getBoneAxisBindingState, getDefaultAnnotationLaterality, getHumanoidSkeletonTemplate, getHumanoidSkeletonTemplateBoneNames, getHumanoidSpecification, getMeshNamesForAUProfile, getModelLocalOrbitAngle, getPreset, getRuntimeAUMeshSideOffset, getRuntimeAUMorphAnchorPoint, getRuntimeAnnotationSide, getSemanticHorizontalSign, getSemanticHorizontalSignForSide, getWorldDirectionForCameraAngle, hasLeftRightMorphs, inferChiralBoneNamePair, inferEyeControlFamily, inferEyeControlScope, inferRuntimeAnnotationPreviewSide, isJointControlAuInfo, isMaxDegreesOnlyAxisBindingUpdate, isMixedAU, isRuntimeAnnotationRegionName, loadCharacterModel, mergeCharacterRegionsByName, normalizeDefaultCharacterLightingSettings, normalizeHairColorAppearance, parseCharacterModel, passesMarkerCameraAngleGate, pickContinuumAnnotationAuId, renderScenePlayback, resolveAnnotationCharacterConfig, resolveBoneAxisChannel, resolveBoneNameForNodeKey, resolveBoneNames, resolveContinuumDisplayLabel, resolveFaceCenter, resolveHumanoidCharacterization, resolveMarkerAnchorRegion, resolveMorphPreviewMeshes, resolveRegionCameraAngle, resolveRegionVisibilityCameraAngle, resolveSkeletonOverlayStartBoneName, resolveVisemeProfile, serializeAnimationClips, setAUMorphTargets, setHumanoidRoleBinding, shouldProjectMarkerAnchorToSurface, shouldUseFaceCenterForMarkerAnchor, stripConfiguredBoneAffixes, supportsRustEmbodyPreset, toWorldDirection, validateHumanoidCharacterization, validateMappings };
|
|
9988
11630
|
//# sourceMappingURL=index.js.map
|
|
9989
11631
|
//# sourceMappingURL=index.js.map
|