@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/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/OmoteAvatar.ts","../src/SceneDiscovery.ts","../src/BlendshapeWriter.ts","../src/BlendshapeController.ts","../src/OmoteA2E.ts"],"sourcesContent":["/**\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":["createLogger","logger","createLogger","LAM_BLENDSHAPES"]}
|
|
1
|
+
{"version":3,"sources":["../src/OmoteAvatar.ts","../src/SceneDiscovery.ts","../src/BlendshapeWriter.ts","../src/BlendshapeController.ts"],"sourcesContent":["/**\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":["createLogger","logger","createLogger","LAM_BLENDSHAPES"]}
|
package/package.json
CHANGED
|
@@ -1,45 +1,51 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@omote/babylon",
|
|
3
|
-
"version": "0.2
|
|
4
|
-
"description": "Babylon.js adapter for Omote AI character SDK",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.cjs",
|
|
7
|
-
"module": "./dist/index.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"types": "./dist/index.d.ts",
|
|
12
|
-
"import": "./dist/index.js",
|
|
13
|
-
"require": "./dist/index.cjs"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"publishConfig": {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@omote/babylon",
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "Babylon.js adapter for Omote AI character SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@omote/core": ">=0.9.0",
|
|
27
|
+
"@babylonjs/core": ">=6.0.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"@babylonjs/loaders": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@omote/core": "file:../core",
|
|
36
|
+
"@babylonjs/core": "^8.0.0",
|
|
37
|
+
"@babylonjs/loaders": "^8.0.0",
|
|
38
|
+
"tsup": "^8.0.0",
|
|
39
|
+
"typescript": "^5.3.0",
|
|
40
|
+
"vitest": "^4.0.16"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist"
|
|
44
|
+
],
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/omoteai/omote.git",
|
|
49
|
+
"directory": "packages/babylon"
|
|
50
|
+
}
|
|
51
|
+
}
|