Closed yuchanahn closed 1 week ago
class VRMExpressionMorphTargetBind { public readonly primitives: THREE.Mesh[]; public readonly index: number; public readonly weight: number; public constructor({ primitives, index, weight, }: { primitives: THREE.Mesh[]; index: number; weight: number; }) { this.primitives = primitives; this.index = index; this.weight = weight; } public applyWeight(weight: number): void { this.primitives.forEach((mesh) => { if (mesh.morphTargetInfluences?.[this.index] != null) { mesh.morphTargetInfluences[this.index] += this.weight * weight; } }); } public clearAppliedWeight(): void { this.primitives.forEach((mesh) => { if (mesh.morphTargetInfluences?.[this.index] != null) { mesh.morphTargetInfluences[this.index] = 0.0; } }); } } export class ExpressionController { private _autoLookAt: AutoLookAt private _autoBlink?: AutoBlink private _expressionManager?: VRMExpressionManager private _currentEmotion: VRMExpressionPresetName private _vrm: VRM private _currentLipSync: { preset: VRMExpressionPresetName value: number } | null private emotionExpressions = [ { name: "annoyance", targets: [ { targetName: "Fcl_BRW_Angry", weight: 0.8 }, { targetName: "Fcl_EYE_Close_R", weight: 0.6 }, { targetName: "Fcl_EYE_Close_L", weight: 0.6 }, { targetName: "Fcl_MTH_Angry", weight: 0.7 }, ] }, ]; constructor(vrm: VRM, camera: THREE.Object3D) { this._vrm = vrm this._autoLookAt = new AutoLookAt(vrm, camera) this._autoLookAt.start() this._currentEmotion = 'neutral' this._currentLipSync = null if (vrm.expressionManager) { this._expressionManager = vrm.expressionManager this._autoBlink = new AutoBlink(vrm.expressionManager) } const expressions = this.custom_expressions( this.emotionExpressions ); expressions.forEach( (x) => {this._expressionManager?.registerExpression(x);} ); this.emotion_test() } public custom_expressions( expressionsData: Array<{ name: string; targets: Array<{ targetName: string; weight: number }> }> ): VRMExpression[] { const expressions: VRMExpression[] = []; expressionsData.forEach(({ name, targets }) => { const expression = new VRMExpression(name); // Face 오브젝트 찾기 const faceObject = this._vrm?.scene.getObjectByName("Face"); if (!faceObject) { console.warn("Face 오브젝트를 찾을 수 없습니다."); return; } // Face 오브젝트의 프리미티브 가져오기 const primitives = faceObject.children.filter( (child) => child instanceof THREE.Mesh ) as THREE.Mesh[]; // SkinnedMesh에서 morphTargetDictionary 가져오기 const skinnedMesh = faceObject.children[0] as THREE.SkinnedMesh; const morphTargetDictionary = skinnedMesh.morphTargetDictionary; if (!morphTargetDictionary) { console.warn("morphTargetDictionary를 찾을 수 없습니다."); return; } else { console.log(Object.keys(morphTargetDictionary)); } // 여러 target을 바인드하는 부분 targets.forEach(({ targetName, weight }) => { if (morphTargetDictionary[targetName] !== undefined) { expression.addBind( new VRMExpressionMorphTargetBind({ primitives, index: morphTargetDictionary[targetName], weight, }) ); } else { console.warn(`Target '${targetName}'을 찾을 수 없습니다.`); } }); expressions.push(expression); // expression을 배열에 추가 }); return expressions; // 모든 Expression 객체를 배열로 반환 } public emotion_test() { // annoyance // approval // caring // confusion const test_emotions = ['annoyance'] for (let i = 0; i < test_emotions.length; i++) { setTimeout(() => { if((i - 1) != -1) { this._expressionManager?.setValue(test_emotions[i - 1], 0); } this._expressionManager?.setValue(test_emotions[i], 1); console.log(test_emotions[i]); }, i * 100000); } }
<- three-vrm(1.0.3)
same code
<- three-vrm(3.1.6)
<- three-vrm(1.0.3)
same code
<- three-vrm(3.1.6)