Skip to content

Conversation

@tegnike
Copy link
Owner

@tegnike tegnike commented May 15, 2025

Summary by CodeRabbit

  • 新機能

    • VRMビューアで.vrmaファイルのドラッグ&ドロップ対応を追加。
    • VRMアニメーションの管理機能を強化し、複数アニメーションのロード・切替・クロスフェード・感情アニメーション再生・足位置の地面補正などを実装。
    • VRMモデルにデフォルトのアイドルアニメーションと感情アニメーションを自動ロード。
    • 設定画面に「キューチェック遅延」スライダーを追加し、音声キュー終了後にニュートラル表情へ戻るまでの待機時間を調整可能に。
    • 「キューチェック遅延」関連の日本語・英語ローカライズ文言を追加。
  • バグ修正

    • 音声キューが空になった際、VRMモデルでニュートラル表情とアイドルアニメーションへ正しく戻るよう修正。
  • その他

    • ライセンス文書の注釈フォーマットをイタリック体に変更(内容自体は変更なし)。

@vercel
Copy link

vercel bot commented May 15, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
aituber-kit ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 30, 2025 0:40am

@coderabbitai
Copy link

coderabbitai bot commented May 15, 2025

Walkthrough

VRMアニメーション管理システムが大幅に拡張され、複数の感情アニメーションやアイドルアニメーションのロード・再生・クロスフェード・状態管理が可能になりました。また、VRMAファイルのドラッグ&ドロップ対応、設定画面での「Queue Check Delay」調整機能、ローカライズ対応、関連UIの追加も行われています。

Changes

Cohort / File(s) Change Summary
VRMアニメーション管理システム拡張
src/features/vrmViewer/model.ts, src/features/vrmViewer/viewer.ts
モデルクラスにアニメーション管理機能(複数アニメーションのロード・再生・クロスフェード・感情アニメーション・アイドル復帰・足位置制約など)を追加。Viewer側もデフォルトアイドルアニメーションや感情アニメーションのロード・再生・切り替えAPIを拡張。
VRMAファイル対応・ドラッグ&ドロップ
src/components/vrmViewer.tsx
.vrmaファイルのドラッグ&ドロップ入力をサポート。VRMAファイルをBlob経由でURL化し、loadVrmaメソッドで読み込む分岐を追加。
VRMアニメーションローダー拡張
src/lib/VRMAnimation/loadVRMAnimation.ts
VRMLoaderPlugin登録、loadVRMAnimationClip関数を新規追加。VRMアニメーションクリップのURLロード・エラーハンドリング・null返却対応。既存関数はほぼ変更なし。
音声再生時の感情アニメーション連動
src/features/messages/speakCharacter.ts
音声再生タスク追加後、モデル種がvrmかつ感情がニュートラル以外の場合、VRMビューアで該当感情アニメーションを再生。ログ出力追加。
音声キュー終了時のアイドル復帰・遅延設定
src/features/messages/speakQueue.ts, src/features/stores/settings.ts, src/components/settings/character.tsx
キュー終了時のニュートラル表現復帰遅延をsettingsStorequeueCheckDelayで管理。UIスライダー追加、設定値の保存・復元・環境変数初期化対応。キュー終了時にidleアニメーション復帰を明示的に呼び出し。
ローカライズ対応
locales/en/translation.json, locales/ja/translation.json
「Queue Check Delay」関連のラベル・説明・値表示用キーを英語・日本語で追加。既存キーの意味変更なし。
ライセンス表記フォーマット修正
LICENSE
非商用利用限定の注意書きのフォーマットを太字からイタリックに変更。文言やライセンス内容自体の変更なし。

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: クロスフェード再生
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • 話終わったあとに表情をneutralに戻す #207: VRMモデルの感情アニメーション再生やspeak queue管理の簡易実装に関するPR。今回のPRはより高度なアニメーション管理を実装しているが、感情アニメーション再生部分で関連性あり。

Suggested labels

devin

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/add-animation-vrma

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between a85c1c0 and 1969ae5.

📒 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モデルに適用できるようになり、アニメーションの多様性が向上します。

Comment on lines 28 to 34
// 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!;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

ヌルチェック不足により実行時例外のリスクがあります

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.

Suggested change
// 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.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented May 15, 2025

Deploying aituber-kit-docs with  Cloudflare Pages  Cloudflare Pages

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

View logs

Copy link

@coderabbitai coderabbitai bot left a 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 suggestion

VRM アンロード時にアクション/ミキサーをクリアしないとメモリリークの恐れ

unLoadVrm() では vrm.scene の dispose のみ行っていますが、
_animationActionsmixer に残った AnimationAction
破棄されず Three.js 側リソースが解放されません。

this.mixer?.stopAllAction()
this._animationActions.clear()
this.mixer = undefined

等でクリアすることを推奨します。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 1969ae5 and d1cf239.

📒 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

Comment on lines 304 to 312

// 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}`)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

音声再生タイミングと感情アニメーション開始がズレる恐れがあります

speakQueue.addTask で音声再生タスクを登録した直後にアニメーションを開始していますが、
キューにすでに別タスクが存在する場合は実際の音声再生までラグが生じます。
その間に感情アニメーションが終了・ループアウトしてしまい、口パクと表情が同期しません。

音声バッファの再生開始コールバック(onStartspeakQueue 側イベント)で
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.

Comment on lines +190 to +198
// 1) 以前のアクションがまだ残っていれば停止してウェイトを 0 にする
if (this._currentAction && this._currentAction !== nextAction) {
this._currentAction.stop()
}

// 2) 次アクションを有効化しウェイトを 1 に設定して即時反映 (T ポーズ防止)
nextAction.enabled = true
nextAction.setEffectiveWeight(0.05)
}
Copy link

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.

Comment on lines 24 to 37
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

アイドルアニメーション名の不一致による状態判定ミス

デフォルト値 _idleAnimationName = 'idle_default' に対し、
_emotionAnimationPaths では 'neutral' をアイドル扱いでロードしています。
このままでは

  1. crossFadeToAnimation 内の
    name !== this._idleAnimationName 判定が常に真になり、
    neutral 再生時でも _isEmotionAnimatingtrue になる
  2. 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.

Copy link

@coderabbitai coderabbitai bot left a 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)

📥 Commits

Reviewing files that changed from the base of the PR and between d1cf239 and 57230ca.

📒 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: アイドルアニメーションへの復帰機能

アイドルアニメーションに戻る機能が適切に実装されています。必要な条件チェックと状態フラグのリセットが含まれており、既にアイドルアニメーションが再生中の場合は不要な遷移を避ける処理も適切です。

Comment on lines 24 to 39
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 // 終了の何秒前にアイドルへ移行するか

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

アニメーション管理用のフィールドとマッピングの追加

アニメーション管理のための各種フィールドが適切に追加されています。しかし、以前のレビューで指摘されたアイドルアニメーション名の不一致に関する問題が解決されていません。

_idleAnimationNameのデフォルト値は'idle_default'ですが、_emotionAnimationPathsでは'neutral'がアイドルアニメーションとして扱われています。これにより状態判定に問題が生じる可能性があります。

この不一致を解決するには、以下のいずれかの対応が必要です:

  1. _emotionAnimationPathsの'neutral'を'idle_default'に変更する
  2. _idleAnimationNameのデフォルト値を'neutral'に変更する
  3. 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.

Copy link

@coderabbitai coderabbitai bot left a 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)

📥 Commits

Reviewing files that changed from the base of the PR and between 57230ca and 6322d8f.

📒 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: アイドルアニメーション設定の明示的な定義が良い設計です

アイドルアニメーションの名前とパスをクラスのプライベートフィールドとして明示的に定義することで、コード全体での一貫性が確保されています。これはメンテナンス性を高める良い設計です。

@tegnike
Copy link
Owner Author

tegnike commented May 30, 2025

@claude

src/features/messages/speakQueue.ts

このファイルに設定されているQUEUE_CHECK_DELAYの値ですが、設定画面から指定できるようにしてほしいです。

@claude
Copy link
Contributor

claude bot commented May 30, 2025

Claude finished @tegnike's task —— View job


Task: Make QUEUE_CHECK_DELAY configurable from settings screen ✅

Todo List:

  • Read repository setup instructions from CLAUDE.md
  • Examine current QUEUE_CHECK_DELAY implementation in speakQueue.ts
  • Analyze existing settings store structure
  • Analyze existing settings UI components
  • Add new queueCheckDelay setting to settings store
  • Update settings UI to include queue check delay configuration
  • Modify speakQueue.ts to use configurable value
  • Test the implementation (committed for testing)

Summary

Successfully implemented the requested feature to make QUEUE_CHECK_DELAY configurable from the settings screen:

Changes Made:

  1. Settings Store (src/features/stores/settings.ts):

    • Added queueCheckDelay: number to the General interface
    • Set default value to 1500ms (maintains existing behavior)
    • Added environment variable support: NEXT_PUBLIC_QUEUE_CHECK_DELAY
    • Added persistence support for the setting
  2. Advanced Settings UI (src/components/settings/advancedSettings.tsx):

    • Added a range input control (500ms - 5000ms, 100ms steps)
    • Added descriptive text explaining the setting's purpose
    • Real-time value display showing current delay in milliseconds
  3. Speech Queue Implementation (src/features/messages/speakQueue.ts):

    • Removed hardcoded QUEUE_CHECK_DELAY constant
    • Updated scheduleNeutralExpression() to read from settings store
    • Dynamic behavior changes take effect immediately

Features:

  • ✅ User-configurable delay through settings UI
  • ✅ Range: 500ms to 5000ms (0.5s to 5s)
  • ✅ Persistent storage (settings saved automatically)
  • ✅ Environment variable support for deployment configuration
  • ✅ Maintains backward compatibility (1500ms default)

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>
Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9aefb20 and 5cdd80b.

📒 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の状態セレクターが他の設定と同じパターンで実装されています。

Comment on lines 82 to 105
<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>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

フォーマット、国際化、アクセシビリティの問題を修正してください

以下の問題があります:

  1. Prettierフォーマットエラー
  2. ハードコードされた英語テキスト
  3. アクセシビリティ属性の不足

以下の修正を適用してください:

       </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
Copy link
Contributor

claude bot commented Jul 22, 2025

Claude finished @tegnike's task —— View job


I'll analyze this and get back to you.

@claude
Copy link
Contributor

claude bot commented Jul 29, 2025

Claude finished @tegnike's task —— View job


Pull Request Review

Reviewing PR #390: Feature/add animation vrma

Todo List:

  • Analyze PR scope and changed files overview
  • Review core VRM animation functionality (src/features/vrmViewer/)
  • Review new FBX animation utilities (src/lib/fbxAnimation/)
  • Review UI components and settings integration
  • Review message/speech handling changes
  • Check localization updates
  • Assess code quality and TypeScript usage
  • Identify potential bugs and security concerns
  • Evaluate performance considerations
  • Check test coverage
  • Provide comprehensive feedback summary

📋 Review Summary

This 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

  1. Architecture & Design

    • Clean separation of concerns between Model, Viewer, and UI components
    • Good use of TypeScript with proper type definitions
    • Well-structured animation management system with Map-based storage for O(1) lookups
    • Comprehensive Mixamo-to-VRM bone mapping
  2. Feature Implementation

    • Complete drag-and-drop support for .vrma and .fbx files
    • Robust crossfade animation system
    • Configurable queue check delay with UI controls
    • Proper integration with existing emotion system
    • Good error handling and logging throughout
  3. Code Quality

    • Consistent naming conventions and documentation
    • Proper resource management and cleanup
    • Good use of async/await patterns

⚠️ Critical Issues

1. Animation Weight Bug (High Priority)

File: src/features/vrmViewer/model.ts:198

nextAction.setEffectiveWeight(0.05)

Problem: When crossfade is not possible, animation weight is set to 0.05 but never reset to 1.0, causing animations to play at only 5% strength.

🔧 Fix Required:

nextAction.setEffectiveWeight(0.05)
nextAction.setEffectiveWeight(1.0)  // Add this line

2. Idle Animation Name Mismatch (High Priority)

Files: src/features/vrmViewer/model.ts:26 vs line 30

private _idleAnimationName: string = 'idle_default'  // Line 26
['neutral', '/idle_loop.vrma'],  // Line 30 - actual idle animation

Problem: State management inconsistency will cause crossFadeToAnimation logic to fail.

🔧 Fix Required: Either change _idleAnimationName to 'neutral' or update the mapping to use 'idle_default'.

3. Null Pointer Vulnerabilities (Medium Priority)

File: src/lib/fbxAnimation/loadMixamoAnimation.ts:27,32-33

const motionHipsHeight = asset.getObjectByName('mixamorigHips')?.position.y
const hipsPositionScale = vrmHipsHeight / motionHipsHeight!  // ❌ Unsafe!

Problem: Non-null assertions without proper checks can cause runtime crashes.

🔧 Fix Required: Add proper null checks with fallback values.

4. Inconsistent Implementation State (Medium Priority)

File: src/features/vrmViewer/viewer.ts:176-180

console.warn('loadMixamoAnimation is not implemented yet')  // ❌ But then uses it below
const clip = await loadMixamoAnimation(url, this.model.vrm)  // ✅ Actually implemented

Problem: Misleading warning message contradicts actual functionality.

🔧 Additional Issues

Code Quality Issues

  1. Memory Leak Risk: Missing animation cleanup in unLoadVrm() - should clear _animationActions and mixer
  2. Inconsistent State Tracking: VRMA vs FBX handling differs for _currentAnimationUrl
  3. Hardcoded Values: Animation paths scattered across multiple files

Performance Considerations

  1. ✅ Good: Efficient animation storage using Map
  2. ✅ Good: Proper Three.js resource management
  3. ⚠️ Minor: Could benefit from animation action pooling for frequently used animations

Security Assessment

  1. ✅ Good: File type validation for drag-and-drop
  2. ✅ Good: Proper blob URL handling
  3. ✅ Good: No direct user input injection vulnerabilities
  4. ⚠️ Minor: File size limits could be added for large animation files

📊 Test Coverage

Missing: No test files were added for the new animation functionality. Consider adding:

  • Unit tests for animation loading and crossfade logic
  • Integration tests for file drag-and-drop
  • Error handling tests for malformed animation files

🌐 Localization

Good: Proper i18n implementation for new settings, though UI components still contain some hardcoded English text that should be moved to translation files.

🏷️ TypeScript Usage

Excellent: Strong typing throughout with proper interfaces and type guards. The MixamoVRMRigMapIndex union type is particularly well done.


🎯 Recommended Actions

Must Fix Before Merge:

  1. Fix animation weight bug (lines 196-198 in model.ts)
  2. Resolve idle animation name mismatch
  3. Add null checks for bone references in FBX loader
  4. Remove misleading warning message in viewer.ts

Should Address:

  1. Add animation cleanup in unLoadVrm()
  2. Standardize animation URL/type tracking behavior
  3. Add basic unit tests for core animation functionality
  4. Move hardcoded English text to i18n files

Nice to Have:

  1. Add file size limits for drag-and-drop
  2. Implement animation action pooling
  3. Add loading states for animation operations

🔬 Detailed Technical Analysis

The implementation demonstrates solid understanding of Three.js animation systems and VRM specifications. The bone retargeting logic for Mixamo animations is mathematically sound and follows established patterns from three-vrm examples.

The crossfade system is well-architected but needs the weight bug fix. The emotion animation integration with speech synthesis is clever and should provide good user experience.

Overall, this is a significant and valuable feature addition that, with the critical bugs fixed, will greatly enhance the application's animation capabilities.


@claude
Copy link
Contributor

claude bot commented Jul 30, 2025

Claude finished @tegnike's task —— View job


Pull Request Review

Reviewing PR #390: Feature/add animation vrma

Todo List:

  • Analyze PR scope and changed files overview
  • Review core VRM animation functionality (src/features/vrmViewer/ files)
  • Review new FBX animation utilities (src/lib/fbxAnimation/ files)
  • Review UI components and settings integration
  • Review message/speech handling changes
  • Check localization updates
  • Assess code quality and TypeScript usage
  • Identify potential bugs and security concerns
  • Evaluate performance considerations
  • Check test coverage
  • Provide comprehensive feedback summary

📋 Review Summary

This 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

  1. Architecture & Design

    • Clean separation of concerns between Model, Viewer, and UI components
    • Good use of TypeScript with proper type definitions
    • Well-structured animation management system with Map-based storage for O(1) lookups
    • Comprehensive Mixamo-to-VRM bone mapping
  2. Feature Implementation

    • Complete drag-and-drop support for .vrma and .fbx files
    • Robust crossfade animation system
    • Configurable queue check delay with UI controls
    • Proper integration with existing emotion system
    • Good error handling and logging throughout
  3. Code Quality

    • Consistent naming conventions and documentation
    • Proper resource management and cleanup
    • Good use of async/await patterns

⚠️ Critical Issues

1. Animation Weight Bug (High Priority)

File: src/features/vrmViewer/model.ts:280

nextAction.setEffectiveWeight(0.05)

Problem: When crossfade is not possible, animation weight is set to 0.05 but never reset to 1.0, causing animations to play at only 5% strength.

🔧 Fix Required:

nextAction.setEffectiveWeight(0.05)
nextAction.setEffectiveWeight(1.0)  // Add this line

2. Idle Animation Name Mismatch (High Priority)

Files: src/features/vrmViewer/model.ts:26 vs line 30

private _idleAnimationName: string = 'idle_default'  // Line 26
['neutral', '/neutral.vrma'],  // Line 30 - actual idle animation

Problem: State management inconsistency will cause crossFadeToAnimation logic to fail.

🔧 Fix Required: Either change _idleAnimationName to 'neutral' or update the mapping to use 'idle_default'.

3. Null Pointer Vulnerabilities (Medium Priority)

File: src/lib/fbxAnimation/loadMixamoAnimation.ts:27,32-33

const motionHipsHeight = asset.getObjectByName('mixamorigHips')?.position.y
const hipsPositionScale = vrmHipsHeight / motionHipsHeight!  // ❌ Unsafe!

Problem: Non-null assertions without proper checks can cause runtime crashes.

🔧 Fix Required: Add proper null checks with fallback values.

4. Inconsistent Implementation State (Medium Priority)

File: src/features/vrmViewer/viewer.ts:176-180

console.warn('loadMixamoAnimation is not implemented yet')  // ❌ But then uses it below
const clip = await loadMixamoAnimation(url, this.model.vrm)  // ✅ Actually implemented

Problem: Misleading warning message contradicts actual functionality.

🔧 Additional Issues

Code Quality Issues

  1. Memory Leak Risk: Missing animation cleanup in unLoadVrm() - should clear _animationActions and mixer
  2. Inconsistent State Tracking: VRMA vs FBX handling differs for _currentAnimationUrl
  3. Hardcoded Values: Animation paths scattered across multiple files

Performance Considerations

  1. ✅ Good: Efficient animation storage using Map
  2. ✅ Good: Proper Three.js resource management
  3. ⚠️ Minor: Could benefit from animation action pooling for frequently used animations

Security Assessment

  1. ✅ Good: File type validation for drag-and-drop
  2. ✅ Good: Proper blob URL handling
  3. ✅ Good: No direct user input injection vulnerabilities
  4. ⚠️ Minor: File size limits could be added for large animation files

📊 Test Coverage

Missing: No test files were added for the new animation functionality. Consider adding:

  • Unit tests for animation loading and crossfade logic
  • Integration tests for file drag-and-drop
  • Error handling tests for malformed animation files

🌐 Localization

Good: Proper i18n implementation for new settings, though UI components still contain some hardcoded English text that should be moved to translation files.

🏷️ TypeScript Usage

Excellent: Strong typing throughout with proper interfaces and type guards. The MixamoVRMRigMapIndex union type is particularly well done.


🎯 Recommended Actions

Must Fix Before Merge:

  1. Fix animation weight bug (lines 196-198 in model.ts)
  2. Resolve idle animation name mismatch
  3. Add null checks for bone references in FBX loader
  4. Remove misleading warning message in viewer.ts

Should Address:

  1. Add animation cleanup in unLoadVrm()
  2. Standardize animation URL/type tracking behavior
  3. Add basic unit tests for core animation functionality
  4. Move hardcoded English text to i18n files

Nice to Have:

  1. Add file size limits for drag-and-drop
  2. Implement animation action pooling
  3. Add loading states for animation operations

🔬 Detailed Technical Analysis

The implementation demonstrates solid understanding of Three.js animation systems and VRM specifications. The bone retargeting logic for Mixamo animations is mathematically sound and follows established patterns from three-vrm examples.

The crossfade system is well-architected but needs the weight bug fix. The emotion animation integration with speech synthesis is clever and should provide good user experience.

Overall, this is a significant and valuable feature addition that, with the critical bugs fixed, will greatly enhance the application's animation capabilities.


Copy link

@coderabbitai coderabbitai bot left a 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内の状態判定などで問題が発生します。

以下のいずれかの対応が必要です:

  1. _idleAnimationName'neutral'に変更
  2. 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

📥 Commits

Reviewing files that changed from the base of the PR and between ef1bffd and c367445.

📒 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: アイドルアニメーションへの復帰処理が適切に実装されています

必要なチェックが行われ、既にアイドル状態の場合は無駄な遷移を避ける実装になっています。

Comment on lines +29 to +35
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: 実際のパスに置き換える
// 他の感情も追加可能
])
Copy link

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.

Comment on lines +383 to +405
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 // 感情アニメーション中フラグを立てる
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

感情アニメーションのループ設定に問題があります

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.

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants