chobit/shared/godot/avatar/expression_controller.gd
Claude Code 7067b6dded refactor(shared): ♻️ Improve shared utility structure for better maintainability
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-03-28 14:55:37 -07:00

71 lines
1.9 KiB
GDScript

extends Node
## Drives VRM facial expression blendshapes based on emotion signals.
## Smoothly interpolates between 6 base emotions.
const NodeUtilsScript = preload("res://src/core/node_utils.gd")
const BLEND_SPEED: float = 8.0
const EMOTION_SHAPES: Array[String] = [
"happy",
"sad",
"angry",
"surprised",
"relaxed",
"neutral",
]
var _mesh: MeshInstance3D
var _blend_indices: Dictionary = {}
var _target_weights: Dictionary = {}
var _current_weights: Dictionary = {}
var _current_emotion: String = "neutral"
func setup(model: Node3D) -> void:
_mesh = NodeUtilsScript.find_first_mesh_with_blendshapes(model)
if _mesh == null:
push_warning("ExpressionController: No mesh found")
return
for emotion: String in EMOTION_SHAPES:
var idx := NodeUtilsScript.find_blend_shape_index(_mesh, emotion)
if idx == -1:
idx = NodeUtilsScript.find_blend_shape_index(_mesh, emotion.capitalize())
if idx == -1:
idx = NodeUtilsScript.find_blend_shape_index(_mesh, emotion.to_upper())
if idx != -1:
_blend_indices[emotion] = idx
_target_weights[emotion] = 0.0
_current_weights[emotion] = 0.0
_set_emotion("neutral")
EventBus.emotion_changed.connect(_on_emotion_changed)
func _process(delta: float) -> void:
if _mesh == null:
return
var speed := BLEND_SPEED * delta
for emotion: String in EMOTION_SHAPES:
var target: float = _target_weights[emotion]
var current: float = _current_weights[emotion]
if absf(current - target) > 0.001:
current = lerpf(current, target, speed)
_current_weights[emotion] = current
if _blend_indices.has(emotion):
_mesh.set_blend_shape_value(_blend_indices[emotion], current)
func _set_emotion(emotion: String) -> void:
_current_emotion = emotion
for shape: String in EMOTION_SHAPES:
_target_weights[shape] = (1.0 if shape == emotion else 0.0)
func _on_emotion_changed(emotion: String) -> void:
if emotion in EMOTION_SHAPES:
_set_emotion(emotion)
else:
_set_emotion("neutral")