71 lines
1.9 KiB
GDScript
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")
|