@omote/babylon 0.2.0 → 0.3.2
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 +23 -0
- package/dist/index.cjs +224 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -31
- package/dist/index.d.ts +86 -31
- package/dist/index.js +232 -43
- package/dist/index.js.map +1 -1
- package/package.json +51 -45
package/README.md
CHANGED
|
@@ -53,6 +53,29 @@ Full-featured avatar class with CharacterController (compositor + gaze + life la
|
|
|
53
53
|
|
|
54
54
|
**Accessors:** `compositor`, `parts`, `hasMorphTargets`, `mappedBlendshapeCount`
|
|
55
55
|
|
|
56
|
+
### Voice Integration
|
|
57
|
+
|
|
58
|
+
`connectVoice()` combines speaker + listener + interruption handling:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { OmoteAvatar } from '@omote/babylon';
|
|
62
|
+
import { KokoroTTSInference } from '@omote/core';
|
|
63
|
+
|
|
64
|
+
const avatar = new OmoteAvatar({ target: rootMesh, scene });
|
|
65
|
+
await avatar.connectVoice({
|
|
66
|
+
tts: new KokoroTTSInference({ defaultVoice: 'af_heart' }),
|
|
67
|
+
interruptionEnabled: true,
|
|
68
|
+
onTranscript: async (text) => {
|
|
69
|
+
const res = await fetch('/api/chat', { body: text });
|
|
70
|
+
return await res.text();
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Or use individual APIs:
|
|
75
|
+
await avatar.connectSpeaker(ttsBackend, { lam: createA2E() });
|
|
76
|
+
await avatar.speak("Hello!");
|
|
77
|
+
```
|
|
78
|
+
|
|
56
79
|
## License
|
|
57
80
|
|
|
58
81
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -21,7 +21,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
BlendshapeController: () => BlendshapeController,
|
|
24
|
-
OmoteA2E: () => OmoteA2E,
|
|
25
24
|
OmoteAvatar: () => OmoteAvatar,
|
|
26
25
|
discoverScene: () => discoverScene,
|
|
27
26
|
writeBlendshapes: () => writeBlendshapes
|
|
@@ -146,7 +145,7 @@ function writeBlendshapes(blendshapes, morphEntries) {
|
|
|
146
145
|
}
|
|
147
146
|
|
|
148
147
|
// src/OmoteAvatar.ts
|
|
149
|
-
var logger2 = (0, import_core2.createLogger)("OmoteAvatar");
|
|
148
|
+
var logger2 = (0, import_core2.createLogger)("OmoteAvatar.Babylon");
|
|
150
149
|
var OmoteAvatar = class {
|
|
151
150
|
constructor(options) {
|
|
152
151
|
// State
|
|
@@ -159,6 +158,12 @@ var OmoteAvatar = class {
|
|
|
159
158
|
// Frame source connection
|
|
160
159
|
this.frameSourceCallback = null;
|
|
161
160
|
this.connectedSource = null;
|
|
161
|
+
// TTS integration
|
|
162
|
+
this.ttsSpeaker = null;
|
|
163
|
+
// Speech listener
|
|
164
|
+
this.speechListener = null;
|
|
165
|
+
// Voice orchestrator
|
|
166
|
+
this.voiceOrchestrator = null;
|
|
162
167
|
// Auto-update
|
|
163
168
|
this.renderCallback = null;
|
|
164
169
|
this.lastTime = 0;
|
|
@@ -237,9 +242,15 @@ var OmoteAvatar = class {
|
|
|
237
242
|
* Only one source can be connected at a time; calling again disconnects the previous.
|
|
238
243
|
*/
|
|
239
244
|
connectFrameSource(source) {
|
|
245
|
+
if (this.ttsSpeaker && source !== this.ttsSpeaker.frameSource) {
|
|
246
|
+
this.ttsSpeaker.stop();
|
|
247
|
+
}
|
|
240
248
|
this.disconnectFrameSource();
|
|
241
249
|
this.frameSourceCallback = (frame) => {
|
|
242
250
|
this.currentBlendshapes = frame.blendshapes;
|
|
251
|
+
if (frame.emotion !== void 0) {
|
|
252
|
+
this._emotion = frame.emotion;
|
|
253
|
+
}
|
|
243
254
|
};
|
|
244
255
|
source.on("frame", this.frameSourceCallback);
|
|
245
256
|
this.connectedSource = source;
|
|
@@ -255,6 +266,186 @@ var OmoteAvatar = class {
|
|
|
255
266
|
this.frameSourceCallback = null;
|
|
256
267
|
}
|
|
257
268
|
// ---------------------------------------------------------------------------
|
|
269
|
+
// Speaker (TTS → lip sync)
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
/**
|
|
272
|
+
* Connect a TTS backend for speak() / streamText() support.
|
|
273
|
+
* Loads LAM model and creates internal PlaybackPipeline.
|
|
274
|
+
*
|
|
275
|
+
* @param tts - TTS backend (e.g., KokoroTTSInference, ElevenLabs adapter)
|
|
276
|
+
* @param config - A2E, expression profile, and playback configuration
|
|
277
|
+
*/
|
|
278
|
+
/** Warm up AudioContext for iOS/Safari autoplay policy. Call from user gesture. */
|
|
279
|
+
async warmup() {
|
|
280
|
+
if (this.ttsSpeaker) await this.ttsSpeaker.warmup();
|
|
281
|
+
}
|
|
282
|
+
async connectSpeaker(tts, config) {
|
|
283
|
+
await this.disconnectSpeaker();
|
|
284
|
+
this.ttsSpeaker = new import_core2.TTSSpeaker();
|
|
285
|
+
await this.ttsSpeaker.connect(tts, config);
|
|
286
|
+
this.connectFrameSource(this.ttsSpeaker.frameSource);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Synthesize text and play with lip sync.
|
|
290
|
+
* Auto-aborts previous speak if still in progress.
|
|
291
|
+
*
|
|
292
|
+
* @param text - Text to synthesize
|
|
293
|
+
* @param options - Optional voice override and abort signal
|
|
294
|
+
*/
|
|
295
|
+
async speak(text, options) {
|
|
296
|
+
if (this.voiceOrchestrator) {
|
|
297
|
+
await this.voiceOrchestrator.speak(text, options);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (!this.ttsSpeaker) {
|
|
301
|
+
throw new Error("No speaker connected. Call connectSpeaker() first.");
|
|
302
|
+
}
|
|
303
|
+
this._isSpeaking = true;
|
|
304
|
+
this._state = "speaking";
|
|
305
|
+
try {
|
|
306
|
+
await this.ttsSpeaker.speak(text, options);
|
|
307
|
+
} finally {
|
|
308
|
+
this._isSpeaking = false;
|
|
309
|
+
if (this._state === "speaking") {
|
|
310
|
+
this._state = "idle";
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Stream LLM tokens with sentence-buffered TTS + lip sync.
|
|
316
|
+
* Returns a sink: call push(token) for each token, end() when done.
|
|
317
|
+
*/
|
|
318
|
+
async streamText(options) {
|
|
319
|
+
if (this.voiceOrchestrator) {
|
|
320
|
+
return this.voiceOrchestrator.streamText(options);
|
|
321
|
+
}
|
|
322
|
+
if (!this.ttsSpeaker) {
|
|
323
|
+
throw new Error("No speaker connected. Call connectSpeaker() first.");
|
|
324
|
+
}
|
|
325
|
+
this._isSpeaking = true;
|
|
326
|
+
this._state = "speaking";
|
|
327
|
+
const stream = await this.ttsSpeaker.streamText(options ?? {});
|
|
328
|
+
return {
|
|
329
|
+
push: stream.push,
|
|
330
|
+
end: async () => {
|
|
331
|
+
try {
|
|
332
|
+
await stream.end();
|
|
333
|
+
} finally {
|
|
334
|
+
this._isSpeaking = false;
|
|
335
|
+
if (this._state === "speaking") this._state = "idle";
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
/** Stop current TTS playback. */
|
|
341
|
+
stopSpeaking() {
|
|
342
|
+
if (this.voiceOrchestrator) {
|
|
343
|
+
this.voiceOrchestrator.stopSpeaking();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
this.ttsSpeaker?.stop();
|
|
347
|
+
}
|
|
348
|
+
/** Disconnect speaker and dispose its resources. */
|
|
349
|
+
async disconnectSpeaker() {
|
|
350
|
+
if (this.ttsSpeaker) {
|
|
351
|
+
this.disconnectFrameSource();
|
|
352
|
+
await this.ttsSpeaker.dispose();
|
|
353
|
+
this.ttsSpeaker = null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/** @deprecated Use connectSpeaker(). Will be removed in v1.0. */
|
|
357
|
+
async connectTTS(tts, config) {
|
|
358
|
+
return this.connectSpeaker(tts, config);
|
|
359
|
+
}
|
|
360
|
+
/** @deprecated Use disconnectSpeaker(). Will be removed in v1.0. */
|
|
361
|
+
async disconnectTTS() {
|
|
362
|
+
return this.disconnectSpeaker();
|
|
363
|
+
}
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
// Listener (mic → VAD → ASR → transcript)
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
/**
|
|
368
|
+
* Connect a speech listener for startListening() / onTranscript() support.
|
|
369
|
+
* Loads ASR + VAD models.
|
|
370
|
+
*/
|
|
371
|
+
async connectListener(config) {
|
|
372
|
+
await this.disconnectListener();
|
|
373
|
+
this.speechListener = new import_core2.SpeechListener(config);
|
|
374
|
+
await this.speechListener.loadModels();
|
|
375
|
+
}
|
|
376
|
+
/** Start listening for user speech. Requires connectListener() or connectVoice() first. */
|
|
377
|
+
async startListening() {
|
|
378
|
+
if (this.voiceOrchestrator) {
|
|
379
|
+
await this.voiceOrchestrator.startListening();
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (!this.speechListener) {
|
|
383
|
+
throw new Error("No listener connected. Call connectListener() first.");
|
|
384
|
+
}
|
|
385
|
+
this._state = "listening";
|
|
386
|
+
await this.speechListener.start();
|
|
387
|
+
}
|
|
388
|
+
/** Stop listening. */
|
|
389
|
+
stopListening() {
|
|
390
|
+
if (this.voiceOrchestrator) {
|
|
391
|
+
this.voiceOrchestrator.stopListening();
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
this.speechListener?.stop();
|
|
395
|
+
if (this._state === "listening") this._state = "idle";
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Subscribe to transcript events. Returns an unsubscribe function.
|
|
399
|
+
* Requires connectListener() first.
|
|
400
|
+
*/
|
|
401
|
+
onTranscript(callback) {
|
|
402
|
+
const listener = this.speechListener ?? this.voiceOrchestrator?.listener;
|
|
403
|
+
if (!listener) {
|
|
404
|
+
throw new Error("No listener connected. Call connectListener() or connectVoice() first.");
|
|
405
|
+
}
|
|
406
|
+
listener.on("transcript", callback);
|
|
407
|
+
return () => {
|
|
408
|
+
listener.off?.("transcript", callback);
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/** Disconnect listener and dispose its resources. */
|
|
412
|
+
async disconnectListener() {
|
|
413
|
+
if (this.speechListener) {
|
|
414
|
+
await this.speechListener.dispose();
|
|
415
|
+
this.speechListener = null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// ---------------------------------------------------------------------------
|
|
419
|
+
// Voice (combined speaker + listener + interruption)
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
/**
|
|
422
|
+
* Connect voice with automatic speaker + listener + interruption wiring.
|
|
423
|
+
* Supports both local TTS (mode: 'local') and cloud TTS (mode: 'cloud').
|
|
424
|
+
* Does NOT auto-start listening — call startListening() when ready.
|
|
425
|
+
*
|
|
426
|
+
* Backward compatible: `mode` defaults to 'local' when not specified.
|
|
427
|
+
*/
|
|
428
|
+
async connectVoice(config) {
|
|
429
|
+
await this.disconnectVoice();
|
|
430
|
+
this.voiceOrchestrator = new import_core2.VoiceOrchestrator();
|
|
431
|
+
await this.voiceOrchestrator.connect(config);
|
|
432
|
+
if (this.voiceOrchestrator.frameSource) {
|
|
433
|
+
this.connectFrameSource(this.voiceOrchestrator.frameSource);
|
|
434
|
+
}
|
|
435
|
+
this.voiceOrchestrator.on("state", (state) => {
|
|
436
|
+
this._state = state;
|
|
437
|
+
this._isSpeaking = state === "speaking";
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
/** Disconnect voice (speaker + listener + interruption). */
|
|
441
|
+
async disconnectVoice() {
|
|
442
|
+
if (this.voiceOrchestrator) {
|
|
443
|
+
this.disconnectFrameSource();
|
|
444
|
+
await this.voiceOrchestrator.disconnect();
|
|
445
|
+
this.voiceOrchestrator = null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// ---------------------------------------------------------------------------
|
|
258
449
|
// State setters
|
|
259
450
|
// ---------------------------------------------------------------------------
|
|
260
451
|
/** Set blendshapes directly (alternative to connectFrameSource). */
|
|
@@ -277,6 +468,10 @@ var OmoteAvatar = class {
|
|
|
277
468
|
setAudioEnergy(energy) {
|
|
278
469
|
this._audioEnergy = energy;
|
|
279
470
|
}
|
|
471
|
+
/** Update character expression profile at runtime. */
|
|
472
|
+
setProfile(profile) {
|
|
473
|
+
this.controller.setProfile(profile);
|
|
474
|
+
}
|
|
280
475
|
/**
|
|
281
476
|
* Set the active camera for gaze tracking.
|
|
282
477
|
* Required when using autoUpdate. Can also be passed directly to update().
|
|
@@ -303,6 +498,26 @@ var OmoteAvatar = class {
|
|
|
303
498
|
get mappedBlendshapeCount() {
|
|
304
499
|
return this.discovery.mappedBlendshapeCount;
|
|
305
500
|
}
|
|
501
|
+
/** Whether the avatar is currently speaking via TTS. */
|
|
502
|
+
get isSpeaking() {
|
|
503
|
+
return this._isSpeaking;
|
|
504
|
+
}
|
|
505
|
+
/** Whether the avatar is currently listening for speech. */
|
|
506
|
+
get isListening() {
|
|
507
|
+
return this._state === "listening";
|
|
508
|
+
}
|
|
509
|
+
/** Current conversational state. */
|
|
510
|
+
get conversationalState() {
|
|
511
|
+
return this._state;
|
|
512
|
+
}
|
|
513
|
+
/** Access the internal TTSSpeaker (null if not connected). */
|
|
514
|
+
get speaker() {
|
|
515
|
+
return this.ttsSpeaker ?? this.voiceOrchestrator?.speaker ?? null;
|
|
516
|
+
}
|
|
517
|
+
/** Access the internal SpeechListener (null if not connected). */
|
|
518
|
+
get listener() {
|
|
519
|
+
return this.speechListener ?? this.voiceOrchestrator?.listener ?? null;
|
|
520
|
+
}
|
|
306
521
|
// ---------------------------------------------------------------------------
|
|
307
522
|
// Lifecycle
|
|
308
523
|
// ---------------------------------------------------------------------------
|
|
@@ -315,8 +530,11 @@ var OmoteAvatar = class {
|
|
|
315
530
|
this._audioEnergy = 0;
|
|
316
531
|
this.controller.reset();
|
|
317
532
|
}
|
|
318
|
-
/**
|
|
319
|
-
dispose() {
|
|
533
|
+
/** Disconnect all voice resources, frame sources, unregister render loop, dispose controller. */
|
|
534
|
+
async dispose() {
|
|
535
|
+
await this.disconnectVoice();
|
|
536
|
+
await this.disconnectSpeaker();
|
|
537
|
+
await this.disconnectListener();
|
|
320
538
|
this.disconnectFrameSource();
|
|
321
539
|
if (this.renderCallback) {
|
|
322
540
|
this.scene.unregisterBeforeRender(this.renderCallback);
|
|
@@ -329,9 +547,9 @@ var OmoteAvatar = class {
|
|
|
329
547
|
// Internal
|
|
330
548
|
// ---------------------------------------------------------------------------
|
|
331
549
|
registerAutoUpdate() {
|
|
332
|
-
this.lastTime =
|
|
550
|
+
this.lastTime = (0, import_core2.getClock)().now();
|
|
333
551
|
this.renderCallback = () => {
|
|
334
|
-
const now =
|
|
552
|
+
const now = (0, import_core2.getClock)().now();
|
|
335
553
|
const delta = (now - this.lastTime) / 1e3;
|
|
336
554
|
this.lastTime = now;
|
|
337
555
|
if (this._camera) {
|
|
@@ -449,40 +667,4 @@ var BlendshapeController = class {
|
|
|
449
667
|
this.scene = null;
|
|
450
668
|
}
|
|
451
669
|
};
|
|
452
|
-
|
|
453
|
-
// src/OmoteA2E.ts
|
|
454
|
-
var import_core4 = require("@omote/core");
|
|
455
|
-
var OmoteA2E = class {
|
|
456
|
-
constructor(options) {
|
|
457
|
-
const { target, scene, controllerOptions, ...orchestratorConfig } = options;
|
|
458
|
-
this.controller = new BlendshapeController(target, scene, controllerOptions);
|
|
459
|
-
this.orchestrator = new import_core4.A2EOrchestrator(orchestratorConfig);
|
|
460
|
-
}
|
|
461
|
-
async load() {
|
|
462
|
-
return this.orchestrator.load();
|
|
463
|
-
}
|
|
464
|
-
async start() {
|
|
465
|
-
return this.orchestrator.start();
|
|
466
|
-
}
|
|
467
|
-
stop() {
|
|
468
|
-
this.orchestrator.stop();
|
|
469
|
-
}
|
|
470
|
-
update() {
|
|
471
|
-
const w = this.orchestrator.latestWeights;
|
|
472
|
-
if (w) this.controller.update(w);
|
|
473
|
-
}
|
|
474
|
-
async dispose() {
|
|
475
|
-
await this.orchestrator.dispose();
|
|
476
|
-
this.controller.dispose();
|
|
477
|
-
}
|
|
478
|
-
get isReady() {
|
|
479
|
-
return this.orchestrator.isReady;
|
|
480
|
-
}
|
|
481
|
-
get isStreaming() {
|
|
482
|
-
return this.orchestrator.isStreaming;
|
|
483
|
-
}
|
|
484
|
-
get backend() {
|
|
485
|
-
return this.orchestrator.backend;
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
670
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/OmoteAvatar.ts","../src/SceneDiscovery.ts","../src/BlendshapeWriter.ts","../src/BlendshapeController.ts","../src/OmoteA2E.ts"],"sourcesContent":["// High-level\nexport { OmoteAvatar } from './OmoteAvatar';\nexport type { OmoteAvatarOptions, FrameSource } from './OmoteAvatar';\n\n// Scene utilities\nexport { discoverScene } from './SceneDiscovery';\nexport type { SceneDiscoveryResult, MorphIndexEntry } from './SceneDiscovery';\nexport { writeBlendshapes } from './BlendshapeWriter';\n\n// Low-level escape hatch\nexport { BlendshapeController } from './BlendshapeController';\nexport type { BlendshapeControllerOptions } from './BlendshapeController';\n\n/** @deprecated Use OmoteAvatar instead */\nexport { OmoteA2E } from './OmoteA2E';\nexport type { OmoteA2EOptions } from './OmoteA2E';\n","/**\n * OmoteAvatar — Full-featured Babylon.js avatar controller.\n *\n * Owns CharacterController, SceneDiscovery, and BlendshapeWriter.\n * Mirrors the Three.js OmoteAvatar API for consistency across adapters.\n *\n * Usage:\n * ```typescript\n * import { OmoteAvatar } from '@omote/babylon';\n * import { createA2E, PlaybackPipeline } from '@omote/core';\n *\n * const avatar = new OmoteAvatar({ target: mesh, scene });\n * avatar.setCamera(camera);\n *\n * // Connect a frame source (PlaybackPipeline, MicLipSync, etc.)\n * const pipeline = new PlaybackPipeline({ lam: createA2E() });\n * avatar.connectFrameSource(pipeline);\n *\n * // Option A: manual update in render loop\n * scene.registerBeforeRender(() => avatar.update(engine.getDeltaTime() / 1000, camera));\n *\n * // Option B: autoUpdate (registers scene.registerBeforeRender internally)\n * const avatar = new OmoteAvatar({ target: mesh, scene, autoUpdate: true });\n * avatar.setCamera(camera);\n * ```\n *\n * @category Babylon\n */\n\nimport { CharacterController, createLogger } from '@omote/core';\n\nconst logger = createLogger('OmoteAvatar');\nimport type {\n CharacterControllerConfig,\n EmotionWeights,\n ConversationalState,\n FaceCompositorConfig,\n} from '@omote/core';\nimport type { AbstractMesh, Scene, Camera } from '@babylonjs/core';\n\nimport { discoverScene, type SceneDiscoveryResult } from './SceneDiscovery';\nimport { writeBlendshapes } from './BlendshapeWriter';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Generic frame source -- any object that emits 'frame' events */\nexport interface FrameSource {\n on(event: 'frame', callback: (frame: { blendshapes: Float32Array }) => void): void;\n off?(event: 'frame', callback: (...args: any[]) => void): void;\n}\n\nexport interface OmoteAvatarOptions {\n /** Root mesh of the avatar (typically loaded via SceneLoader) */\n target: AbstractMesh;\n /** Babylon.js scene */\n scene: Scene;\n /** FaceCompositor configuration */\n compositor?: FaceCompositorConfig;\n /** Gaze tracking configuration */\n gaze?: CharacterControllerConfig['gaze'];\n /**\n * Register scene.registerBeforeRender() for automatic update.\n * Requires setCamera() to be called before the first frame.\n * Default: false\n */\n autoUpdate?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// OmoteAvatar\n// ---------------------------------------------------------------------------\n\nexport class OmoteAvatar {\n private readonly controller: CharacterController;\n private readonly discovery: SceneDiscoveryResult;\n private readonly scene: Scene;\n\n // State\n private currentBlendshapes: Float32Array | null = null;\n private _emotion: string | EmotionWeights | null = null;\n private _isSpeaking = false;\n private _state: ConversationalState = 'idle';\n private _audioEnergy = 0;\n private _camera: Camera | null = null;\n\n // Frame source connection\n private frameSourceCallback: ((frame: { blendshapes: Float32Array }) => void) | null = null;\n private connectedSource: FrameSource | null = null;\n\n // Auto-update\n private renderCallback: (() => void) | null = null;\n private lastTime = 0;\n\n constructor(options: OmoteAvatarOptions) {\n this.scene = options.scene;\n this.discovery = discoverScene(options.target);\n this.controller = new CharacterController({\n compositor: options.compositor,\n gaze: options.gaze,\n });\n\n if (this.discovery.morphEntries.length === 0) {\n logger.warn('No morph targets found — blendshape animation will have no effect');\n }\n if (!this.discovery.headBone) {\n logger.warn('Head bone not found — gaze tracking will be disabled');\n }\n logger.info(\n `Initialized: ${this.discovery.meshes.length} mesh(es), ${this.discovery.mappedBlendshapeCount} mapped blendshapes, headBone=${!!this.discovery.headBone}`,\n );\n\n if (options.autoUpdate) {\n this.registerAutoUpdate();\n }\n }\n\n // ---------------------------------------------------------------------------\n // Frame update\n // ---------------------------------------------------------------------------\n\n /**\n * Call each frame with delta time and camera.\n *\n * Runs CharacterController (compositor, gaze, procedural life) then writes\n * blendshapes to morph targets and applies head rotation.\n *\n * If using autoUpdate, this is called automatically via scene.registerBeforeRender().\n *\n * @param delta - Time since last frame in seconds\n * @param camera - Active Babylon.js camera (for gaze tracking)\n * @param avatarRotationY - Optional avatar Y rotation in radians for gaze compensation\n */\n update(delta: number, camera: Camera, avatarRotationY?: number): void {\n // Get camera world position (globalPosition handles parented cameras)\n const camPos = camera.globalPosition;\n const cameraWorldPos = { x: camPos.x, y: camPos.y, z: camPos.z };\n\n // Get head bone world position + quaternion (if available)\n let headWorldPos: { x: number; y: number; z: number } | undefined;\n let headWorldQuat: { x: number; y: number; z: number; w: number } | undefined;\n if (this.discovery.headBone) {\n const wp = this.discovery.headBone.getAbsolutePosition();\n headWorldPos = { x: wp.x, y: wp.y, z: wp.z };\n const rq = this.discovery.headBone.absoluteRotationQuaternion;\n if (rq) {\n headWorldQuat = { x: rq.x, y: rq.y, z: rq.z, w: rq.w };\n }\n }\n\n // Run CharacterController (compositor + gaze + life layer)\n const output = this.controller.update({\n deltaTime: delta,\n baseBlendshapes: this.currentBlendshapes,\n emotion: this._emotion,\n isSpeaking: this._isSpeaking,\n state: this._state,\n audioEnergy: this._audioEnergy,\n cameraWorldPos,\n headWorldPos,\n headWorldQuat,\n avatarRotationY: avatarRotationY ?? 0,\n });\n\n // Write blendshapes to morph targets\n writeBlendshapes(output.blendshapes, this.discovery.morphEntries);\n\n // Apply head rotation delta\n if (this.discovery.headBone) {\n this.discovery.headBone.rotation.y = output.headDelta.yaw;\n this.discovery.headBone.rotation.x = output.headDelta.pitch;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Frame source connection\n // ---------------------------------------------------------------------------\n\n /**\n * Connect a frame source (PlaybackPipeline, MicLipSync, etc.).\n *\n * Automatically listens for 'frame' events and stores the latest blendshapes.\n * Only one source can be connected at a time; calling again disconnects the previous.\n */\n connectFrameSource(source: FrameSource): void {\n this.disconnectFrameSource();\n this.frameSourceCallback = (frame) => {\n this.currentBlendshapes = frame.blendshapes;\n };\n source.on('frame', this.frameSourceCallback);\n this.connectedSource = source;\n logger.debug('Frame source connected');\n }\n\n /** Disconnect the current frame source (if any). */\n disconnectFrameSource(): void {\n if (this.connectedSource && this.frameSourceCallback) {\n this.connectedSource.off?.('frame', this.frameSourceCallback);\n logger.debug('Frame source disconnected');\n }\n this.connectedSource = null;\n this.frameSourceCallback = null;\n }\n\n // ---------------------------------------------------------------------------\n // State setters\n // ---------------------------------------------------------------------------\n\n /** Set blendshapes directly (alternative to connectFrameSource). */\n setFrame(blendshapes: Float32Array): void {\n this.currentBlendshapes = blendshapes;\n }\n\n /** Set emotion (string preset like 'happy' or EmotionWeights object). */\n setEmotion(emotion: string | EmotionWeights): void {\n this._emotion = emotion;\n }\n\n /** Set whether the avatar is currently speaking (drives mouth emphasis). */\n setSpeaking(speaking: boolean): void {\n this._isSpeaking = speaking;\n }\n\n /** Set conversational state (idle, listening, thinking, speaking). */\n setState(state: ConversationalState): void {\n this._state = state;\n }\n\n /** Set audio energy level (0-1, drives emphasis/gesture intensity). */\n setAudioEnergy(energy: number): void {\n this._audioEnergy = energy;\n }\n\n /**\n * Set the active camera for gaze tracking.\n * Required when using autoUpdate. Can also be passed directly to update().\n */\n setCamera(camera: Camera): void {\n this._camera = camera;\n }\n\n // ---------------------------------------------------------------------------\n // Accessors\n // ---------------------------------------------------------------------------\n\n /** Access underlying FaceCompositor for advanced use. */\n get compositor() {\n return this.controller.compositor;\n }\n\n /** Access SceneDiscoveryResult (meshes, bones, morph entries). */\n get parts(): SceneDiscoveryResult {\n return this.discovery;\n }\n\n /** Whether the scene has any mapped morph targets. */\n get hasMorphTargets(): boolean {\n return this.discovery.morphEntries.length > 0;\n }\n\n /** Number of successfully mapped ARKit blendshapes. */\n get mappedBlendshapeCount(): number {\n return this.discovery.mappedBlendshapeCount;\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------\n\n /** Reset all state (smoothing, life layer, emotions). */\n reset(): void {\n this.currentBlendshapes = null;\n this._emotion = null;\n this._isSpeaking = false;\n this._state = 'idle';\n this._audioEnergy = 0;\n this.controller.reset();\n }\n\n /** Clean up all resources: disconnect frame source, unregister render loop, dispose controller. */\n dispose(): void {\n this.disconnectFrameSource();\n if (this.renderCallback) {\n this.scene.unregisterBeforeRender(this.renderCallback);\n this.renderCallback = null;\n }\n this.controller.dispose();\n logger.debug('Disposed');\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private registerAutoUpdate(): void {\n this.lastTime = performance.now();\n this.renderCallback = () => {\n const now = performance.now();\n const delta = (now - this.lastTime) / 1000;\n this.lastTime = now;\n if (this._camera) {\n this.update(delta, this._camera);\n }\n };\n this.scene.registerBeforeRender(this.renderCallback);\n }\n}\n","/**\n * SceneDiscovery — Discover avatar structure from a Babylon.js scene graph.\n *\n * Finds morph targets, head/neck/eye bones, and pre-computes a fast index\n * mapping from LAM_BLENDSHAPES order to per-mesh morph target indices.\n *\n * @category Babylon\n */\n\nimport { LAM_BLENDSHAPES, createLogger } from '@omote/core';\n\nconst logger = createLogger('SceneDiscovery');\nimport type { AbstractMesh, TransformNode } from '@babylonjs/core';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MorphIndexEntry {\n mesh: AbstractMesh;\n /** indices[lamIndex] = morphTargetIndex (or -1 if not found) */\n indices: Int16Array;\n}\n\nexport interface SceneDiscoveryResult {\n /** All meshes under the root that have a MorphTargetManager */\n meshes: AbstractMesh[];\n /** Head bone (TransformNode named 'Head') or null */\n headBone: TransformNode | null;\n /** Neck bone (TransformNode named 'Neck') or null */\n neckBone: TransformNode | null;\n /** Left eye bone (TransformNode named 'LeftEye') or null */\n leftEyeBone: TransformNode | null;\n /** Right eye bone (TransformNode named 'RightEye') or null */\n rightEyeBone: TransformNode | null;\n /** Pre-computed morph index mapping per mesh */\n morphEntries: MorphIndexEntry[];\n /** Primary face mesh (prefers 'Head_Mesh', falls back to first with morph targets) */\n faceMesh: AbstractMesh | null;\n /** Total number of ARKit blendshapes successfully mapped across all meshes */\n mappedBlendshapeCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Normalize morph target name by stripping common prefixes */\nfunction normalizeName(name: string): string {\n return name.replace(/^morph_/i, '').replace(/^blendshape\\./i, '');\n}\n\n/** Case-insensitive bone name matching */\nconst BONE_PATTERNS: Record<string, RegExp> = {\n head: /^head$/i,\n neck: /^neck$/i,\n leftEye: /^lefteye$/i,\n rightEye: /^righteye$/i,\n};\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Discover avatar structure from a Babylon.js root node.\n *\n * Traverses the scene graph under `root` to find:\n * - Meshes with MorphTargetManagers\n * - Head, neck, and eye bones\n * - Pre-computed LAM_BLENDSHAPES -> morph target index mapping\n *\n * @param root - The root AbstractMesh (typically loaded via SceneLoader)\n * @returns SceneDiscoveryResult with all discovered components\n */\nexport function discoverScene(root: AbstractMesh): SceneDiscoveryResult {\n const meshes: AbstractMesh[] = [];\n const morphEntries: MorphIndexEntry[] = [];\n let faceMesh: AbstractMesh | null = null;\n let mappedBlendshapeCount = 0;\n\n // ---- Morph target discovery ----\n\n // Collect all candidate meshes (root + children)\n const candidates: AbstractMesh[] = [root];\n if (root.getChildMeshes) {\n candidates.push(...root.getChildMeshes(false));\n }\n\n for (const mesh of candidates) {\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n meshes.push(mesh);\n\n // Prefer mesh named 'Head_Mesh' as the face mesh\n if (!faceMesh || mesh.name === 'Head_Mesh') {\n faceMesh = mesh;\n }\n\n // Build normalized name -> morph target index lookup for this mesh\n const morphLookup = new Map<string, number>();\n const numTargets = manager.numTargets;\n for (let t = 0; t < numTargets; t++) {\n const morphTarget = manager.getTarget(t);\n const normalized = normalizeName(morphTarget.name);\n morphLookup.set(normalized, t);\n }\n\n // Pre-compute Int16Array mapping: lamIndex -> morphTargetIndex (or -1)\n const indices = new Int16Array(LAM_BLENDSHAPES.length);\n let meshMapped = 0;\n for (let i = 0; i < LAM_BLENDSHAPES.length; i++) {\n const morphIdx = morphLookup.get(LAM_BLENDSHAPES[i]);\n if (morphIdx !== undefined) {\n indices[i] = morphIdx;\n meshMapped++;\n } else {\n indices[i] = -1;\n }\n }\n\n if (meshMapped > 0) {\n morphEntries.push({ mesh, indices });\n }\n mappedBlendshapeCount = Math.max(mappedBlendshapeCount, meshMapped);\n\n logger.debug(`Mesh \"${mesh.name}\": ${meshMapped}/${LAM_BLENDSHAPES.length} blendshapes mapped`);\n }\n\n // ---- Bone discovery ----\n\n let headBone: TransformNode | null = null;\n let neckBone: TransformNode | null = null;\n let leftEyeBone: TransformNode | null = null;\n let rightEyeBone: TransformNode | null = null;\n\n // getChildTransformNodes is on TransformNode (AbstractMesh extends TransformNode)\n const transformNodes = root.getChildTransformNodes(false);\n\n for (const node of transformNodes) {\n if (!headBone && BONE_PATTERNS.head.test(node.name)) {\n headBone = node;\n } else if (!neckBone && BONE_PATTERNS.neck.test(node.name)) {\n neckBone = node;\n } else if (!leftEyeBone && BONE_PATTERNS.leftEye.test(node.name)) {\n leftEyeBone = node;\n } else if (!rightEyeBone && BONE_PATTERNS.rightEye.test(node.name)) {\n rightEyeBone = node;\n }\n\n // Early exit if all bones found\n if (headBone && neckBone && leftEyeBone && rightEyeBone) break;\n }\n\n if (morphEntries.length === 0) {\n logger.warn('No morph targets found in scene');\n }\n if (!headBone) {\n logger.warn('Head bone not found in scene');\n }\n\n const boneNames = [\n headBone && 'Head',\n neckBone && 'Neck',\n leftEyeBone && 'LeftEye',\n rightEyeBone && 'RightEye',\n ].filter(Boolean);\n\n logger.info(\n `Discovery complete: ${meshes.length} mesh(es), ${mappedBlendshapeCount} mapped blendshapes, bones: [${boneNames.join(', ')}]`,\n );\n\n return {\n meshes,\n headBone,\n neckBone,\n leftEyeBone,\n rightEyeBone,\n morphEntries,\n faceMesh,\n mappedBlendshapeCount,\n };\n}\n","/**\n * BlendshapeWriter — Zero-lookup hot-path blendshape application.\n *\n * Uses pre-computed Int16Array indices from SceneDiscovery for O(1) lookups\n * per blendshape per mesh. No Map.get(), no string comparison on the hot path.\n *\n * @category Babylon\n */\n\nimport type { MorphIndexEntry } from './SceneDiscovery';\n\n/**\n * Write 52 ARKit blendshapes to Babylon.js morph targets.\n *\n * Uses pre-computed indices for zero-lookup hot path.\n * Call this every frame after CharacterController.update().\n *\n * @param blendshapes - 52-element Float32Array in LAM_BLENDSHAPES order\n * @param morphEntries - Pre-computed index mappings from discoverScene()\n */\nexport function writeBlendshapes(\n blendshapes: Float32Array,\n morphEntries: MorphIndexEntry[],\n): void {\n for (let e = 0; e < morphEntries.length; e++) {\n const { mesh, indices } = morphEntries[e];\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n for (let i = 0; i < 52; i++) {\n const morphIdx = indices[i];\n if (morphIdx >= 0) {\n const target = manager.getTarget(morphIdx);\n if (target) {\n target.influence = blendshapes[i];\n }\n }\n }\n }\n}\n","import { LAM_BLENDSHAPES, lerpBlendshapes } from '@omote/core';\nimport type { AbstractMesh, Scene } from '@babylonjs/core';\n\nexport interface BlendshapeControllerOptions {\n /** Blendshape names in order (default: LAM_BLENDSHAPES, 52 ARKit) */\n names?: readonly string[];\n /** Smoothing factor 0-1 (0 = no change, 1 = snap to target). Default: 0.7 */\n smoothing?: number;\n /** Traverse target for child meshes with MorphTargetManager automatically. Default: true */\n autoFind?: boolean;\n /** Register scene.registerBeforeRender() callback automatically. Default: false */\n autoRegister?: boolean;\n /** Called when meshes with morph targets are found */\n onMeshesFound?: (meshes: AbstractMesh[]) => void;\n}\n\nexport class BlendshapeController {\n private _meshes: AbstractMesh[] = [];\n private nameToIndex: Map<string, number>[] = [];\n private currentWeights: number[] = [];\n private blendshapeNames: readonly string[];\n private smoothing: number;\n private scene: Scene | null;\n private renderObserver: (() => void) | null = null;\n private pendingWeights: Float32Array | number[] | null = null;\n private onMeshesFound?: (meshes: AbstractMesh[]) => void;\n\n constructor(target: AbstractMesh, scene: Scene, options?: BlendshapeControllerOptions) {\n this.blendshapeNames = options?.names ?? LAM_BLENDSHAPES;\n this.smoothing = options?.smoothing ?? 0.7;\n this.scene = scene;\n this.onMeshesFound = options?.onMeshesFound;\n\n if (options?.autoFind !== false) {\n this.setTarget(target);\n }\n\n if (options?.autoRegister) {\n this.registerRenderLoop();\n }\n }\n\n get meshes(): AbstractMesh[] {\n return this._meshes;\n }\n\n /** Normalize Babylon morph target names (strip common prefixes) */\n private normalizeName(name: string): string {\n return name.replace(/^morph_/i, '').replace(/^blendshape\\./i, '');\n }\n\n setTarget(target: AbstractMesh): void {\n this._meshes = [];\n this.nameToIndex = [];\n\n // Collect meshes with MorphTargetManager\n const candidates: AbstractMesh[] = [target];\n if (target.getChildMeshes) {\n candidates.push(...target.getChildMeshes(false));\n }\n\n for (const mesh of candidates) {\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n this._meshes.push(mesh);\n\n // Build normalized name -> morph target index lookup\n const morphLookup = new Map<string, number>();\n const numTargets = manager.numTargets;\n for (let t = 0; t < numTargets; t++) {\n const morphTarget = manager.getTarget(t);\n const normalizedName = this.normalizeName(morphTarget.name);\n morphLookup.set(normalizedName, t);\n }\n\n // Map blendshape names to morph target indices\n const map = new Map<string, number>();\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const idx = morphLookup.get(name);\n if (idx !== undefined) {\n map.set(name, idx);\n }\n }\n\n this.nameToIndex.push(map);\n }\n\n this.currentWeights = new Array(this.blendshapeNames.length).fill(0);\n\n if (this._meshes.length > 0 && this.onMeshesFound) {\n this.onMeshesFound(this._meshes);\n }\n }\n\n update(weights: Float32Array | number[]): void {\n this.currentWeights = lerpBlendshapes(this.currentWeights, weights, this.smoothing);\n\n for (let m = 0; m < this._meshes.length; m++) {\n const mesh = this._meshes[m];\n const map = this.nameToIndex[m];\n const manager = mesh.morphTargetManager;\n if (!manager || !map) continue;\n\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const morphIdx = map.get(name);\n if (morphIdx !== undefined) {\n const target = manager.getTarget(morphIdx);\n if (target) {\n target.influence = this.currentWeights[i];\n }\n }\n }\n }\n }\n\n /** Buffer weights for the next render loop tick (used with autoRegister) */\n setWeightsForNextFrame(weights: Float32Array | number[]): void {\n this.pendingWeights = weights;\n }\n\n private registerRenderLoop(): void {\n if (!this.scene) return;\n const callback = () => {\n if (this.pendingWeights) {\n this.update(this.pendingWeights);\n this.pendingWeights = null;\n }\n };\n this.scene.registerBeforeRender(callback);\n this.renderObserver = () => {\n this.scene?.unregisterBeforeRender(callback);\n };\n }\n\n dispose(): void {\n if (this.renderObserver) {\n this.renderObserver();\n this.renderObserver = null;\n }\n this._meshes = [];\n this.nameToIndex = [];\n this.currentWeights = [];\n this.scene = null;\n }\n}\n","import { A2EOrchestrator } from '@omote/core';\nimport type { A2EOrchestratorConfig } from '@omote/core';\nimport type { AbstractMesh, Scene } from '@babylonjs/core';\nimport { BlendshapeController } from './BlendshapeController';\nimport type { BlendshapeControllerOptions } from './BlendshapeController';\n\nexport interface OmoteA2EOptions extends A2EOrchestratorConfig {\n target: AbstractMesh;\n scene: Scene;\n controllerOptions?: BlendshapeControllerOptions;\n}\n\n/** @deprecated Use {@link OmoteAvatar} instead. OmoteA2E will be removed in v0.8.0. */\nexport class OmoteA2E {\n private orchestrator: A2EOrchestrator;\n private controller: BlendshapeController;\n\n constructor(options: OmoteA2EOptions) {\n const { target, scene, controllerOptions, ...orchestratorConfig } = options;\n this.controller = new BlendshapeController(target, scene, controllerOptions);\n this.orchestrator = new A2EOrchestrator(orchestratorConfig);\n }\n\n async load(): Promise<void> { return this.orchestrator.load(); }\n async start(): Promise<void> { return this.orchestrator.start(); }\n stop(): void { this.orchestrator.stop(); }\n\n update(): void {\n const w = this.orchestrator.latestWeights;\n if (w) this.controller.update(w);\n }\n\n async dispose(): Promise<void> {\n await this.orchestrator.dispose();\n this.controller.dispose();\n }\n\n get isReady(): boolean { return this.orchestrator.isReady; }\n get isStreaming(): boolean { return this.orchestrator.isStreaming; }\n get backend(): string | null { return this.orchestrator.backend; }\n}\n"],"mappings":"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","names":["import_core","logger","import_core","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/OmoteAvatar.ts","../src/SceneDiscovery.ts","../src/BlendshapeWriter.ts","../src/BlendshapeController.ts"],"sourcesContent":["// High-level\nexport { OmoteAvatar } from './OmoteAvatar';\nexport type { OmoteAvatarOptions, FrameSource } from './OmoteAvatar';\n\n// Re-export TTSSpeakerConfig as TTSConfig for convenience\nexport type { TTSSpeakerConfig as TTSConfig } from '@omote/core';\n\n// Scene utilities\nexport { discoverScene } from './SceneDiscovery';\nexport type { SceneDiscoveryResult, MorphIndexEntry } from './SceneDiscovery';\nexport { writeBlendshapes } from './BlendshapeWriter';\n\n// Low-level escape hatch\nexport { BlendshapeController } from './BlendshapeController';\nexport type { BlendshapeControllerOptions } from './BlendshapeController';\n\n","/**\r\n * OmoteAvatar — Full-featured Babylon.js avatar controller.\r\n *\r\n * Owns CharacterController, SceneDiscovery, and BlendshapeWriter.\r\n * Mirrors the Three.js OmoteAvatar API for consistency across adapters.\r\n *\r\n * Usage:\r\n * ```typescript\r\n * import { OmoteAvatar } from '@omote/babylon';\r\n * import { createA2E, PlaybackPipeline } from '@omote/core';\r\n *\r\n * const avatar = new OmoteAvatar({ target: mesh, scene });\r\n * avatar.setCamera(camera);\r\n *\r\n * // Connect a frame source (PlaybackPipeline, MicLipSync, etc.)\r\n * const pipeline = new PlaybackPipeline({ lam: createA2E() });\r\n * avatar.connectFrameSource(pipeline);\r\n *\r\n * // Option A: manual update in render loop\r\n * scene.registerBeforeRender(() => avatar.update(engine.getDeltaTime() / 1000, camera));\r\n *\r\n * // Option B: autoUpdate (registers scene.registerBeforeRender internally)\r\n * const avatar = new OmoteAvatar({ target: mesh, scene, autoUpdate: true });\r\n * avatar.setCamera(camera);\r\n * ```\r\n *\r\n * @example Speaker integration\r\n * ```typescript\r\n * const avatar = new OmoteAvatar({ target: mesh, scene });\r\n * await avatar.connectSpeaker(myTTSBackend, { profile: { mouth: 1.2 } });\r\n * await avatar.speak(\"Hello world!\"); // lip-syncs automatically\r\n * ```\r\n *\r\n * @category Babylon\r\n */\r\n\r\nimport {\r\n CharacterController,\r\n TTSSpeaker,\r\n SpeechListener,\r\n VoiceOrchestrator,\r\n createLogger,\r\n getClock,\r\n} from '@omote/core';\r\nimport type {\r\n CharacterControllerConfig,\r\n CharacterProfile,\r\n EmotionWeights,\r\n ConversationalState,\r\n FaceCompositorConfig,\r\n FrameSource,\r\n TTSSpeakerConfig,\r\n TTSBackend,\r\n SpeechListenerConfig,\r\n TranscriptResult,\r\n VoiceOrchestratorConfig,\r\n} from '@omote/core';\r\n\r\nconst logger = createLogger('OmoteAvatar.Babylon');\r\nimport type { AbstractMesh, Scene, Camera } from '@babylonjs/core';\r\n\r\nimport { discoverScene, type SceneDiscoveryResult } from './SceneDiscovery';\r\nimport { writeBlendshapes } from './BlendshapeWriter';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\n// Re-export FrameSource from @omote/core for backward compatibility\r\nexport type { FrameSource } from '@omote/core';\r\n\r\nexport interface OmoteAvatarOptions {\r\n /** Root mesh of the avatar (typically loaded via SceneLoader) */\r\n target: AbstractMesh;\r\n /** Babylon.js scene */\r\n scene: Scene;\r\n /** FaceCompositor configuration */\r\n compositor?: FaceCompositorConfig;\r\n /** Gaze tracking configuration */\r\n gaze?: CharacterControllerConfig['gaze'];\r\n /**\r\n * Register scene.registerBeforeRender() for automatic update.\r\n * Requires setCamera() to be called before the first frame.\r\n * Default: false\r\n */\r\n autoUpdate?: boolean;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// OmoteAvatar\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class OmoteAvatar {\r\n private readonly controller: CharacterController;\r\n private readonly discovery: SceneDiscoveryResult;\r\n private readonly scene: Scene;\r\n\r\n // State\r\n private currentBlendshapes: Float32Array | null = null;\r\n private _emotion: string | EmotionWeights | null = null;\r\n private _isSpeaking = false;\r\n private _state: ConversationalState = 'idle';\r\n private _audioEnergy = 0;\r\n private _camera: Camera | null = null;\r\n\r\n // Frame source connection\r\n private frameSourceCallback: ((frame: { blendshapes: Float32Array }) => void) | null = null;\r\n private connectedSource: FrameSource | null = null;\r\n\r\n // TTS integration\r\n private ttsSpeaker: TTSSpeaker | null = null;\r\n\r\n // Speech listener\r\n private speechListener: SpeechListener | null = null;\r\n\r\n // Voice orchestrator\r\n private voiceOrchestrator: VoiceOrchestrator | null = null;\r\n\r\n // Auto-update\r\n private renderCallback: (() => void) | null = null;\r\n private lastTime = 0;\r\n\r\n constructor(options: OmoteAvatarOptions) {\r\n this.scene = options.scene;\r\n this.discovery = discoverScene(options.target);\r\n this.controller = new CharacterController({\r\n compositor: options.compositor,\r\n gaze: options.gaze,\r\n });\r\n\r\n if (this.discovery.morphEntries.length === 0) {\r\n logger.warn('No morph targets found — blendshape animation will have no effect');\r\n }\r\n if (!this.discovery.headBone) {\r\n logger.warn('Head bone not found — gaze tracking will be disabled');\r\n }\r\n logger.info(\r\n `Initialized: ${this.discovery.meshes.length} mesh(es), ${this.discovery.mappedBlendshapeCount} mapped blendshapes, headBone=${!!this.discovery.headBone}`,\r\n );\r\n\r\n if (options.autoUpdate) {\r\n this.registerAutoUpdate();\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Frame update\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Call each frame with delta time and camera.\r\n *\r\n * Runs CharacterController (compositor, gaze, procedural life) then writes\r\n * blendshapes to morph targets and applies head rotation.\r\n *\r\n * If using autoUpdate, this is called automatically via scene.registerBeforeRender().\r\n *\r\n * @param delta - Time since last frame in seconds\r\n * @param camera - Active Babylon.js camera (for gaze tracking)\r\n * @param avatarRotationY - Optional avatar Y rotation in radians for gaze compensation\r\n */\r\n update(delta: number, camera: Camera, avatarRotationY?: number): void {\r\n // Get camera world position (globalPosition handles parented cameras)\r\n const camPos = camera.globalPosition;\r\n const cameraWorldPos = { x: camPos.x, y: camPos.y, z: camPos.z };\r\n\r\n // Get head bone world position + quaternion (if available)\r\n let headWorldPos: { x: number; y: number; z: number } | undefined;\r\n let headWorldQuat: { x: number; y: number; z: number; w: number } | undefined;\r\n if (this.discovery.headBone) {\r\n const wp = this.discovery.headBone.getAbsolutePosition();\r\n headWorldPos = { x: wp.x, y: wp.y, z: wp.z };\r\n const rq = this.discovery.headBone.absoluteRotationQuaternion;\r\n if (rq) {\r\n headWorldQuat = { x: rq.x, y: rq.y, z: rq.z, w: rq.w };\r\n }\r\n }\r\n\r\n // Run CharacterController (compositor + gaze + life layer)\r\n const output = this.controller.update({\r\n deltaTime: delta,\r\n baseBlendshapes: this.currentBlendshapes,\r\n emotion: this._emotion,\r\n isSpeaking: this._isSpeaking,\r\n state: this._state,\r\n audioEnergy: this._audioEnergy,\r\n cameraWorldPos,\r\n headWorldPos,\r\n headWorldQuat,\r\n avatarRotationY: avatarRotationY ?? 0,\r\n });\r\n\r\n // Write blendshapes to morph targets\r\n writeBlendshapes(output.blendshapes, this.discovery.morphEntries);\r\n\r\n // Apply head rotation delta\r\n if (this.discovery.headBone) {\r\n this.discovery.headBone.rotation.y = output.headDelta.yaw;\r\n this.discovery.headBone.rotation.x = output.headDelta.pitch;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Frame source connection\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect a frame source (PlaybackPipeline, MicLipSync, etc.).\r\n *\r\n * Automatically listens for 'frame' events and stores the latest blendshapes.\r\n * Only one source can be connected at a time; calling again disconnects the previous.\r\n */\r\n connectFrameSource(source: FrameSource): void {\r\n // If connecting a different source while TTS is active, stop current speak\r\n if (this.ttsSpeaker && source !== this.ttsSpeaker.frameSource) {\r\n this.ttsSpeaker.stop();\r\n }\r\n\r\n this.disconnectFrameSource();\r\n this.frameSourceCallback = (frame: { blendshapes: Float32Array; emotion?: string }) => {\r\n this.currentBlendshapes = frame.blendshapes;\r\n if (frame.emotion !== undefined) {\r\n this._emotion = frame.emotion;\r\n }\r\n };\r\n source.on('frame', this.frameSourceCallback);\r\n this.connectedSource = source;\r\n logger.debug('Frame source connected');\r\n }\r\n\r\n /** Disconnect the current frame source (if any). */\r\n disconnectFrameSource(): void {\r\n if (this.connectedSource && this.frameSourceCallback) {\r\n this.connectedSource.off?.('frame', this.frameSourceCallback);\r\n logger.debug('Frame source disconnected');\r\n }\r\n this.connectedSource = null;\r\n this.frameSourceCallback = null;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Speaker (TTS → lip sync)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect a TTS backend for speak() / streamText() support.\r\n * Loads LAM model and creates internal PlaybackPipeline.\r\n *\r\n * @param tts - TTS backend (e.g., KokoroTTSInference, ElevenLabs adapter)\r\n * @param config - A2E, expression profile, and playback configuration\r\n */\r\n /** Warm up AudioContext for iOS/Safari autoplay policy. Call from user gesture. */\r\n async warmup(): Promise<void> {\r\n if (this.ttsSpeaker) await this.ttsSpeaker.warmup();\r\n }\r\n\r\n async connectSpeaker(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\r\n await this.disconnectSpeaker();\r\n this.ttsSpeaker = new TTSSpeaker();\r\n await this.ttsSpeaker.connect(tts, config);\r\n this.connectFrameSource(this.ttsSpeaker.frameSource!);\r\n }\r\n\r\n /**\r\n * Synthesize text and play with lip sync.\r\n * Auto-aborts previous speak if still in progress.\r\n *\r\n * @param text - Text to synthesize\r\n * @param options - Optional voice override and abort signal\r\n */\r\n async speak(text: string, options?: { signal?: AbortSignal; voice?: string }): Promise<void> {\r\n if (this.voiceOrchestrator) {\r\n await this.voiceOrchestrator.speak(text, options);\r\n return;\r\n }\r\n if (!this.ttsSpeaker) {\r\n throw new Error('No speaker connected. Call connectSpeaker() first.');\r\n }\r\n this._isSpeaking = true;\r\n this._state = 'speaking';\r\n try {\r\n await this.ttsSpeaker.speak(text, options);\r\n } finally {\r\n this._isSpeaking = false;\r\n if (this._state === 'speaking') {\r\n this._state = 'idle';\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Stream LLM tokens with sentence-buffered TTS + lip sync.\r\n * Returns a sink: call push(token) for each token, end() when done.\r\n */\r\n async streamText(options?: { signal?: AbortSignal; voice?: string }): Promise<{\r\n push: (token: string) => void;\r\n end: () => Promise<void>;\r\n }> {\r\n if (this.voiceOrchestrator) {\r\n return this.voiceOrchestrator.streamText(options);\r\n }\r\n if (!this.ttsSpeaker) {\r\n throw new Error('No speaker connected. Call connectSpeaker() first.');\r\n }\r\n this._isSpeaking = true;\r\n this._state = 'speaking';\r\n const stream = await this.ttsSpeaker.streamText(options ?? {});\r\n return {\r\n push: stream.push,\r\n end: async () => {\r\n try { await stream.end(); }\r\n finally { this._isSpeaking = false; if (this._state === 'speaking') this._state = 'idle'; }\r\n },\r\n };\r\n }\r\n\r\n /** Stop current TTS playback. */\r\n stopSpeaking(): void {\r\n if (this.voiceOrchestrator) {\r\n this.voiceOrchestrator.stopSpeaking();\r\n return;\r\n }\r\n this.ttsSpeaker?.stop();\r\n }\r\n\r\n /** Disconnect speaker and dispose its resources. */\r\n async disconnectSpeaker(): Promise<void> {\r\n if (this.ttsSpeaker) {\r\n this.disconnectFrameSource();\r\n await this.ttsSpeaker.dispose();\r\n this.ttsSpeaker = null;\r\n }\r\n }\r\n\r\n /** @deprecated Use connectSpeaker(). Will be removed in v1.0. */\r\n async connectTTS(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\r\n return this.connectSpeaker(tts, config);\r\n }\r\n\r\n /** @deprecated Use disconnectSpeaker(). Will be removed in v1.0. */\r\n async disconnectTTS(): Promise<void> {\r\n return this.disconnectSpeaker();\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Listener (mic → VAD → ASR → transcript)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect a speech listener for startListening() / onTranscript() support.\r\n * Loads ASR + VAD models.\r\n */\r\n async connectListener(config?: SpeechListenerConfig): Promise<void> {\r\n await this.disconnectListener();\r\n this.speechListener = new SpeechListener(config);\r\n await this.speechListener.loadModels();\r\n }\r\n\r\n /** Start listening for user speech. Requires connectListener() or connectVoice() first. */\r\n async startListening(): Promise<void> {\r\n if (this.voiceOrchestrator) {\r\n await this.voiceOrchestrator.startListening();\r\n return;\r\n }\r\n if (!this.speechListener) {\r\n throw new Error('No listener connected. Call connectListener() first.');\r\n }\r\n this._state = 'listening';\r\n await this.speechListener.start();\r\n }\r\n\r\n /** Stop listening. */\r\n stopListening(): void {\r\n if (this.voiceOrchestrator) {\r\n this.voiceOrchestrator.stopListening();\r\n return;\r\n }\r\n this.speechListener?.stop();\r\n if (this._state === 'listening') this._state = 'idle';\r\n }\r\n\r\n /**\r\n * Subscribe to transcript events. Returns an unsubscribe function.\r\n * Requires connectListener() first.\r\n */\r\n onTranscript(callback: (result: TranscriptResult) => void): () => void {\r\n const listener = this.speechListener ?? this.voiceOrchestrator?.listener;\r\n if (!listener) {\r\n throw new Error('No listener connected. Call connectListener() or connectVoice() first.');\r\n }\r\n listener.on('transcript', callback);\r\n return () => { listener.off?.('transcript', callback); };\r\n }\r\n\r\n /** Disconnect listener and dispose its resources. */\r\n async disconnectListener(): Promise<void> {\r\n if (this.speechListener) {\r\n await this.speechListener.dispose();\r\n this.speechListener = null;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Voice (combined speaker + listener + interruption)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect voice with automatic speaker + listener + interruption wiring.\r\n * Supports both local TTS (mode: 'local') and cloud TTS (mode: 'cloud').\r\n * Does NOT auto-start listening — call startListening() when ready.\r\n *\r\n * Backward compatible: `mode` defaults to 'local' when not specified.\r\n */\r\n async connectVoice(config: VoiceOrchestratorConfig): Promise<void> {\r\n await this.disconnectVoice();\r\n this.voiceOrchestrator = new VoiceOrchestrator();\r\n await this.voiceOrchestrator.connect(config);\r\n\r\n // Connect frame source from orchestrator\r\n if (this.voiceOrchestrator.frameSource) {\r\n this.connectFrameSource(this.voiceOrchestrator.frameSource);\r\n }\r\n\r\n // Sync state from orchestrator → avatar\r\n this.voiceOrchestrator.on('state', (state) => {\r\n this._state = state;\r\n this._isSpeaking = state === 'speaking';\r\n });\r\n }\r\n\r\n /** Disconnect voice (speaker + listener + interruption). */\r\n async disconnectVoice(): Promise<void> {\r\n if (this.voiceOrchestrator) {\r\n this.disconnectFrameSource();\r\n await this.voiceOrchestrator.disconnect();\r\n this.voiceOrchestrator = null;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // State setters\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Set blendshapes directly (alternative to connectFrameSource). */\r\n setFrame(blendshapes: Float32Array): void {\r\n this.currentBlendshapes = blendshapes;\r\n }\r\n\r\n /** Set emotion (string preset like 'happy' or EmotionWeights object). */\r\n setEmotion(emotion: string | EmotionWeights): void {\r\n this._emotion = emotion;\r\n }\r\n\r\n /** Set whether the avatar is currently speaking (drives mouth emphasis). */\r\n setSpeaking(speaking: boolean): void {\r\n this._isSpeaking = speaking;\r\n }\r\n\r\n /** Set conversational state (idle, listening, thinking, speaking). */\r\n setState(state: ConversationalState): void {\r\n this._state = state;\r\n }\r\n\r\n /** Set audio energy level (0-1, drives emphasis/gesture intensity). */\r\n setAudioEnergy(energy: number): void {\r\n this._audioEnergy = energy;\r\n }\r\n\r\n /** Update character expression profile at runtime. */\r\n setProfile(profile: CharacterProfile): void {\r\n this.controller.setProfile(profile);\r\n }\r\n\r\n /**\r\n * Set the active camera for gaze tracking.\r\n * Required when using autoUpdate. Can also be passed directly to update().\r\n */\r\n setCamera(camera: Camera): void {\r\n this._camera = camera;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Accessors\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Access underlying FaceCompositor for advanced use. */\r\n get compositor() {\r\n return this.controller.compositor;\r\n }\r\n\r\n /** Access SceneDiscoveryResult (meshes, bones, morph entries). */\r\n get parts(): SceneDiscoveryResult {\r\n return this.discovery;\r\n }\r\n\r\n /** Whether the scene has any mapped morph targets. */\r\n get hasMorphTargets(): boolean {\r\n return this.discovery.morphEntries.length > 0;\r\n }\r\n\r\n /** Number of successfully mapped ARKit blendshapes. */\r\n get mappedBlendshapeCount(): number {\r\n return this.discovery.mappedBlendshapeCount;\r\n }\r\n\r\n /** Whether the avatar is currently speaking via TTS. */\r\n get isSpeaking(): boolean {\r\n return this._isSpeaking;\r\n }\r\n\r\n /** Whether the avatar is currently listening for speech. */\r\n get isListening(): boolean {\r\n return this._state === 'listening';\r\n }\r\n\r\n /** Current conversational state. */\r\n get conversationalState(): ConversationalState {\r\n return this._state;\r\n }\r\n\r\n /** Access the internal TTSSpeaker (null if not connected). */\r\n get speaker(): TTSSpeaker | null {\r\n return this.ttsSpeaker ?? this.voiceOrchestrator?.speaker ?? null;\r\n }\r\n\r\n /** Access the internal SpeechListener (null if not connected). */\r\n get listener(): SpeechListener | null {\r\n return this.speechListener ?? this.voiceOrchestrator?.listener ?? null;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Lifecycle\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Reset all state (smoothing, life layer, emotions). */\r\n reset(): void {\r\n this.currentBlendshapes = null;\r\n this._emotion = null;\r\n this._isSpeaking = false;\r\n this._state = 'idle';\r\n this._audioEnergy = 0;\r\n this.controller.reset();\r\n }\r\n\r\n /** Disconnect all voice resources, frame sources, unregister render loop, dispose controller. */\r\n async dispose(): Promise<void> {\r\n await this.disconnectVoice();\r\n await this.disconnectSpeaker();\r\n await this.disconnectListener();\r\n this.disconnectFrameSource();\r\n if (this.renderCallback) {\r\n this.scene.unregisterBeforeRender(this.renderCallback);\r\n this.renderCallback = null;\r\n }\r\n this.controller.dispose();\r\n logger.debug('Disposed');\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal\r\n // ---------------------------------------------------------------------------\r\n\r\n private registerAutoUpdate(): void {\r\n this.lastTime = getClock().now();\r\n this.renderCallback = () => {\r\n const now = getClock().now();\r\n const delta = (now - this.lastTime) / 1000;\r\n this.lastTime = now;\r\n if (this._camera) {\r\n this.update(delta, this._camera);\r\n }\r\n };\r\n this.scene.registerBeforeRender(this.renderCallback);\r\n }\r\n}\r\n\r\n","/**\n * SceneDiscovery — Discover avatar structure from a Babylon.js scene graph.\n *\n * Finds morph targets, head/neck/eye bones, and pre-computes a fast index\n * mapping from LAM_BLENDSHAPES order to per-mesh morph target indices.\n *\n * @category Babylon\n */\n\nimport { LAM_BLENDSHAPES, createLogger } from '@omote/core';\n\nconst logger = createLogger('SceneDiscovery');\nimport type { AbstractMesh, TransformNode } from '@babylonjs/core';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MorphIndexEntry {\n mesh: AbstractMesh;\n /** indices[lamIndex] = morphTargetIndex (or -1 if not found) */\n indices: Int16Array;\n}\n\nexport interface SceneDiscoveryResult {\n /** All meshes under the root that have a MorphTargetManager */\n meshes: AbstractMesh[];\n /** Head bone (TransformNode named 'Head') or null */\n headBone: TransformNode | null;\n /** Neck bone (TransformNode named 'Neck') or null */\n neckBone: TransformNode | null;\n /** Left eye bone (TransformNode named 'LeftEye') or null */\n leftEyeBone: TransformNode | null;\n /** Right eye bone (TransformNode named 'RightEye') or null */\n rightEyeBone: TransformNode | null;\n /** Pre-computed morph index mapping per mesh */\n morphEntries: MorphIndexEntry[];\n /** Primary face mesh (prefers 'Head_Mesh', falls back to first with morph targets) */\n faceMesh: AbstractMesh | null;\n /** Total number of ARKit blendshapes successfully mapped across all meshes */\n mappedBlendshapeCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Normalize morph target name by stripping common prefixes */\nfunction normalizeName(name: string): string {\n return name.replace(/^morph_/i, '').replace(/^blendshape\\./i, '');\n}\n\n/** Case-insensitive bone name matching */\nconst BONE_PATTERNS: Record<string, RegExp> = {\n head: /^head$/i,\n neck: /^neck$/i,\n leftEye: /^lefteye$/i,\n rightEye: /^righteye$/i,\n};\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Discover avatar structure from a Babylon.js root node.\n *\n * Traverses the scene graph under `root` to find:\n * - Meshes with MorphTargetManagers\n * - Head, neck, and eye bones\n * - Pre-computed LAM_BLENDSHAPES -> morph target index mapping\n *\n * @param root - The root AbstractMesh (typically loaded via SceneLoader)\n * @returns SceneDiscoveryResult with all discovered components\n */\nexport function discoverScene(root: AbstractMesh): SceneDiscoveryResult {\n const meshes: AbstractMesh[] = [];\n const morphEntries: MorphIndexEntry[] = [];\n let faceMesh: AbstractMesh | null = null;\n let mappedBlendshapeCount = 0;\n\n // ---- Morph target discovery ----\n\n // Collect all candidate meshes (root + children)\n const candidates: AbstractMesh[] = [root];\n if (root.getChildMeshes) {\n candidates.push(...root.getChildMeshes(false));\n }\n\n for (const mesh of candidates) {\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n meshes.push(mesh);\n\n // Prefer mesh named 'Head_Mesh' as the face mesh\n if (!faceMesh || mesh.name === 'Head_Mesh') {\n faceMesh = mesh;\n }\n\n // Build normalized name -> morph target index lookup for this mesh\n const morphLookup = new Map<string, number>();\n const numTargets = manager.numTargets;\n for (let t = 0; t < numTargets; t++) {\n const morphTarget = manager.getTarget(t);\n const normalized = normalizeName(morphTarget.name);\n morphLookup.set(normalized, t);\n }\n\n // Pre-compute Int16Array mapping: lamIndex -> morphTargetIndex (or -1)\n const indices = new Int16Array(LAM_BLENDSHAPES.length);\n let meshMapped = 0;\n for (let i = 0; i < LAM_BLENDSHAPES.length; i++) {\n const morphIdx = morphLookup.get(LAM_BLENDSHAPES[i]);\n if (morphIdx !== undefined) {\n indices[i] = morphIdx;\n meshMapped++;\n } else {\n indices[i] = -1;\n }\n }\n\n if (meshMapped > 0) {\n morphEntries.push({ mesh, indices });\n }\n mappedBlendshapeCount = Math.max(mappedBlendshapeCount, meshMapped);\n\n logger.debug(`Mesh \"${mesh.name}\": ${meshMapped}/${LAM_BLENDSHAPES.length} blendshapes mapped`);\n }\n\n // ---- Bone discovery ----\n\n let headBone: TransformNode | null = null;\n let neckBone: TransformNode | null = null;\n let leftEyeBone: TransformNode | null = null;\n let rightEyeBone: TransformNode | null = null;\n\n // getChildTransformNodes is on TransformNode (AbstractMesh extends TransformNode)\n const transformNodes = root.getChildTransformNodes(false);\n\n for (const node of transformNodes) {\n if (!headBone && BONE_PATTERNS.head.test(node.name)) {\n headBone = node;\n } else if (!neckBone && BONE_PATTERNS.neck.test(node.name)) {\n neckBone = node;\n } else if (!leftEyeBone && BONE_PATTERNS.leftEye.test(node.name)) {\n leftEyeBone = node;\n } else if (!rightEyeBone && BONE_PATTERNS.rightEye.test(node.name)) {\n rightEyeBone = node;\n }\n\n // Early exit if all bones found\n if (headBone && neckBone && leftEyeBone && rightEyeBone) break;\n }\n\n if (morphEntries.length === 0) {\n logger.warn('No morph targets found in scene');\n }\n if (!headBone) {\n logger.warn('Head bone not found in scene');\n }\n\n const boneNames = [\n headBone && 'Head',\n neckBone && 'Neck',\n leftEyeBone && 'LeftEye',\n rightEyeBone && 'RightEye',\n ].filter(Boolean);\n\n logger.info(\n `Discovery complete: ${meshes.length} mesh(es), ${mappedBlendshapeCount} mapped blendshapes, bones: [${boneNames.join(', ')}]`,\n );\n\n return {\n meshes,\n headBone,\n neckBone,\n leftEyeBone,\n rightEyeBone,\n morphEntries,\n faceMesh,\n mappedBlendshapeCount,\n };\n}\n","/**\n * BlendshapeWriter — Zero-lookup hot-path blendshape application.\n *\n * Uses pre-computed Int16Array indices from SceneDiscovery for O(1) lookups\n * per blendshape per mesh. No Map.get(), no string comparison on the hot path.\n *\n * @category Babylon\n */\n\nimport type { MorphIndexEntry } from './SceneDiscovery';\n\n/**\n * Write 52 ARKit blendshapes to Babylon.js morph targets.\n *\n * Uses pre-computed indices for zero-lookup hot path.\n * Call this every frame after CharacterController.update().\n *\n * @param blendshapes - 52-element Float32Array in LAM_BLENDSHAPES order\n * @param morphEntries - Pre-computed index mappings from discoverScene()\n */\nexport function writeBlendshapes(\n blendshapes: Float32Array,\n morphEntries: MorphIndexEntry[],\n): void {\n for (let e = 0; e < morphEntries.length; e++) {\n const { mesh, indices } = morphEntries[e];\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n for (let i = 0; i < 52; i++) {\n const morphIdx = indices[i];\n if (morphIdx >= 0) {\n const target = manager.getTarget(morphIdx);\n if (target) {\n target.influence = blendshapes[i];\n }\n }\n }\n }\n}\n","import { LAM_BLENDSHAPES, lerpBlendshapes } from '@omote/core';\nimport type { AbstractMesh, Scene } from '@babylonjs/core';\n\nexport interface BlendshapeControllerOptions {\n /** Blendshape names in order (default: LAM_BLENDSHAPES, 52 ARKit) */\n names?: readonly string[];\n /** Smoothing factor 0-1 (0 = no change, 1 = snap to target). Default: 0.7 */\n smoothing?: number;\n /** Traverse target for child meshes with MorphTargetManager automatically. Default: true */\n autoFind?: boolean;\n /** Register scene.registerBeforeRender() callback automatically. Default: false */\n autoRegister?: boolean;\n /** Called when meshes with morph targets are found */\n onMeshesFound?: (meshes: AbstractMesh[]) => void;\n}\n\nexport class BlendshapeController {\n private _meshes: AbstractMesh[] = [];\n private nameToIndex: Map<string, number>[] = [];\n private currentWeights: number[] = [];\n private blendshapeNames: readonly string[];\n private smoothing: number;\n private scene: Scene | null;\n private renderObserver: (() => void) | null = null;\n private pendingWeights: Float32Array | number[] | null = null;\n private onMeshesFound?: (meshes: AbstractMesh[]) => void;\n\n constructor(target: AbstractMesh, scene: Scene, options?: BlendshapeControllerOptions) {\n this.blendshapeNames = options?.names ?? LAM_BLENDSHAPES;\n this.smoothing = options?.smoothing ?? 0.7;\n this.scene = scene;\n this.onMeshesFound = options?.onMeshesFound;\n\n if (options?.autoFind !== false) {\n this.setTarget(target);\n }\n\n if (options?.autoRegister) {\n this.registerRenderLoop();\n }\n }\n\n get meshes(): AbstractMesh[] {\n return this._meshes;\n }\n\n /** Normalize Babylon morph target names (strip common prefixes) */\n private normalizeName(name: string): string {\n return name.replace(/^morph_/i, '').replace(/^blendshape\\./i, '');\n }\n\n setTarget(target: AbstractMesh): void {\n this._meshes = [];\n this.nameToIndex = [];\n\n // Collect meshes with MorphTargetManager\n const candidates: AbstractMesh[] = [target];\n if (target.getChildMeshes) {\n candidates.push(...target.getChildMeshes(false));\n }\n\n for (const mesh of candidates) {\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n this._meshes.push(mesh);\n\n // Build normalized name -> morph target index lookup\n const morphLookup = new Map<string, number>();\n const numTargets = manager.numTargets;\n for (let t = 0; t < numTargets; t++) {\n const morphTarget = manager.getTarget(t);\n const normalizedName = this.normalizeName(morphTarget.name);\n morphLookup.set(normalizedName, t);\n }\n\n // Map blendshape names to morph target indices\n const map = new Map<string, number>();\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const idx = morphLookup.get(name);\n if (idx !== undefined) {\n map.set(name, idx);\n }\n }\n\n this.nameToIndex.push(map);\n }\n\n this.currentWeights = new Array(this.blendshapeNames.length).fill(0);\n\n if (this._meshes.length > 0 && this.onMeshesFound) {\n this.onMeshesFound(this._meshes);\n }\n }\n\n update(weights: Float32Array | number[]): void {\n this.currentWeights = lerpBlendshapes(this.currentWeights, weights, this.smoothing);\n\n for (let m = 0; m < this._meshes.length; m++) {\n const mesh = this._meshes[m];\n const map = this.nameToIndex[m];\n const manager = mesh.morphTargetManager;\n if (!manager || !map) continue;\n\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const morphIdx = map.get(name);\n if (morphIdx !== undefined) {\n const target = manager.getTarget(morphIdx);\n if (target) {\n target.influence = this.currentWeights[i];\n }\n }\n }\n }\n }\n\n /** Buffer weights for the next render loop tick (used with autoRegister) */\n setWeightsForNextFrame(weights: Float32Array | number[]): void {\n this.pendingWeights = weights;\n }\n\n private registerRenderLoop(): void {\n if (!this.scene) return;\n const callback = () => {\n if (this.pendingWeights) {\n this.update(this.pendingWeights);\n this.pendingWeights = null;\n }\n };\n this.scene.registerBeforeRender(callback);\n this.renderObserver = () => {\n this.scene?.unregisterBeforeRender(callback);\n };\n }\n\n dispose(): void {\n if (this.renderObserver) {\n this.renderObserver();\n this.renderObserver = null;\n }\n this._meshes = [];\n this.nameToIndex = [];\n this.currentWeights = [];\n this.scene = null;\n }\n}\n"],"mappings":"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","names":["import_core","logger","import_core"]}
|