-
-
Notifications
You must be signed in to change notification settings - Fork 143
Feature/add animation vrma #390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughVRMアニメーション管理システムが大幅に拡張され、複数の感情アニメーションやアイドルアニメーションのロード・再生・クロスフェード・状態管理が可能になりました。また、VRMAファイルのドラッグ&ドロップ対応、設定画面での「Queue Check Delay」調整機能、ローカライズ対応、関連UIの追加も行われています。 Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant VRMViewerComponent
participant Viewer
participant Model
User->>VRMViewerComponent: .vrmaファイルをドロップ
VRMViewerComponent->>Viewer: loadVrma(fileUrl)
Viewer->>Model: loadAnimationFromUrl(fileUrl, animationName)
Model->>Model: アニメーションクリップをロード・登録
Viewer->>Model: crossFadeToAnimation(animationName)
Model->>Model: クロスフェード再生
sequenceDiagram
participant SpeakCharacter
participant HomeStore
participant Viewer
participant Model
SpeakCharacter->>HomeStore: viewer, modelType, talk.emotion取得
alt modelType == 'vrm' and emotion != 'neutral'
SpeakCharacter->>Viewer: playEmotionAnimation(emotion)
Viewer->>Model: playEmotionAnimation(emotion)
Model->>Model: 感情アニメーション再生
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🔭 Outside diff range comments (1)
src/features/vrmViewer/viewer.ts (1)
1-7:⚠️ Potential issue
loadMixamoAnimationの import が欠落しておりコンパイルに失敗します
loadFbx()内でloadMixamoAnimationを利用していますが、上部に import がありません。TypeScript のビルドが通りませんので追加してください。import * as THREE from 'three' import { Model } from './model' import { loadVRMAnimation } from '@/lib/VRMAnimation/loadVRMAnimation' +import { loadMixamoAnimation } from '@/lib/fbxAnimation/loadMixamoAnimation' import { buildUrl } from '@/utils/buildUrl'
🧹 Nitpick comments (3)
src/lib/fbxAnimation/loadMixamoAnimation.ts (2)
14-18: 戻り値型を明示し、可読性と型安全性を向上させましょう現状のシグネチャは
export function loadMixamoAnimation(url: string, vrm: VRM)
で、戻り値型が省略されています。Promise<THREE.AnimationClip>を明示すると IDE 補完や呼び出し側での型推論がより確実になります。-export function loadMixamoAnimation(url: string, vrm: VRM) { +export function loadMixamoAnimation( + url: string, + vrm: VRM +): Promise<THREE.AnimationClip> {🧰 Tools
🪛 ESLint
[error] 15-16: Replace
⏎↹const·loader·=·new·FBXLoader();with··const·loader·=·new·FBXLoader()(prettier/prettier)
[error] 17-17: Replace
↹with··(prettier/prettier)
35-44:any[]の使用は型安全性を損ないます
const tracks: any[] = []は最終的にTHREE.KeyframeTrack[]なので、具体的な型を付与してください。-const tracks: any[] = []; +const tracks: THREE.KeyframeTrack[] = [];🧰 Tools
🪛 ESLint
[error] 35-35: Replace
↹↹with····(prettier/prettier)
[error] 36-37: Replace
⏎↹↹↹with······(prettier/prettier)
[error] 38-38: Replace
↹↹↹const·trackSplitted·=·track.name.split('.');with······const·trackSplitted·=·track.name.split('.')(prettier/prettier)
[error] 39-39: Replace
↹↹↹const·mixamoRigName:·MixamoVRMRigMapIndex·=·trackSplitted[0]·as·MixamoVRMRigMapIndex;with······const·mixamoRigName:·MixamoVRMRigMapIndex·=⏎········trackSplitted[0]·as·MixamoVRMRigMapIndex(prettier/prettier)
[error] 40-40: Replace
↹↹↹const·vrmBoneName:·VRMHumanBoneName·=·mixamoVRMRigMap[mixamoRigName]·as·VRMHumanBoneName;with······const·vrmBoneName:·VRMHumanBoneName·=·mixamoVRMRigMap[⏎········mixamoRigName⏎······]·as·VRMHumanBoneName(prettier/prettier)
[error] 41-41: Replace
↹↹↹const·vrmNodeName·=·vrm.humanoid?.getNormalizedBoneNode(vrmBoneName)?.name;with······const·vrmNodeName·=·vrm.humanoid?.getNormalizedBoneNode(vrmBoneName)?.name(prettier/prettier)
[error] 42-42: Replace
↹↹↹const·mixamoRigNode·=·asset.getObjectByName(mixamoRigName);with······const·mixamoRigNode·=·asset.getObjectByName(mixamoRigName)(prettier/prettier)
[error] 44-44: Replace
↹↹↹with······(prettier/prettier)
src/features/vrmViewer/viewer.ts (1)
23-30: アニメーション種別はリテラル型で限定すると意図が明確になります
_currentAnimationType: stringではなく'vrma' | 'fbx'など union 型を用いるとタイプミスを防げます。-private _currentAnimationType: string +private _currentAnimationType: 'vrma' | 'fbx'
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
-
LICENSE(1 hunks) -
src/components/vrmViewer.tsx(1 hunks) -
src/features/vrmViewer/model.ts(1 hunks) -
src/features/vrmViewer/viewer.ts(4 hunks) -
src/lib/fbxAnimation/loadMixamoAnimation.ts(1 hunks) -
src/lib/fbxAnimation/mixamoVRMRigMap.ts(1 hunks)
🧰 Additional context used
🪛 ESLint
src/lib/fbxAnimation/mixamoVRMRigMap.ts
[error] 5-5: Replace ↹ with ··
(prettier/prettier)
[error] 6-6: Replace ↹ with ··
(prettier/prettier)
[error] 7-7: Replace ↹ with ··
(prettier/prettier)
[error] 8-8: Replace ↹ with ··
(prettier/prettier)
[error] 9-9: Replace ↹ with ··
(prettier/prettier)
[error] 10-10: Replace ↹ with ··
(prettier/prettier)
[error] 11-11: Replace ↹ with ··
(prettier/prettier)
[error] 12-12: Replace ↹ with ··
(prettier/prettier)
[error] 13-13: Replace ↹ with ··
(prettier/prettier)
[error] 14-14: Replace ↹ with ··
(prettier/prettier)
[error] 15-15: Replace ↹ with ··
(prettier/prettier)
[error] 16-16: Replace ↹ with ··
(prettier/prettier)
[error] 17-17: Replace ↹ with ··
(prettier/prettier)
[error] 18-18: Replace ↹ with ··
(prettier/prettier)
[error] 19-19: Replace ↹ with ··
(prettier/prettier)
[error] 20-20: Replace ↹ with ··
(prettier/prettier)
[error] 21-21: Replace ↹ with ··
(prettier/prettier)
[error] 22-22: Replace ↹ with ··
(prettier/prettier)
[error] 23-23: Replace ↹ with ··
(prettier/prettier)
[error] 24-24: Replace ↹ with ··
(prettier/prettier)
[error] 25-25: Replace ↹ with ··
(prettier/prettier)
[error] 26-26: Replace ↹ with ··
(prettier/prettier)
[error] 27-27: Replace ↹ with ··
(prettier/prettier)
[error] 28-28: Replace ↹ with ··
(prettier/prettier)
[error] 29-29: Replace ↹ with ··
(prettier/prettier)
[error] 30-30: Replace ↹ with ··
(prettier/prettier)
[error] 31-31: Replace ↹ with ··
(prettier/prettier)
[error] 32-32: Replace ↹ with ··
(prettier/prettier)
[error] 33-33: Replace ↹ with ··
(prettier/prettier)
[error] 34-34: Replace ↹ with ··
(prettier/prettier)
[error] 35-35: Replace ↹ with ··
(prettier/prettier)
[error] 36-36: Replace ↹ with ··
(prettier/prettier)
[error] 37-37: Replace ↹ with ··
(prettier/prettier)
[error] 38-38: Replace ↹ with ··
(prettier/prettier)
[error] 39-39: Replace ↹ with ··
(prettier/prettier)
[error] 40-40: Replace ↹ with ··
(prettier/prettier)
[error] 41-41: Replace ↹ with ··
(prettier/prettier)
[error] 42-42: Replace ↹ with ··
(prettier/prettier)
[error] 43-43: Replace ↹ with ··
(prettier/prettier)
[error] 44-44: Replace ↹ with ··
(prettier/prettier)
[error] 45-45: Replace ↹ with ··
(prettier/prettier)
[error] 46-46: Replace ↹ with ··
(prettier/prettier)
[error] 47-47: Replace ↹ with ··
(prettier/prettier)
[error] 48-48: Replace ↹ with ··
(prettier/prettier)
[error] 49-49: Replace ↹ with ··
(prettier/prettier)
[error] 50-50: Replace ↹ with ··
(prettier/prettier)
[error] 51-51: Replace ↹ with ··
(prettier/prettier)
[error] 52-52: Replace ↹ with ··
(prettier/prettier)
[error] 53-53: Replace ↹ with ··
(prettier/prettier)
[error] 54-54: Replace ↹ with ··
(prettier/prettier)
[error] 55-55: Replace ↹ with ··
(prettier/prettier)
[error] 56-56: Replace ↹ with ··
(prettier/prettier)
[error] 57-58: Delete ;⏎
(prettier/prettier)
[error] 60-103: Replace ·⏎↹"mixamorigHips"·|·"mixamorigSpine"·|·"mixamorigSpine1"·|·"mixamorigSpine2"·|⏎↹"mixamorigNeck"·|·"mixamorigHead"·|·"mixamorigLeftShoulder"·|·"mixamorigLeftArm"·|·⏎↹"mixamorigLeftForeArm"·|·"mixamorigLeftHand"·|·"mixamorigLeftHandThumb1"·|·⏎↹"mixamorigLeftHandThumb2"·|·"mixamorigLeftHandThumb3"·|⏎↹"mixamorigLeftHandIndex1"·|⏎↹"mixamorigLeftHandIndex2"·|·⏎↹"mixamorigLeftHandIndex3"·|⏎↹"mixamorigLeftHandMiddle1"·|·⏎↹"mixamorigLeftHandMiddle2"·|⏎↹"mixamorigLeftHandMiddle3"·|⏎↹"mixamorigLeftHandRing1"·|⏎↹"mixamorigLeftHandRing2"·|⏎↹"mixamorigLeftHandRing3"·|⏎↹"mixamorigLeftHandPinky1"·|⏎↹"mixamorigLeftHandPinky2"·|⏎↹"mixamorigLeftHandPinky3"·|⏎↹"mixamorigRightShoulder"·|⏎↹"mixamorigRightArm"·|⏎↹"mixamorigRightForeArm"·|⏎↹"mixamorigRightHand"·|⏎↹"mixamorigRightHandPinky1"·|⏎↹"mixamorigRightHandPinky2"·|⏎↹"mixamorigRightHandPinky3"·|⏎↹"mixamorigRightHandRing1"·|⏎↹"mixamorigRightHandRing2"·|⏎↹"mixamorigRightHandRing3"·|⏎↹"mixamorigRightHandMiddle1"·|⏎↹"mixamorigRightHandMiddle2"·|⏎↹"mixamorigRightHandMiddle3"·|⏎↹"mixamorigRightHandIndex1"·|⏎↹"mixamorigRightHandIndex2"·|⏎↹"mixamorigRightHandIndex3"·|⏎↹"mixamorigRightHandThumb1"·|⏎↹"mixamorigRightHandThumb2"·|⏎↹"mixamorigRightHandThumb3"·|⏎↹"mixamorigLeftUpLeg"·|⏎↹"mixamorigLeftLeg"·|⏎↹"mixamorigLeftFoot"·|⏎↹"mixamorigLeftToeBase"·|⏎↹"mixamorigRightUpLeg"·|⏎↹"mixamorigRightLeg"·|⏎↹"mixamorigRightFoot"·|⏎↹"mixamorigRightToeBase"; with ⏎··|·'mixamorigHips'⏎··|·'mixamorigSpine'⏎··|·'mixamorigSpine1'⏎··|·'mixamorigSpine2'⏎··|·'mixamorigNeck'⏎··|·'mixamorigHead'⏎··|·'mixamorigLeftShoulder'⏎··|·'mixamorigLeftArm'⏎··|·'mixamorigLeftForeArm'⏎··|·'mixamorigLeftHand'⏎··|·'mixamorigLeftHandThumb1'⏎··|·'mixamorigLeftHandThumb2'⏎··|·'mixamorigLeftHandThumb3'⏎··|·'mixamorigLeftHandIndex1'⏎··|·'mixamorigLeftHandIndex2'⏎··|·'mixamorigLeftHandIndex3'⏎··|·'mixamorigLeftHandMiddle1'⏎··|·'mixamorigLeftHandMiddle2'⏎··|·'mixamorigLeftHandMiddle3'⏎··|·'mixamorigLeftHandRing1'⏎··|·'mixamorigLeftHandRing2'⏎··|·'mixamorigLeftHandRing3'⏎··|·'mixamorigLeftHandPinky1'⏎··|·'mixamorigLeftHandPinky2'⏎··|·'mixamorigLeftHandPinky3'⏎··|·'mixamorigRightShoulder'⏎··|·'mixamorigRightArm'⏎··|·'mixamorigRightForeArm'⏎··|·'mixamorigRightHand'⏎··|·'mixamorigRightHandPinky1'⏎··|·'mixamorigRightHandPinky2'⏎··|·'mixamorigRightHandPinky3'⏎··|·'mixamorigRightHandRing1'⏎··|·'mixamorigRightHandRing2'⏎··|·'mixamorigRightHandRing3'⏎··|·'mixamorigRightHandMiddle1'⏎··|·'mixamorigRightHandMiddle2'⏎··|·'mixamorigRightHandMiddle3'⏎··|·'mixamorigRightHandIndex1'⏎··|·'mixamorigRightHandIndex2'⏎··|·'mixamorigRightHandIndex3'⏎··|·'mixamorigRightHandThumb1'⏎··|·'mixamorigRightHandThumb2'⏎··|·'mixamorigRightHandThumb3'⏎··|·'mixamorigLeftUpLeg'⏎··|·'mixamorigLeftLeg'⏎··|·'mixamorigLeftFoot'⏎··|·'mixamorigLeftToeBase'⏎··|·'mixamorigRightUpLeg'⏎··|·'mixamorigRightLeg'⏎··|·'mixamorigRightFoot'⏎··|·'mixamorigRightToeBase'
(prettier/prettier)
src/lib/fbxAnimation/loadMixamoAnimation.ts
[error] 1-1: Delete ;
(prettier/prettier)
[error] 2-2: Delete ;
(prettier/prettier)
[error] 3-3: Delete ;
(prettier/prettier)
[error] 4-4: Delete ;
(prettier/prettier)
[error] 9-9: Delete ·
(prettier/prettier)
[error] 15-16: Replace ⏎↹const·loader·=·new·FBXLoader(); with ··const·loader·=·new·FBXLoader()
(prettier/prettier)
[error] 17-17: Replace ↹ with ··
(prettier/prettier)
[error] 18-19: Replace ⏎↹↹const·clip·=·THREE.AnimationClip.findByName(asset.animations,·'mixamo.com'); with ····const·clip·=·THREE.AnimationClip.findByName(asset.animations,·'mixamo.com')
(prettier/prettier)
[error] 21-21: Replace ↹↹const·tracks:·any[]·=·[]; with ····const·tracks:·any[]·=·[]
(prettier/prettier)
[error] 23-23: Replace ↹↹const·restRotationInverse·=·new·THREE.Quaternion(); with ····const·restRotationInverse·=·new·THREE.Quaternion()
(prettier/prettier)
[error] 24-24: Replace ↹↹const·parentRestWorldRotation·=·new·THREE.Quaternion(); with ····const·parentRestWorldRotation·=·new·THREE.Quaternion()
(prettier/prettier)
[error] 25-25: Replace ↹↹const·_quatA·=·new·THREE.Quaternion(); with ····const·_quatA·=·new·THREE.Quaternion()
(prettier/prettier)
[error] 26-26: Replace ↹↹const·_vec3·=·new·THREE.Vector3(); with ····const·_vec3·=·new·THREE.Vector3()
(prettier/prettier)
[error] 28-28: Replace ↹↹ with ····
(prettier/prettier)
[error] 29-29: Replace ↹↹const·motionHipsHeight·=·asset.getObjectByName('mixamorigHips')?.position.y; with ····const·motionHipsHeight·=·asset.getObjectByName('mixamorigHips')?.position.y
(prettier/prettier)
[error] 30-30: Replace ↹↹const·vrmHipsY·=·vrm.humanoid?.getNormalizedBoneNode('hips')?.getWorldPosition(_vec3).y; with ····const·vrmHipsY·=·vrm.humanoid⏎······?.getNormalizedBoneNode('hips')⏎······?.getWorldPosition(_vec3).y
(prettier/prettier)
[error] 31-31: Replace ↹↹const·vrmRootY·=·vrm.scene.getWorldPosition(_vec3).y; with ····const·vrmRootY·=·vrm.scene.getWorldPosition(_vec3).y
(prettier/prettier)
[error] 32-32: Replace ↹↹const·vrmHipsHeight·=·Math.abs(vrmHipsY!·-·vrmRootY); with ····const·vrmHipsHeight·=·Math.abs(vrmHipsY!·-·vrmRootY)
(prettier/prettier)
[error] 33-33: Replace ↹↹const·hipsPositionScale·=·vrmHipsHeight·/·motionHipsHeight!; with ····const·hipsPositionScale·=·vrmHipsHeight·/·motionHipsHeight!
(prettier/prettier)
[error] 35-35: Replace ↹↹ with ····
(prettier/prettier)
[error] 36-37: Replace ⏎↹↹↹ with ······
(prettier/prettier)
[error] 38-38: Replace ↹↹↹const·trackSplitted·=·track.name.split('.'); with ······const·trackSplitted·=·track.name.split('.')
(prettier/prettier)
[error] 39-39: Replace ↹↹↹const·mixamoRigName:·MixamoVRMRigMapIndex·=·trackSplitted[0]·as·MixamoVRMRigMapIndex; with ······const·mixamoRigName:·MixamoVRMRigMapIndex·=⏎········trackSplitted[0]·as·MixamoVRMRigMapIndex
(prettier/prettier)
[error] 40-40: Replace ↹↹↹const·vrmBoneName:·VRMHumanBoneName·=·mixamoVRMRigMap[mixamoRigName]·as·VRMHumanBoneName; with ······const·vrmBoneName:·VRMHumanBoneName·=·mixamoVRMRigMap[⏎········mixamoRigName⏎······]·as·VRMHumanBoneName
(prettier/prettier)
[error] 41-41: Replace ↹↹↹const·vrmNodeName·=·vrm.humanoid?.getNormalizedBoneNode(vrmBoneName)?.name; with ······const·vrmNodeName·=·vrm.humanoid?.getNormalizedBoneNode(vrmBoneName)?.name
(prettier/prettier)
[error] 42-42: Replace ↹↹↹const·mixamoRigNode·=·asset.getObjectByName(mixamoRigName); with ······const·mixamoRigNode·=·asset.getObjectByName(mixamoRigName)
(prettier/prettier)
[error] 44-44: Replace ↹↹↹ with ······
(prettier/prettier)
[error] 45-46: Replace ⏎↹↹↹↹const·propertyName·=·trackSplitted[1]; with ········const·propertyName·=·trackSplitted[1]
(prettier/prettier)
[error] 48-48: Replace ↹↹↹↹ with ········
(prettier/prettier)
[error] 49-49: Replace ↹↹↹↹mixamoRigNode?.getWorldQuaternion(restRotationInverse).invert(); with ········mixamoRigNode?.getWorldQuaternion(restRotationInverse).invert()
(prettier/prettier)
[error] 50-50: Replace ↹↹↹↹mixamoRigNode?.parent?.getWorldQuaternion(parentRestWorldRotation); with ········mixamoRigNode?.parent?.getWorldQuaternion(parentRestWorldRotation)
(prettier/prettier)
[error] 52-52: Replace ↹↹↹↹ with ········
(prettier/prettier)
[error] 53-54: Replace ⏎↹↹↹↹↹ with ··········
(prettier/prettier)
[error] 55-55: Replace ↹↹↹↹↹ with ··········
(prettier/prettier)
[error] 56-57: Replace ⏎↹↹↹↹↹↹const·flatQuaternion·=·track.values.slice(i,·i·+·4); with ············const·flatQuaternion·=·track.values.slice(i,·i·+·4)
(prettier/prettier)
[error] 59-59: Replace ↹↹↹↹↹↹_quatA.fromArray(flatQuaternion); with ············_quatA.fromArray(flatQuaternion)
(prettier/prettier)
[error] 61-61: Replace ↹↹↹↹↹↹ with ············
(prettier/prettier)
[error] 62-62: Replace ↹↹↹↹↹↹ with ············
(prettier/prettier)
[error] 63-63: Replace ↹↹↹↹↹↹↹ with ··············
(prettier/prettier)
[error] 64-64: Replace ↹↹↹↹↹↹↹.multiply(restRotationInverse); with ··············.multiply(restRotationInverse)
(prettier/prettier)
[error] 66-66: Replace ↹↹↹↹↹↹_quatA.toArray(flatQuaternion); with ············_quatA.toArray(flatQuaternion)
(prettier/prettier)
[error] 68-68: Replace ↹↹↹↹↹↹ with ············
(prettier/prettier)
[error] 69-70: Replace ⏎↹↹↹↹↹↹↹track.values[index·+·i]·=·v; with ··············track.values[index·+·i]·=·v
(prettier/prettier)
[error] 71-73: Replace ⏎↹↹↹↹↹↹});⏎ with ············})
(prettier/prettier)
[error] 74-74: Replace ↹↹↹↹↹ with ··········
(prettier/prettier)
[error] 76-76: Replace ↹↹↹↹↹ with ··········
(prettier/prettier)
[error] 77-77: Replace ↹↹↹↹↹↹ with ············
(prettier/prettier)
[error] 78-78: Replace ↹↹↹↹↹↹↹ with ··············
(prettier/prettier)
[error] 79-79: Replace ↹↹↹↹↹↹↹ with ··············
(prettier/prettier)
[error] 80-80: Replace ↹↹↹↹↹↹↹track.values.map((v,·i)·=>·(vrm.meta?.metaVersion·===·'0'·&&·i·%·2·===·0·?·-·v·:·v)), with ··············track.values.map((v,·i)·=>⏎················vrm.meta?.metaVersion·===·'0'·&&·i·%·2·===·0·?·-v·:·v
(prettier/prettier)
[error] 81-81: Replace ↹↹↹↹↹↹), with ··············)
(prettier/prettier)
[error] 82-82: Replace ↹↹↹↹↹); with ············)
(prettier/prettier)
[error] 83-83: Insert ··········)
(prettier/prettier)
[error] 84-84: Replace ↹↹↹↹ with ········
(prettier/prettier)
[error] 85-86: Replace ⏎↹↹↹↹↹const·value·=·track.values.map((v,·i)·=>·(vrm.meta?.metaVersion·===·'0'·&&·i·%·3·!==·1·?·-·v·:·v)·*·hipsPositionScale); with ··········const·value·=·track.values.map(⏎············(v,·i)·=>⏎··············(vrm.meta?.metaVersion·===·'0'·&&·i·%·3·!==·1·?·-v·:·v)·*⏎··············hipsPositionScale⏎··········)
(prettier/prettier)
[error] 87-87: Replace ↹↹↹↹↹tracks.push(new·THREE.VectorKeyframeTrack(${vrmNodeName}.${propertyName},·track.times,·value)); with ··········tracks.push(⏎············new·THREE.VectorKeyframeTrack(⏎··············${vrmNodeName}.${propertyName},⏎··············track.times,⏎··············value⏎············)⏎··········)
(prettier/prettier)
[error] 88-89: Replace ⏎↹↹↹↹ with ········
(prettier/prettier)
[error] 90-91: Replace ⏎↹↹↹ with ······
(prettier/prettier)
[error] 92-93: Replace ⏎↹↹}); with ····})
(prettier/prettier)
[error] 95-96: Replace ↹↹return·new·THREE.AnimationClip('vrmAnimation',·clip.duration,·tracks);⏎ with ····return·new·THREE.AnimationClip('vrmAnimation',·clip.duration,·tracks)
(prettier/prettier)
[error] 97-98: Replace ↹});⏎ with ··})
(prettier/prettier)
🪛 GitHub Actions: Lint and Format
src/lib/fbxAnimation/mixamoVRMRigMap.ts
[error] 5-60: Prettier formatting errors detected. Please run 'prettier --write' to fix code style issues.
src/lib/fbxAnimation/loadMixamoAnimation.ts
[error] 1-97: Prettier formatting errors detected. Please run 'prettier --write' to fix code style issues.
🪛 GitHub Actions: Run Tests
src/lib/fbxAnimation/mixamoVRMRigMap.ts
[error] 5-57: Prettier formatting errors detected. Multiple lines require indentation fixes and semicolon deletion. Run 'prettier --write' to fix code style issues.
[error] 60-60: Prettier formatting error: complex multiline replacement needed for string literals. Run 'prettier --write' to fix code style issues.
src/lib/fbxAnimation/loadMixamoAnimation.ts
[error] 1-97: Prettier formatting errors detected. Multiple lines require indentation and semicolon fixes. Run 'prettier --write' to fix code style issues.
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (4)
LICENSE (1)
22-23: フォーマットの改善を確認しましたボールド(太字)からイタリック(斜体)への変更により、非商用利用に関する注記の視認性が向上しています。
src/components/vrmViewer.tsx (1)
43-50: VRMAとFBXファイルのドラッグ&ドロップ処理が適切に実装されています既存のVRMファイル処理と同じパターンを使用して、.vrmaと.fbxファイルの処理が追加されています。Blobの作成、URLの生成、そして適切なローダーメソッドの呼び出しが正しく実装されています。
新しいアニメーションフォーマットに対応することで、ユーザーエクスペリエンスが向上します。
src/features/vrmViewer/model.ts (2)
63-73: アニメーションミキサーのリセット処理が適切に実装されていますアニメーションを読み込む前にミキサーをリセットすることで、複数のアニメーションが同時に適用される問題が解決されています。明示的に
action.play()を呼び出すことで、アニメーションの再生が確実に開始されるようになっています。コメント「reset animation mixer, otherwise funny merge」は実装の意図を明確に説明しています。
75-84: FBXアニメーション用の新しいメソッドが適切に実装されています
loadFbxAnimationメソッドはTHREE.AnimationClipを直接受け取り、VRMモデルに適用する機能を追加しています。前提条件のチェック、ミキサーのリセット、アニメーションアクションの作成と再生が適切に実装されています。このメソッドの追加により、Mixamoなどの外部ソースからのFBXアニメーションをVRMモデルに適用できるようになり、アニメーションの多様性が向上します。
| // Adjust with reference to hips height. | ||
| const motionHipsHeight = asset.getObjectByName('mixamorigHips')?.position.y; | ||
| const vrmHipsY = vrm.humanoid?.getNormalizedBoneNode('hips')?.getWorldPosition(_vec3).y; | ||
| const vrmRootY = vrm.scene.getWorldPosition(_vec3).y; | ||
| const vrmHipsHeight = Math.abs(vrmHipsY! - vrmRootY); | ||
| const hipsPositionScale = vrmHipsHeight / motionHipsHeight!; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ヌルチェック不足により実行時例外のリスクがあります
motionHipsHeight!・vrmHipsY! とノン null アサーションを多用していますが、FBX/VRM に期待したボーンが無い場合 undefined が入り計算時に NaN になります。例外化 or デフォルト値代入を検討してください。
-const motionHipsHeight = asset.getObjectByName('mixamorigHips')?.position.y;
+const motionHipsHeight =
+ asset.getObjectByName('mixamorigHips')?.position.y ?? 1; // 1 など安全なデフォルト📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Adjust with reference to hips height. | |
| const motionHipsHeight = asset.getObjectByName('mixamorigHips')?.position.y; | |
| const vrmHipsY = vrm.humanoid?.getNormalizedBoneNode('hips')?.getWorldPosition(_vec3).y; | |
| const vrmRootY = vrm.scene.getWorldPosition(_vec3).y; | |
| const vrmHipsHeight = Math.abs(vrmHipsY! - vrmRootY); | |
| const hipsPositionScale = vrmHipsHeight / motionHipsHeight!; | |
| // Adjust with reference to hips height. | |
| - const motionHipsHeight = asset.getObjectByName('mixamorigHips')?.position.y; | |
| + const motionHipsHeight = | |
| + asset.getObjectByName('mixamorigHips')?.position.y ?? 1; // 1 など安全なデフォルト | |
| const vrmHipsY = vrm.humanoid?.getNormalizedBoneNode('hips')?.getWorldPosition(_vec3).y; | |
| const vrmRootY = vrm.scene.getWorldPosition(_vec3).y; | |
| const vrmHipsHeight = Math.abs(vrmHipsY! - vrmRootY); | |
| const hipsPositionScale = vrmHipsHeight / motionHipsHeight!; |
🧰 Tools
🪛 ESLint
[error] 28-28: Replace ↹↹ with ····
(prettier/prettier)
[error] 29-29: Replace ↹↹const·motionHipsHeight·=·asset.getObjectByName('mixamorigHips')?.position.y; with ····const·motionHipsHeight·=·asset.getObjectByName('mixamorigHips')?.position.y
(prettier/prettier)
[error] 30-30: Replace ↹↹const·vrmHipsY·=·vrm.humanoid?.getNormalizedBoneNode('hips')?.getWorldPosition(_vec3).y; with ····const·vrmHipsY·=·vrm.humanoid⏎······?.getNormalizedBoneNode('hips')⏎······?.getWorldPosition(_vec3).y
(prettier/prettier)
[error] 31-31: Replace ↹↹const·vrmRootY·=·vrm.scene.getWorldPosition(_vec3).y; with ····const·vrmRootY·=·vrm.scene.getWorldPosition(_vec3).y
(prettier/prettier)
[error] 32-32: Replace ↹↹const·vrmHipsHeight·=·Math.abs(vrmHipsY!·-·vrmRootY); with ····const·vrmHipsHeight·=·Math.abs(vrmHipsY!·-·vrmRootY)
(prettier/prettier)
[error] 33-33: Replace ↹↹const·hipsPositionScale·=·vrmHipsHeight·/·motionHipsHeight!; with ····const·hipsPositionScale·=·vrmHipsHeight·/·motionHipsHeight!
(prettier/prettier)
🤖 Prompt for AI Agents
In src/lib/fbxAnimation/loadMixamoAnimation.ts around lines 28 to 34, the code
uses non-null assertions on motionHipsHeight and vrmHipsY without checking if
these values are undefined, which can cause runtime exceptions or NaN results if
the expected bones are missing. Add null checks before using these values and
handle the cases where they are undefined by either throwing a clear error or
assigning safe default values to prevent calculation errors.
Deploying aituber-kit-docs with
|
| Latest commit: |
6322d8f
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://96604069.aituber-kit-4s1.pages.dev |
| Branch Preview URL: | https://feature-add-animation-vrma.aituber-kit-4s1.pages.dev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🔭 Outside diff range comments (1)
src/features/vrmViewer/model.ts (1)
68-73: 🛠️ Refactor suggestionVRM アンロード時にアクション/ミキサーをクリアしないとメモリリークの恐れ
unLoadVrm()ではvrm.sceneの dispose のみ行っていますが、
_animationActionsやmixerに残ったAnimationActionが
破棄されず Three.js 側リソースが解放されません。this.mixer?.stopAllAction() this._animationActions.clear() this.mixer = undefined等でクリアすることを推奨します。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 Files selected for processing (3)
src/features/messages/speakCharacter.ts(2 hunks)src/features/vrmViewer/model.ts(4 hunks)src/features/vrmViewer/viewer.ts(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/features/vrmViewer/viewer.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/features/vrmViewer/model.ts (3)
src/lib/VRMAnimation/VRMAnimation.ts (1)
VRMAnimation(4-116)src/utils/buildUrl.ts (1)
buildUrl(7-22)src/lib/VRMAnimation/loadVRMAnimation.ts (1)
loadVRMAnimation(8-17)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
|
|
||
| // VRMの場合、ここで感情アニメーションを再生 | ||
| if (ss.modelType === 'vrm') { | ||
| if (hs.viewer && talk.emotion && talk.emotion !== 'neutral') { | ||
| // neutral は常時アイドルアニメーションが流れているため、個別に再生しない | ||
| hs.viewer.playEmotionAnimation(talk.emotion, 0.3, false) | ||
| console.log(`VRM emotion animation started: ${talk.emotion}`) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
音声再生タイミングと感情アニメーション開始がズレる恐れがあります
speakQueue.addTask で音声再生タスクを登録した直後にアニメーションを開始していますが、
キューにすでに別タスクが存在する場合は実際の音声再生までラグが生じます。
その間に感情アニメーションが終了・ループアウトしてしまい、口パクと表情が同期しません。
音声バッファの再生開始コールバック(onStart や speakQueue 側イベント)で
playEmotionAnimation を呼び出すようにすると、同期ズレを防げます。
例:
- hs.viewer.playEmotionAnimation(talk.emotion, 0.3, false)
+ guardedOnCompleteEmotion = () => {
+ hs.viewer?.playEmotionAnimation(talk.emotion, 0.3, false)
+ }
+ speakQueue.addTask({ ..., onStart: guardedOnStart, onComplete: guardedOnCompleteEmotion })ご検討ください。
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/features/messages/speakCharacter.ts around lines 304 to 312, the emotion
animation starts immediately after adding the speech playback task, which can
cause timing mismatches if there are queued tasks. To fix this, move the call to
playEmotionAnimation into the speech playback start callback (such as onStart or
a speakQueue event) so the animation begins exactly when the audio starts
playing, ensuring lip-sync and emotion animation stay synchronized.
| // 1) 以前のアクションがまだ残っていれば停止してウェイトを 0 にする | ||
| if (this._currentAction && this._currentAction !== nextAction) { | ||
| this._currentAction.stop() | ||
| } | ||
|
|
||
| // 2) 次アクションを有効化しウェイトを 1 に設定して即時反映 (T ポーズ防止) | ||
| nextAction.enabled = true | ||
| nextAction.setEffectiveWeight(0.05) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
即時切替時のウェイトが 0.05 のまま固定される
crossFadeToAnimation でクロスフェード不可の場合、
nextAction.setEffectiveWeight(0.05)としていますが、その後ウェイトを 1 に設定し直していません。
結果としてアニメーションが常に 5% の強さで再生され、
期待通りのポーズにならない可能性があります。
- nextAction.setEffectiveWeight(0.05)
+ nextAction.setEffectiveWeight(1.0)などで修正してください。
🤖 Prompt for AI Agents
In src/features/vrmViewer/model.ts around lines 190 to 198, after setting the
nextAction's effective weight to 0.05 for immediate reflection, you need to
reset the weight back to 1 to ensure the animation plays at full strength. Fix
this by adding a call to setEffectiveWeight(1) on nextAction after the initial
0.05 setting to properly finalize the weight adjustment.
| private _animationActions: Map<string, THREE.AnimationAction> = new Map() | ||
| private _currentAction: THREE.AnimationAction | null = null | ||
| private _idleAnimationName: string = 'idle_default' // Default idle animation name | ||
|
|
||
| // 仮の感情アニメーションマッピング (パスはsettingsStoreなどから取得することを想定) | ||
| private _emotionAnimationPaths: Map<string, string> = new Map([ | ||
| ['neutral', '/idle_loop.vrma'], // デフォルトアイドルをneutralとする | ||
| ['happy', '/vrma/happy.vrma'], // TODO: 実際のパスに置き換える | ||
| ['sad', '/vrma/sad.vrma'], // TODO: 実際のパスに置き換える | ||
| ['angry', '/vrma/angry.vrma'], // TODO: 実際のパスに置き換える | ||
| // 他の感情も追加可能 | ||
| ]) | ||
| private _isEmotionAnimating: boolean = false | ||
| private _watchingEmotionAction: THREE.AnimationAction | null = null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
アイドルアニメーション名の不一致による状態判定ミス
デフォルト値 _idleAnimationName = 'idle_default' に対し、
_emotionAnimationPaths では 'neutral' をアイドル扱いでロードしています。
このままでは
crossFadeToAnimation内の
name !== this._idleAnimationName判定が常に真になり、
neutral 再生時でも_isEmotionAnimatingがtrueになる- idle へ戻す際に
'idle_default'が存在せずフェードバックできない
という不具合が生じます。
どちらかに統一、もしくは loadAllEmotionAnimations 後に
this.setIdleAnimationName('neutral')を呼び出してください。
🤖 Prompt for AI Agents
In src/features/vrmViewer/model.ts around lines 24 to 37, the default idle
animation name '_idleAnimationName' is set to 'idle_default', but the
'_emotionAnimationPaths' map uses 'neutral' as the idle animation key, causing
mismatches in state checks and animation transitions. To fix this, unify the
idle animation name by either changing '_idleAnimationName' to 'neutral' or,
after loading all emotion animations, call a method like 'setIdleAnimationName'
with 'neutral' to synchronize the idle animation name with the loaded
animations, ensuring correct state detection and smooth transitions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/features/vrmViewer/model.ts (1)
157-203:⚠️ Potential issueクロスフェード機能の実装
クロスフェードでアニメーションを切り替える機能が実装されています。しかし、以前のレビューで指摘された問題が修正されていません。
クロスフェード不可の場合に
nextAction.setEffectiveWeight(0.05)としていますが、その後ウェイトを1に設定し直していないため、アニメーションが常に5%の強さで再生される可能性があります。次のように修正してください:
nextAction.setEffectiveWeight(0.05) + nextAction.setEffectiveWeight(1.0)これによりアニメーションが正しい強度で再生されるようになります。
🧹 Nitpick comments (1)
src/features/vrmViewer/viewer.ts (1)
69-102: アイドルアニメーションの設定と読み込みロジックの実装VRMモデル読み込み後のアイドルアニメーション設定と読み込みロジックが適切に実装されています。エラーハンドリングも適切に行われています。
しかし、アニメーション読み込み失敗時にコンソールログのみでユーザーへの通知がない点は改善できます。
アニメーション読み込み失敗時にユーザーに何らかの通知を表示することを検討してください。例えば、エラーメッセージをUIに表示するなど。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 Files selected for processing (4)
src/features/messages/speakCharacter.ts(2 hunks)src/features/messages/speakQueue.ts(1 hunks)src/features/vrmViewer/model.ts(5 hunks)src/features/vrmViewer/viewer.ts(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/features/messages/speakCharacter.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/features/vrmViewer/viewer.ts (3)
src/utils/buildUrl.ts (1)
buildUrl(7-22)src/features/vrmViewer/model.ts (1)
Model(20-378)src/lib/VRMAnimation/loadVRMAnimation.ts (1)
loadVRMAnimation(8-17)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (15)
src/features/messages/speakQueue.ts (1)
161-162:switchToIdleAnimationの呼び出しが適切に追加されています
neutral感情を再生した後にアイドルアニメーションに戻るよう設定されているのは良い実装です。これにより、感情表現後に自然な状態へ戻ることが保証されます。src/features/vrmViewer/viewer.ts (7)
1-7: アニメーション関連の定数とインポートが適切に設定されていますデフォルトのアニメーションURLの設定とFBXアニメーション関連のコメントアウトについて、開発中の機能として明確に示されています。
17-32: アニメーション管理のためのプライベートフィールドが追加されていますアイドルアニメーション名とデフォルトパスの定義、現在のアニメーション状態を追跡するためのフィールドが適切に追加されています。
111-118: アンロード処理でアニメーションがリセットされるようになりましたVRMモデルのアンロード時にアニメーションをリセットし、モデル参照をクリアする処理が追加されました。これにより、メモリリークを防止し、次回のモデル読み込み時に問題が発生しにくくなります。
120-151: VRMAアニメーション読み込み機能の追加
loadVrmaメソッドの実装は適切であり、エラー処理も含まれています。モデルチェック、アニメーション名生成、読み込みと再生のロジックが適切に実装されています。
153-181: FBXアニメーション読み込み機能(未実装)FBXアニメーション読み込み機能が将来の実装のために準備されています。現時点では警告のみを表示し、実際の実装はコメントアウトされています。
実装予定のコードがコメントアウトされていますが、今後実装する際に適切なエラーハンドリングを含めることを推奨します。
FBXアニメーション実装時には、以前のレビューで指摘された非同期処理のエラーハンドリングを忘れずに追加してください。
183-197: 感情アニメーション再生機能の追加感情アニメーションを再生するためのメソッドが適切に実装されています。モデルがロードされていない場合の警告も含まれています。
286-296: アイドルアニメーションへの切り替え機能の追加アイドルアニメーションに切り替えるメソッドが適切に実装され、モデルがロードされていない場合の警告も含まれています。感情アニメーション後に自然な状態に戻るために必要な機能です。
src/features/vrmViewer/model.ts (7)
80-110: アニメーション読み込み機能の強化アニメーション名の指定とマップへの保存機能が追加され、既存のアニメーションの上書き時の処理も適切に実装されています。再生制御が分離されたことで柔軟性が向上しています。
112-129: FBXアニメーション読み込み機能の追加FBXアニメーションを読み込むための基本機能が実装されています。THREE.AnimationClipからアクションを作成し、名前を付けて保存する処理が適切に実装されています。
134-150: アニメーション再生機能の追加クロスフェードなしでアニメーションを再生する機能が適切に実装されています。現在のアクションが存在する場合は停止してから新しいアクションを再生する処理も適切です。
208-228: アニメーション停止とリセット機能の実装現在のアニメーションを停止する機能とすべてのアニメーションをリセットする機能が適切に実装されています。フェードアウト処理や状態フラグのリセットも適切に行われています。
274-300: 感情アニメーションの一括読み込み機能定義された感情アニメーションをすべて読み込む機能が適切に実装され、エラーハンドリングも含まれています。各アニメーションのロード失敗に対して適切なエラーログが出力されるため、デバッグが容易になります。
306-328: 感情アニメーションの再生機能感情アニメーションを再生する機能が適切に実装されています。アイドルアニメーションや指定された感情アニメーションが存在しない場合の警告も含まれています。
345-377: アイドルアニメーションへの復帰機能アイドルアニメーションに戻る機能が適切に実装されています。必要な条件チェックと状態フラグのリセットが含まれており、既にアイドルアニメーションが再生中の場合は不要な遷移を避ける処理も適切です。
| private _animationActions: Map<string, THREE.AnimationAction> = new Map() | ||
| private _currentAction: THREE.AnimationAction | null = null | ||
| private _idleAnimationName: string = 'idle_default' // Default idle animation name | ||
|
|
||
| // 仮の感情アニメーションマッピング (パスはsettingsStoreなどから取得することを想定) | ||
| private _emotionAnimationPaths: Map<string, string> = new Map([ | ||
| ['neutral', '/idle_loop.vrma'], // デフォルトアイドルをneutralとする | ||
| ['happy', '/vrma/happy.vrma'], // TODO: 実際のパスに置き換える | ||
| ['sad', '/vrma/sad.vrma'], // TODO: 実際のパスに置き換える | ||
| ['angry', '/vrma/angry.vrma'], // TODO: 実際のパスに置き換える | ||
| // 他の感情も追加可能 | ||
| ]) | ||
| private _isEmotionAnimating: boolean = false | ||
| private _watchingEmotionAction: THREE.AnimationAction | null = null | ||
| private _emotionFadeOutThreshold: number = 0.5 // 終了の何秒前にアイドルへ移行するか | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
アニメーション管理用のフィールドとマッピングの追加
アニメーション管理のための各種フィールドが適切に追加されています。しかし、以前のレビューで指摘されたアイドルアニメーション名の不一致に関する問題が解決されていません。
_idleAnimationNameのデフォルト値は'idle_default'ですが、_emotionAnimationPathsでは'neutral'がアイドルアニメーションとして扱われています。これにより状態判定に問題が生じる可能性があります。
この不一致を解決するには、以下のいずれかの対応が必要です:
_emotionAnimationPathsの'neutral'を'idle_default'に変更する_idleAnimationNameのデフォルト値を'neutral'に変更するloadAllEmotionAnimations後にsetIdleAnimationName('neutral')を呼び出す
特にcrossFadeToAnimation内のname !== this._idleAnimationName判定に影響するため、修正が必要です。
🤖 Prompt for AI Agents
In src/features/vrmViewer/model.ts around lines 24 to 39, there is a mismatch
between the default idle animation name '_idleAnimationName' set to
'idle_default' and the 'neutral' key used in '_emotionAnimationPaths' as the
idle animation. This inconsistency can cause issues in state checks like in
'crossFadeToAnimation'. To fix this, choose one approach: either change the
'neutral' key in '_emotionAnimationPaths' to 'idle_default', or update
'_idleAnimationName' to 'neutral', or after loading all emotion animations, call
'setIdleAnimationName' with 'neutral' to synchronize the idle animation name.
Implement one of these to ensure consistent idle animation naming across the
code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/features/vrmViewer/viewer.ts (1)
130-132: コメントアウトされたコードの意図が不明確です
loadVrmaメソッドでは現在のアニメーションURL・タイプを更新するコードがコメントアウトされていますが、一方でloadFbxメソッドでは同様のコードが有効になっています。この非一貫性の理由を明記するか、統一することをお勧めします。コメントを追加して理由を説明するか、一貫性を持たせるために以下のいずれかの対応を検討してください:
- // this._currentAnimationUrl = url // 最後にロードしたURLを維持する必要性は減りました。 - // this._currentAnimationType = 'vrma' + /* + * Note: FBXと異なり、VRMAではcurrentAnimationを追跡しません。 + * これは... [理由を追加] + */または両方のメソッドで同じ動作にする:
- // this._currentAnimationUrl = url // 最後にロードしたURLを維持する必要性は減りました。 - // this._currentAnimationType = 'vrma' + this._currentAnimationUrl = url + this._currentAnimationType = 'vrma'
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 Files selected for processing (1)
src/features/vrmViewer/viewer.ts(6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (6)
src/features/vrmViewer/viewer.ts (6)
169-186: 非同期処理のエラーハンドリングを適切に実装していますFBXアニメーション読み込み処理で、前回のレビューで指摘されていたエラーハンドリングが正しく実装されており、良い改善です。catch ブロックでエラーログを出力することで、問題発生時のデバッグがしやすくなっています。
78-96: デフォルトアニメーション読み込みの例外処理が適切に実装されていますデフォルトのVRMAアニメーション読み込み処理で、try-catchによる例外処理が適切に実装されています。また、成功・失敗時のログ出力も明確で、デバッグに役立つ情報が提供されています。
114-117: VRMモデル解放時のリソース管理が改善されています
unloadVRMメソッドで、アニメーションのリセットとモデル参照のクリアが追加されており、リソース管理が適切に行われるようになっています。これはメモリリークを防ぐために重要な改善です。
190-202: 感情アニメーションのAPIが明確に定義されています
playEmotionAnimationメソッドは、パラメーターの型とデフォルト値が明確に定義されており、JSDocコメントによる説明も十分です。また、モデルが読み込まれていない場合の警告処理も適切に実装されています。
292-302: アイドル状態への復帰機能が適切に実装されています
switchToIdleAnimationメソッドにより、アニメーションをアイドル状態に戻す機能が追加されました。クロスフェードのデフォルト値が提供されており、モデル未読み込み時の警告処理も適切です。
17-19: アイドルアニメーション設定の明示的な定義が良い設計ですアイドルアニメーションの名前とパスをクラスのプライベートフィールドとして明示的に定義することで、コード全体での一貫性が確保されています。これはメンテナンス性を高める良い設計です。
|
src/features/messages/speakQueue.ts このファイルに設定されているQUEUE_CHECK_DELAYの値ですが、設定画面から指定できるようにしてほしいです。 |
|
Claude finished @tegnike's task —— View job Task: Make QUEUE_CHECK_DELAY configurable from settings screen ✅Todo List:
SummarySuccessfully implemented the requested feature to make QUEUE_CHECK_DELAY configurable from the settings screen: Changes Made:
Features:
The setting can be found in Settings → Other → Advanced Settings section. Users can now adjust how long the system waits before resetting to neutral expression after the speech queue becomes empty. |
- Add queueCheckDelay setting to settings store (default: 1500ms) - Add range input control in advanced settings UI (500-5000ms range) - Update speakQueue to use configurable value instead of hardcoded constant - Allow users to adjust timing of neutral expression reset after speech queue empties Co-authored-by: tegnike <tegnike@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/settings/advancedSettings.tsx(2 hunks)src/features/messages/speakQueue.ts(2 hunks)src/features/stores/settings.ts(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/features/messages/speakQueue.ts
🧰 Additional context used
🪛 GitHub Actions: Run Tests
src/components/settings/advancedSettings.tsx
[error] 83-83: Prettier formatting error: Replace line break and indentation with 'Queue Check Delay'.
[error] 87-87: Prettier formatting error: Insert line break and indentation.
🪛 GitHub Actions: Lint and Format
src/components/settings/advancedSettings.tsx
[error] 83-83: Prettier formatting error: Replace line break and indentation with 'Queue Check Delay'.
[error] 87-87: Prettier formatting error: Insert line break and indentation.
🔇 Additional comments (4)
src/features/stores/settings.ts (3)
179-179: 設定インターフェースへの追加が適切です
queueCheckDelayプロパティの追加により、キューチェック遅延が設定可能になりました。他の数値設定と一貫した型定義です。
415-416: 環境変数の処理が適切です環境変数
NEXT_PUBLIC_QUEUE_CHECK_DELAYからの値解析とデフォルト値1500msの設定が、他の数値設定と同じパターンで実装されています。
593-593: 永続化設定が適切に追加されています
partialize関数にqueueCheckDelayが含まれ、設定の保存・復元が正しく動作します。src/components/settings/advancedSettings.tsx (1)
13-13: 状態セレクターが適切に追加されています
queueCheckDelayの状態セレクターが他の設定と同じパターンで実装されています。
| <div className="my-6"> | ||
| <div className="my-4 text-xl font-bold"> | ||
| Queue Check Delay | ||
| </div> | ||
| <div className="my-4 text-base whitespace-pre-line"> | ||
| Time to wait before resetting to neutral expression after speech queue becomes empty (in milliseconds). | ||
| </div> | ||
| <div className="select-none"> | ||
| Queue Check Delay: {queueCheckDelay}ms | ||
| </div> | ||
| <input | ||
| type="range" | ||
| min="500" | ||
| max="5000" | ||
| step="100" | ||
| value={queueCheckDelay} | ||
| onChange={(e) => | ||
| settingsStore.setState({ | ||
| queueCheckDelay: parseInt(e.target.value), | ||
| }) | ||
| } | ||
| className="mt-2 mb-4 input-range" | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
フォーマット、国際化、アクセシビリティの問題を修正してください
以下の問題があります:
- Prettierフォーマットエラー
- ハードコードされた英語テキスト
- アクセシビリティ属性の不足
以下の修正を適用してください:
</div>
- <div className="my-6">
- <div className="my-4 text-xl font-bold">
- Queue Check Delay
- </div>
- <div className="my-4 text-base whitespace-pre-line">
- Time to wait before resetting to neutral expression after speech queue becomes empty (in milliseconds).
- </div>
+ <div className="my-6">
+ <div className="my-4 text-xl font-bold">{t('QueueCheckDelay')}</div>
+ <div className="my-4 text-base whitespace-pre-line">
+ {t('QueueCheckDelayInfo')}
+ </div>
<div className="select-none">
- Queue Check Delay: {queueCheckDelay}ms
+ {t('QueueCheckDelay')}: {queueCheckDelay}ms
</div>
<input
type="range"
min="500"
max="5000"
step="100"
value={queueCheckDelay}
+ aria-label={t('QueueCheckDelay')}
+ aria-describedby="queue-delay-description"
onChange={(e) =>
settingsStore.setState({
queueCheckDelay: parseInt(e.target.value),
})
}
className="mt-2 mb-4 input-range"
/>
</div>翻訳キーを国際化ファイルに追加する必要があります:
QueueCheckDelay: "キューチェック遅延"QueueCheckDelayInfo: "音声キューが空になった後、ニュートラル表情にリセットするまでの待機時間(ミリ秒)。"
Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 GitHub Actions: Run Tests
[error] 83-83: Prettier formatting error: Replace line break and indentation with 'Queue Check Delay'.
[error] 87-87: Prettier formatting error: Insert line break and indentation.
🪛 GitHub Actions: Lint and Format
[error] 83-83: Prettier formatting error: Replace line break and indentation with 'Queue Check Delay'.
[error] 87-87: Prettier formatting error: Insert line break and indentation.
🤖 Prompt for AI Agents
In src/components/settings/advancedSettings.tsx around lines 82 to 105, fix the
formatting issues by running Prettier or adjusting spacing and indentation.
Replace all hardcoded English text with internationalized strings by using
translation keys like 'QueueCheckDelay' and 'QueueCheckDelayInfo' from the i18n
files. Add appropriate accessibility attributes such as aria-label or
aria-describedby to the input range element to improve accessibility for screen
readers.
- 設定値の単位をミリ秒から秒に変更(より直感的に) - 設定項目を「拡張設定」から「キャラクター設定」の最下部に移動 - デフォルト値を1.5秒に設定 - 内部処理では秒を自動的にミリ秒に変換
|
Claude finished @tegnike's task —— View job Pull Request ReviewReviewing PR #390: Feature/add animation vrma Todo List:
📋 Review SummaryThis PR successfully implements comprehensive VRM animation features including .vrma/.fbx file support, emotion animations, and configurable queue management. The implementation is well-structured but has several issues that should be addressed. ✅ Strengths
|
|
Claude finished @tegnike's task —— View job Pull Request Review
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/features/vrmViewer/model.ts (2)
24-39: アイドルアニメーション名の不一致による状態判定の問題
_idleAnimationNameのデフォルト値が'idle_default'なのに対し、_emotionAnimationPathsでは'neutral'がアイドルアニメーションとして扱われています。この不一致により、crossFadeToAnimation内の状態判定などで問題が発生します。以下のいずれかの対応が必要です:
_idleAnimationNameを'neutral'に変更loadAllEmotionAnimations後にsetIdleAnimationName('neutral')を呼び出す
273-281: 即時切替時のウェイトが0.05のまま固定される問題クロスフェードできない場合、
nextAction.setEffectiveWeight(0.05)でウェイトを設定していますが、その後1.0に戻していません。これによりアニメーションが5%の強度でしか再生されません。// 2) 次アクションを有効化しウェイトを 1 に設定して即時反映 (T ポーズ防止) nextAction.enabled = true - nextAction.setEffectiveWeight(0.05) + nextAction.setEffectiveWeight(1.0)
🧹 Nitpick comments (1)
src/features/vrmViewer/model.ts (1)
144-156: 未使用のメソッドが定義されています
removeUnnecessaryTranslationTracksメソッドが定義されていますが、コード内で一度も呼び出されていません。アニメーションの最適化に有用そうなので、アニメーション読み込み時に使用するか、不要なら削除することを検討してください。const clip = await loadVRMAnimationClip(url, this.vrm) if (!clip) { console.warn(`Failed to load animation clip from ${url}`) return undefined } + + // 不要なTranslationトラックを除去 + this.removeUnnecessaryTranslationTracks(clip)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/components/vrmViewer.tsx(1 hunks)src/features/vrmViewer/model.ts(5 hunks)src/features/vrmViewer/viewer.ts(8 hunks)src/lib/VRMAnimation/loadVRMAnimation.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/vrmViewer.tsx
- src/features/vrmViewer/viewer.ts
🧰 Additional context used
🧠 Learnings (1)
src/features/vrmViewer/model.ts (1)
Learnt from: tegnike
PR: tegnike/aituber-kit#0
File: :0-0
Timestamp: 2024-07-09T17:39:15.085Z
Learning: src/features/messages/messages.ts ファイルは、テキストを感情表現を含む Screenplay オブジェクトに変換する機能を持つ。
🧬 Code Graph Analysis (2)
src/lib/VRMAnimation/loadVRMAnimation.ts (2)
src/__mocks__/three/examples/jsm/loaders/GLTFLoader.js (1)
GLTFLoader(1-59)src/lib/VRMAnimation/VRMAnimation.ts (1)
VRMAnimation(4-116)
src/features/vrmViewer/model.ts (3)
src/lib/VRMAnimation/VRMAnimation.ts (1)
VRMAnimation(4-116)src/lib/VRMAnimation/loadVRMAnimation.ts (1)
loadVRMAnimationClip(26-43)src/utils/buildUrl.ts (1)
buildUrl(7-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: claude-review
🔇 Additional comments (7)
src/lib/VRMAnimation/loadVRMAnimation.ts (2)
1-10: インポートとローダー登録が適切に実装されていますVRMLoaderPluginの追加とGLTFLoaderへの登録が正しく行われています。
23-43: VRMAアニメーションの読み込み関数が適切に実装されています非同期処理、エラーハンドリング、null安全性が考慮された実装になっています。
src/features/vrmViewer/model.ts (5)
78-101: loadAnimationメソッドが適切に更新されていますオプショナルなアニメーション名パラメータの追加、エラーハンドリング、既存アニメーションの上書き警告など、適切に実装されています。
161-193: 足の地面固定処理が適切に実装されていますワールド座標での判定とローカル座標での調整が正しく行われており、アニメーション中の足の沈み込みを防ぐ実装になっています。
357-377: 感情アニメーションの一括読み込みが適切に実装されています各アニメーションの読み込みエラーが個別に処理され、一つの失敗が全体に影響しない実装になっています。
416-419: アニメーション更新後の足固定処理が適切に追加されていますアニメーション実行中のみ足の地面固定処理を適用する実装になっており、パフォーマンスへの配慮もされています。
428-460: アイドルアニメーションへの復帰処理が適切に実装されています必要なチェックが行われ、既にアイドル状態の場合は無駄な遷移を避ける実装になっています。
| private _emotionAnimationPaths: Map<string, string> = new Map([ | ||
| ['neutral', '/neutral.vrma'], // デフォルトアイドルをneutralとする | ||
| ['happy', '/vrma/happy.vrma'], // TODO: 実際のパスに置き換える | ||
| ['sad', '/vrma/sad.vrma'], // TODO: 実際のパスに置き換える | ||
| ['angry', '/vrma/angry.vrma'], // TODO: 実際のパスに置き換える | ||
| // 他の感情も追加可能 | ||
| ]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
感情アニメーションパスをハードコードせず設定可能にすべきです
TODOコメントにもあるように、パスがハードコードされています。PRの目的である「設定画面から調整可能にする」という方針に合わせて、これらのパスも設定から取得できるようにすることを推奨します。
- // 仮の感情アニメーションマッピング (パスはsettingsStoreなどから取得することを想定)
- private _emotionAnimationPaths: Map<string, string> = new Map([
- ['neutral', '/neutral.vrma'], // デフォルトアイドルをneutralとする
- ['happy', '/vrma/happy.vrma'], // TODO: 実際のパスに置き換える
- ['sad', '/vrma/sad.vrma'], // TODO: 実際のパスに置き換える
- ['angry', '/vrma/angry.vrma'], // TODO: 実際のパスに置き換える
- // 他の感情も追加可能
- ])
+ private _emotionAnimationPaths: Map<string, string> = new Map()
+
+ // コンストラクタまたは初期化メソッドで設定から読み込む
+ public initializeEmotionPaths(emotionPaths: Record<string, string>) {
+ this._emotionAnimationPaths.clear()
+ Object.entries(emotionPaths).forEach(([emotion, path]) => {
+ this._emotionAnimationPaths.set(emotion, path)
+ })
+ }🤖 Prompt for AI Agents
In src/features/vrmViewer/model.ts around lines 29 to 35, the emotion animation
paths are currently hardcoded in a Map, which conflicts with the goal of making
these paths configurable via a settings screen. Refactor the code to load these
paths from a configuration source instead of hardcoding them, ensuring the Map
is populated dynamically based on user-adjustable settings.
| public playEmotionAnimation( | ||
| emotionName: string, | ||
| crossFadeDuration: number = 0.3 | ||
| ) { | ||
| if (!this._animationActions.has(this._idleAnimationName)) { | ||
| console.warn( | ||
| 'Idle animation is not loaded. Cannot properly manage emotion animations.' | ||
| ) | ||
| // アイドルがない場合は、単純に指定された感情アニメーションを再生するだけでも良いかもしれない | ||
| } | ||
|
|
||
| const emotionAction = this._animationActions.get(emotionName) | ||
| if (!emotionAction) { | ||
| console.warn(`Emotion animation "${emotionName}" not found.`) | ||
| return | ||
| } | ||
|
|
||
| console.log( | ||
| `[playEmotionAnimation] Starting emotion: "${emotionName}". Current action: "${this._currentAction?.getClip().name}", Crossfade duration: ${crossFadeDuration}` | ||
| ) | ||
| this.crossFadeToAnimation(emotionName, crossFadeDuration, THREE.LoopRepeat) | ||
| this._isEmotionAnimating = true // 感情アニメーション中フラグを立てる | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
感情アニメーションのループ設定に問題があります
Line 403でTHREE.LoopRepeatを使用していますが、感情アニメーションは通常一度だけ再生されるべきです。また、_isEmotionAnimatingフラグをtrueに設定していますが、アニメーション終了時にfalseに戻す仕組みがありません。
- this.crossFadeToAnimation(emotionName, crossFadeDuration, THREE.LoopRepeat)
+ this.crossFadeToAnimation(emotionName, crossFadeDuration, THREE.LoopOnce)アニメーション終了時の処理も追加する必要があります。
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public playEmotionAnimation( | |
| emotionName: string, | |
| crossFadeDuration: number = 0.3 | |
| ) { | |
| if (!this._animationActions.has(this._idleAnimationName)) { | |
| console.warn( | |
| 'Idle animation is not loaded. Cannot properly manage emotion animations.' | |
| ) | |
| // アイドルがない場合は、単純に指定された感情アニメーションを再生するだけでも良いかもしれない | |
| } | |
| const emotionAction = this._animationActions.get(emotionName) | |
| if (!emotionAction) { | |
| console.warn(`Emotion animation "${emotionName}" not found.`) | |
| return | |
| } | |
| console.log( | |
| `[playEmotionAnimation] Starting emotion: "${emotionName}". Current action: "${this._currentAction?.getClip().name}", Crossfade duration: ${crossFadeDuration}` | |
| ) | |
| this.crossFadeToAnimation(emotionName, crossFadeDuration, THREE.LoopRepeat) | |
| this._isEmotionAnimating = true // 感情アニメーション中フラグを立てる | |
| } | |
| public playEmotionAnimation( | |
| emotionName: string, | |
| crossFadeDuration: number = 0.3 | |
| ) { | |
| if (!this._animationActions.has(this._idleAnimationName)) { | |
| console.warn( | |
| 'Idle animation is not loaded. Cannot properly manage emotion animations.' | |
| ) | |
| // アイドルがない場合は、単純に指定された感情アニメーションを再生するだけでも良いかもしれない | |
| } | |
| const emotionAction = this._animationActions.get(emotionName) | |
| if (!emotionAction) { | |
| console.warn(`Emotion animation "${emotionName}" not found.`) | |
| return | |
| } | |
| console.log( | |
| `[playEmotionAnimation] Starting emotion: "${emotionName}". Current action: "${this._currentAction?.getClip().name}", Crossfade duration: ${crossFadeDuration}` | |
| ) | |
| - this.crossFadeToAnimation(emotionName, crossFadeDuration, THREE.LoopRepeat) | |
| + this.crossFadeToAnimation(emotionName, crossFadeDuration, THREE.LoopOnce) | |
| this._isEmotionAnimating = true // 感情アニメーション中フラグを立てる | |
| } |
🤖 Prompt for AI Agents
In src/features/vrmViewer/model.ts between lines 383 and 405, the
playEmotionAnimation method incorrectly uses THREE.LoopRepeat for emotion
animations, which should only play once. Change the loop mode to THREE.LoopOnce
to ensure the animation plays a single time. Additionally, add an event listener
or callback to detect when the emotion animation finishes and reset the
_isEmotionAnimating flag to false to properly track animation state.

Summary by CodeRabbit
新機能
バグ修正
その他