import { SkinnedMesh, Vector3, type Object3D } from "three"; import type { AvatarAttachmentPoint } from "./characterManifest"; export interface AvatarAttachmentTransform { readonly target: Object3D; readonly position: Vector3; readonly scale: Vector3; } export interface AvatarAttachmentOptions { /** The manifest point is the authored origin of its referenced attachment joint. */ readonly useTargetOrigin?: boolean; } const safeScaleRatio = (rootScale: number, targetScale: number): number => ( Number.isFinite(rootScale) && Number.isFinite(targetScale) && Math.abs(targetScale) > 1e-6 ? rootScale / targetScale : 1 ); function attachmentScale(root: Object3D, target: Object3D): Vector3 { const rootScale = root.getWorldScale(new Vector3()); const targetScale = target.getWorldScale(new Vector3()); return new Vector3( safeScaleRatio(rootScale.x, targetScale.x), safeScaleRatio(rootScale.y, targetScale.y), safeScaleRatio(rootScale.z, targetScale.z), ); } export function findAvatarSkinBone(root: Object3D, boneIndex: number): Object3D | null { let bone: Object3D | null = null; root.traverse((node) => { if (bone) return; const skinnedMesh = node as SkinnedMesh; if (!skinnedMesh.isSkinnedMesh) return; bone = skinnedMesh.skeleton.bones[boneIndex] ?? null; }); return bone; } /** Converts a manifest point from avatar-local space into its target bone. */ export function resolveAvatarAttachmentPoint( root: Object3D, point: AvatarAttachmentPoint | undefined, fallbackTarget: Object3D | null, options: AvatarAttachmentOptions = {}, ): AvatarAttachmentTransform | null { const skinBone = point ? findAvatarSkinBone(root, point.bone) : null; const target = skinBone ?? fallbackTarget; if (!target) return null; root.updateWorldMatrix(true, true); const scale = attachmentScale(root, target); if (!point || (skinBone && options.useTargetOrigin)) { return { target, position: new Vector3(), scale }; } const position = root.localToWorld(new Vector3(...point.position)); target.worldToLocal(position); return { target, position, scale }; }