EG/plugins/user/morphing_animation/core.py
2025-12-12 16:16:15 +08:00

992 lines
40 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Morphing和变形动画插件 - 核心模块
提供顶点变形、morphing目标、网格变形等核心功能
"""
import math
import numpy as np
from typing import Dict, List, Tuple, Optional, Any, Callable
from dataclasses import dataclass, field
from enum import Enum
from panda3d.core import *
from direct.actor.Actor import Actor
from direct.showbase.DirectObject import DirectObject
# 定义常量
MORPHING_SETTINGS = {
'max_morph_targets': 100,
'default_interpolation': 'linear',
'update_rate': 60,
'cache_size': 200
}
# 枚举定义
class MorphingMode(Enum):
"""Morphing模式枚举"""
VERTEX_BLEND = 0 # 顶点混合
NORMAL_BLEND = 1 # 法线混合
TANGENT_BLEND = 2 # 切线混合
FULL_BLEND = 3 # 完全混合
class InterpolationMode(Enum):
"""插值模式枚举"""
LINEAR = 0 # 线性插值
EASE_IN = 1 # 缓入
EASE_OUT = 2 # 缓出
EASE_IN_OUT = 3 # 缓入缓出
SPLINE = 4 # 样条插值
class DeformationType(Enum):
"""变形类型枚举"""
MORPH_TARGET = 0 # Morph目标
VERTEX_ANIMATION = 1 # 顶点动画
SKELETAL_DEFORM = 2 # 骨骼变形
PROCEDURAL = 3 # 程序化变形
@dataclass
class MorphTarget:
"""Morph目标数据类"""
name: str
vertices: List[Tuple[float, float, float]] # 顶点位置偏移
normals: Optional[List[Tuple[float, float, float]]] = None # 法线偏移
tangents: Optional[List[Tuple[float, float, float]]] = None # 切线偏移
weights: List[float] = field(default_factory=list) # 权重列表
active: bool = True
@dataclass
class MorphingAnimation:
"""Morphing动画数据类"""
name: str
target_weights: Dict[str, float] # 目标权重 {target_name: weight}
duration: float # 持续时间
interpolation: InterpolationMode = InterpolationMode.LINEAR
loop: bool = False
start_time: float = 0.0
current_time: float = 0.0
playing: bool = False
paused: bool = False
@dataclass
class VertexDeformation:
"""顶点变形数据类"""
name: str
vertex_indices: List[int] # 顶点索引
target_positions: List[Tuple[float, float, float]] # 目标位置
duration: float # 持续时间
start_time: float = 0.0
current_time: float = 0.0
active: bool = True
interpolation: InterpolationMode = InterpolationMode.LINEAR
@dataclass
class ProceduralDeformation:
"""程序化变形数据类"""
name: str
deformation_function: Callable # 变形函数
parameters: Dict[str, Any] # 参数
update_rate: float = 1.0 # 更新频率
last_update: float = 0.0
active: bool = True
class MorphingCache:
"""Morphing缓存系统"""
def __init__(self, max_size: int = 200):
self.max_size = max_size
self.cache: Dict[str, Any] = {}
self.access_order: List[str] = []
def get(self, key: str) -> Optional[Any]:
"""获取缓存项"""
if key in self.cache:
# 更新访问顺序
if key in self.access_order:
self.access_order.remove(key)
self.access_order.append(key)
return self.cache[key]
return None
def put(self, key: str, value: Any) -> None:
"""放入缓存项"""
# 如果缓存已满,移除最久未使用的项
if len(self.cache) >= self.max_size:
if self.access_order:
oldest_key = self.access_order.pop(0)
del self.cache[oldest_key]
self.cache[key] = value
self.access_order.append(key)
def invalidate(self, key: str) -> None:
"""使缓存项失效"""
if key in self.cache:
del self.cache[key]
if key in self.access_order:
self.access_order.remove(key)
def clear(self) -> None:
"""清空缓存"""
self.cache.clear()
self.access_order.clear()
class MorphingCore(DirectObject):
"""
Morphing核心系统
提供完整的顶点变形、morphing目标、网格变形等功能
"""
def __init__(self, world):
"""
初始化Morphing核心系统
:param world: 世界对象
"""
super().__init__()
self.world = world
self.morph_targets: Dict[str, Dict[str, MorphTarget]] = {} # {model_id: {target_name: MorphTarget}}
self.morphing_animations: Dict[str, Dict[str, MorphingAnimation]] = {} # {model_id: {anim_name: MorphingAnimation}}
self.vertex_deformations: Dict[str, List[VertexDeformation]] = {} # {model_id: [VertexDeformation]}
self.procedural_deformations: Dict[str, List[ProceduralDeformation]] = {} # {model_id: [ProceduralDeformation]}
self.model_data: Dict[str, Dict[str, Any]] = {} # {model_id: {原始数据}}
self.cache = MorphingCache(MORPHING_SETTINGS['cache_size'])
self.is_updating = False
self.update_task = None
self.update_rate = MORPHING_SETTINGS['update_rate']
# 性能统计
self.stats = {
'models_processed': 0,
'morph_targets_created': 0,
'animations_played': 0,
'deformations_applied': 0,
'cache_hits': 0,
'cache_misses': 0
}
print("Morphing核心系统初始化完成")
def register_model(self, model_node: NodePath, model_id: Optional[str] = None) -> str:
"""
注册模型以支持morphing
:param model_node: 模型节点
:param model_id: 模型ID如果为None则自动生成
:return: 模型ID
"""
if model_id is None:
model_id = f"model_{len(self.model_data)}"
# 检查模型是否已注册
if model_id in self.model_data:
print(f"警告: 模型 {model_id} 已注册,将被替换")
self.unregister_model(model_id)
# 提取模型数据
model_data = self._extract_model_data(model_node)
if not model_data:
print(f"错误: 无法提取模型 {model_id} 的数据")
return ""
# 存储模型数据
self.model_data[model_id] = model_data
self.morph_targets[model_id] = {}
self.morphing_animations[model_id] = {}
self.vertex_deformations[model_id] = []
self.procedural_deformations[model_id] = []
self.stats['models_processed'] += 1
print(f"模型已注册: {model_id}")
return model_id
def unregister_model(self, model_id: str) -> bool:
"""
注销模型
:param model_id: 模型ID
:return: 是否成功注销
"""
if model_id not in self.model_data:
print(f"错误: 模型 {model_id} 未注册")
return False
# 清理相关数据
if model_id in self.morph_targets:
del self.morph_targets[model_id]
if model_id in self.morphing_animations:
del self.morphing_animations[model_id]
if model_id in self.vertex_deformations:
del self.vertex_deformations[model_id]
if model_id in self.procedural_deformations:
del self.procedural_deformations[model_id]
if model_id in self.model_data:
del self.model_data[model_id]
# 使相关缓存失效
cache_keys_to_invalidate = [key for key in self.cache.cache.keys() if key.startswith(f"{model_id}_")]
for key in cache_keys_to_invalidate:
self.cache.invalidate(key)
print(f"模型已注销: {model_id}")
return True
def _extract_model_data(self, model_node: NodePath) -> Optional[Dict[str, Any]]:
"""
提取模型数据
:param model_node: 模型节点
:return: 模型数据字典
"""
try:
# 查找GeomNode
geom_nodes = model_node.findAllMatches("**/+GeomNode")
if not geom_nodes:
return None
model_data = {
'node_path': model_node,
'geom_nodes': [],
'vertex_data': [],
'original_vertices': [],
'original_normals': [],
'original_tangents': []
}
# 提取每个GeomNode的数据
for geom_node in geom_nodes:
geom_node_obj = geom_node.node()
for i in range(geom_node_obj.getNumGeoms()):
geom = geom_node_obj.getGeom(i)
vertex_data = geom.getVertexData()
model_data['geom_nodes'].append(geom_node)
model_data['vertex_data'].append(vertex_data)
# 提取原始顶点数据
vertex_reader = GeomVertexReader(vertex_data, 'vertex')
vertices = []
while not vertex_reader.isAtEnd():
vertex = vertex_reader.getData3f()
vertices.append((vertex.x, vertex.y, vertex.z))
model_data['original_vertices'].append(vertices)
# 提取原始法线数据(如果存在)
if vertex_data.hasColumn('normal'):
normal_reader = GeomVertexReader(vertex_data, 'normal')
normals = []
while not normal_reader.isAtEnd():
normal = normal_reader.getData3f()
normals.append((normal.x, normal.y, normal.z))
model_data['original_normals'].append(normals)
# 提取原始切线数据(如果存在)
if vertex_data.hasColumn('tangent'):
tangent_reader = GeomVertexReader(vertex_data, 'tangent')
tangents = []
while not tangent_reader.isAtEnd():
tangent = tangent_reader.getData3f()
tangents.append((tangent.x, tangent.y, tangent.z))
model_data['original_tangents'].append(tangents)
return model_data
except Exception as e:
print(f"提取模型数据时出错: {e}")
import traceback
traceback.print_exc()
return None
def create_morph_target(self, model_id: str, target_name: str,
vertex_offsets: List[Tuple[float, float, float]],
normal_offsets: Optional[List[Tuple[float, float, float]]] = None,
tangent_offsets: Optional[List[Tuple[float, float, float]]] = None) -> bool:
"""
创建Morph目标
:param model_id: 模型ID
:param target_name: 目标名称
:param vertex_offsets: 顶点偏移列表
:param normal_offsets: 法线偏移列表(可选)
:param tangent_offsets: 切线偏移列表(可选)
:return: 是否成功创建
"""
if model_id not in self.model_data:
print(f"错误: 模型 {model_id} 未注册")
return False
# 检查模型是否有足够的顶点
total_vertices = sum(len(vertices) for vertices in self.model_data[model_id]['original_vertices'])
if len(vertex_offsets) != total_vertices:
print(f"错误: 顶点偏移数量 ({len(vertex_offsets)}) 与模型顶点数量 ({total_vertices}) 不匹配")
return False
# 创建Morph目标
morph_target = MorphTarget(
name=target_name,
vertices=vertex_offsets,
normals=normal_offsets,
tangents=tangent_offsets,
weights=[0.0] * len(vertex_offsets)
)
# 存储Morph目标
self.morph_targets[model_id][target_name] = morph_target
self.stats['morph_targets_created'] += 1
print(f"Morph目标已创建: {target_name} (模型: {model_id})")
return True
def remove_morph_target(self, model_id: str, target_name: str) -> bool:
"""
移除Morph目标
:param model_id: 模型ID
:param target_name: 目标名称
:return: 是否成功移除
"""
if model_id not in self.morph_targets or target_name not in self.morph_targets[model_id]:
print(f"错误: Morph目标 {target_name} 不存在于模型 {model_id}")
return False
del self.morph_targets[model_id][target_name]
print(f"Morph目标已移除: {target_name} (模型: {model_id})")
return True
def set_morph_target_weight(self, model_id: str, target_name: str, weight: float) -> bool:
"""
设置Morph目标权重
:param model_id: 模型ID
:param target_name: 目标名称
:param weight: 权重值 (0.0-1.0)
:return: 是否成功设置
"""
if model_id not in self.morph_targets or target_name not in self.morph_targets[model_id]:
print(f"错误: Morph目标 {target_name} 不存在于模型 {model_id}")
return False
# 限制权重范围
weight = max(0.0, min(1.0, weight))
# 更新权重
morph_target = self.morph_targets[model_id][target_name]
morph_target.weights = [weight] * len(morph_target.weights)
# 应用变形
self._apply_morph_target(model_id, target_name, weight)
return True
def _apply_morph_target(self, model_id: str, target_name: str, weight: float) -> None:
"""
应用Morph目标
:param model_id: 模型ID
:param target_name: 目标名称
:param weight: 权重值
"""
if model_id not in self.morph_targets or target_name not in self.morph_targets[model_id]:
return
morph_target = self.morph_targets[model_id][target_name]
model_data = self.model_data[model_id]
# 应用到每个GeomNode
vertex_offset_index = 0
for i, (geom_node, vertex_data) in enumerate(zip(model_data['geom_nodes'], model_data['vertex_data'])):
# 创建可写的顶点数据
writable_vertex_data = vertex_data.copy()
# 更新顶点位置
vertex_writer = GeomVertexWriter(writable_vertex_data, 'vertex')
original_vertices = model_data['original_vertices'][i]
for j, original_vertex in enumerate(original_vertices):
if vertex_offset_index + j < len(morph_target.vertices):
offset = morph_target.vertices[vertex_offset_index + j]
new_vertex = (
original_vertex[0] + offset[0] * weight,
original_vertex[1] + offset[1] * weight,
original_vertex[2] + offset[2] * weight
)
vertex_writer.setRow(j)
vertex_writer.setData3f(new_vertex[0], new_vertex[1], new_vertex[2])
# 更新法线(如果存在)
if morph_target.normals and writable_vertex_data.hasColumn('normal'):
normal_writer = GeomVertexWriter(writable_vertex_data, 'normal')
original_normals = model_data['original_normals'][i] if i < len(model_data['original_normals']) else []
for j, original_normal in enumerate(original_normals):
if vertex_offset_index + j < len(morph_target.normals):
offset = morph_target.normals[vertex_offset_index + j]
new_normal = (
original_normal[0] + offset[0] * weight,
original_normal[1] + offset[1] * weight,
original_normal[2] + offset[2] * weight
)
normal_writer.setRow(j)
normal_writer.setData3f(new_normal[0], new_normal[1], new_normal[2])
# 更新切线(如果存在)
if morph_target.tangents and writable_vertex_data.hasColumn('tangent'):
tangent_writer = GeomVertexWriter(writable_vertex_data, 'tangent')
original_tangents = model_data['original_tangents'][i] if i < len(model_data['original_tangents']) else []
for j, original_tangent in enumerate(original_tangents):
if vertex_offset_index + j < len(morph_target.tangents):
offset = morph_target.tangents[vertex_offset_index + j]
new_tangent = (
original_tangent[0] + offset[0] * weight,
original_tangent[1] + offset[1] * weight,
original_tangent[2] + offset[2] * weight
)
tangent_writer.setRow(j)
tangent_writer.setData3f(new_tangent[0], new_tangent[1], new_tangent[2])
# 更新GeomNode的顶点数据
geom_node.node().modifyGeom(0).setVertexData(writable_vertex_data)
# 更新顶点偏移索引
vertex_offset_index += len(original_vertices)
self.stats['deformations_applied'] += 1
def create_morphing_animation(self, model_id: str, anim_name: str,
target_weights: Dict[str, float],
duration: float,
interpolation: InterpolationMode = InterpolationMode.LINEAR,
loop: bool = False) -> bool:
"""
创建Morphing动画
:param model_id: 模型ID
:param anim_name: 动画名称
:param target_weights: 目标权重字典 {target_name: weight}
:param duration: 持续时间
:param interpolation: 插值模式
:param loop: 是否循环
:return: 是否成功创建
"""
if model_id not in self.model_data:
print(f"错误: 模型 {model_id} 未注册")
return False
# 验证Morph目标是否存在
for target_name in target_weights.keys():
if target_name not in self.morph_targets[model_id]:
print(f"错误: Morph目标 {target_name} 不存在于模型 {model_id}")
return False
# 创建动画
animation = MorphingAnimation(
name=anim_name,
target_weights=target_weights,
duration=duration,
interpolation=interpolation,
loop=loop,
start_time=globalClock.getRealTime(),
current_time=0.0,
playing=False,
paused=False
)
# 存储动画
self.morphing_animations[model_id][anim_name] = animation
print(f"Morphing动画已创建: {anim_name} (模型: {model_id})")
return True
def play_morphing_animation(self, model_id: str, anim_name: str) -> bool:
"""
播放Morphing动画
:param model_id: 模型ID
:param anim_name: 动画名称
:return: 是否成功播放
"""
if (model_id not in self.morphing_animations or
anim_name not in self.morphing_animations[model_id]):
print(f"错误: Morphing动画 {anim_name} 不存在于模型 {model_id}")
return False
animation = self.morphing_animations[model_id][anim_name]
animation.playing = True
animation.paused = False
animation.start_time = globalClock.getRealTime()
animation.current_time = 0.0
self.stats['animations_played'] += 1
print(f"Morphing动画开始播放: {anim_name} (模型: {model_id})")
return True
def stop_morphing_animation(self, model_id: str, anim_name: Optional[str] = None) -> bool:
"""
停止Morphing动画
:param model_id: 模型ID
:param anim_name: 动画名称如果为None则停止所有动画
:return: 是否成功停止
"""
if model_id not in self.morphing_animations:
print(f"错误: 模型 {model_id} 未注册")
return False
if anim_name:
# 停止单个动画
if anim_name not in self.morphing_animations[model_id]:
print(f"错误: Morphing动画 {anim_name} 不存在于模型 {model_id}")
return False
animation = self.morphing_animations[model_id][anim_name]
animation.playing = False
animation.paused = False
print(f"Morphing动画已停止: {anim_name} (模型: {model_id})")
else:
# 停止所有动画
for animation in self.morphing_animations[model_id].values():
animation.playing = False
animation.paused = False
print(f"所有Morphing动画已停止 (模型: {model_id})")
return True
def pause_morphing_animation(self, model_id: str, anim_name: str) -> bool:
"""
暂停Morphing动画
:param model_id: 模型ID
:param anim_name: 动画名称
:return: 是否成功暂停
"""
if (model_id not in self.morphing_animations or
anim_name not in self.morphing_animations[model_id]):
print(f"错误: Morphing动画 {anim_name} 不存在于模型 {model_id}")
return False
animation = self.morphing_animations[model_id][anim_name]
if animation.playing and not animation.paused:
animation.paused = True
print(f"Morphing动画已暂停: {anim_name} (模型: {model_id})")
return True
return False
def resume_morphing_animation(self, model_id: str, anim_name: str) -> bool:
"""
恢复Morphing动画
:param model_id: 模型ID
:param anim_name: 动画名称
:return: 是否成功恢复
"""
if (model_id not in self.morphing_animations or
anim_name not in self.morphing_animations[model_id]):
print(f"错误: Morphing动画 {anim_name} 不存在于模型 {model_id}")
return False
animation = self.morphing_animations[model_id][anim_name]
if animation.playing and animation.paused:
animation.paused = False
# 调整开始时间以补偿暂停时间
animation.start_time = globalClock.getRealTime() - animation.current_time
print(f"Morphing动画已恢复: {anim_name} (模型: {model_id})")
return True
return False
def create_vertex_deformation(self, model_id: str, deformation_name: str,
vertex_indices: List[int],
target_positions: List[Tuple[float, float, float]],
duration: float,
interpolation: InterpolationMode = InterpolationMode.LINEAR) -> bool:
"""
创建顶点变形
:param model_id: 模型ID
:param deformation_name: 变形名称
:param vertex_indices: 顶点索引列表
:param target_positions: 目标位置列表
:param duration: 持续时间
:param interpolation: 插值模式
:return: 是否成功创建
"""
if model_id not in self.model_data:
print(f"错误: 模型 {model_id} 未注册")
return False
if len(vertex_indices) != len(target_positions):
print(f"错误: 顶点索引数量 ({len(vertex_indices)}) 与目标位置数量 ({len(target_positions)}) 不匹配")
return False
# 创建顶点变形
deformation = VertexDeformation(
name=deformation_name,
vertex_indices=vertex_indices,
target_positions=target_positions,
duration=duration,
interpolation=interpolation,
start_time=globalClock.getRealTime(),
current_time=0.0,
active=True
)
# 存储变形
self.vertex_deformations[model_id].append(deformation)
print(f"顶点变形已创建: {deformation_name} (模型: {model_id})")
return True
def remove_vertex_deformation(self, model_id: str, deformation_name: str) -> bool:
"""
移除顶点变形
:param model_id: 模型ID
:param deformation_name: 变形名称
:return: 是否成功移除
"""
if model_id not in self.vertex_deformations:
print(f"错误: 模型 {model_id} 未注册")
return False
# 查找并移除变形
deformations = self.vertex_deformations[model_id]
for i, deformation in enumerate(deformations):
if deformation.name == deformation_name:
deformations.pop(i)
print(f"顶点变形已移除: {deformation_name} (模型: {model_id})")
return True
print(f"错误: 顶点变形 {deformation_name} 不存在于模型 {model_id}")
return False
def add_procedural_deformation(self, model_id: str, deformation_name: str,
deformation_function: Callable,
parameters: Dict[str, Any],
update_rate: float = 1.0) -> bool:
"""
添加程序化变形
:param model_id: 模型ID
:param deformation_name: 变形名称
:param deformation_function: 变形函数
:param parameters: 参数字典
:param update_rate: 更新频率
:return: 是否成功添加
"""
if model_id not in self.model_data:
print(f"错误: 模型 {model_id} 未注册")
return False
# 创建程序化变形
deformation = ProceduralDeformation(
name=deformation_name,
deformation_function=deformation_function,
parameters=parameters,
update_rate=update_rate,
last_update=globalClock.getRealTime(),
active=True
)
# 存储变形
self.procedural_deformations[model_id].append(deformation)
print(f"程序化变形已添加: {deformation_name} (模型: {model_id})")
return True
def remove_procedural_deformation(self, model_id: str, deformation_name: str) -> bool:
"""
移除程序化变形
:param model_id: 模型ID
:param deformation_name: 变形名称
:return: 是否成功移除
"""
if model_id not in self.procedural_deformations:
print(f"错误: 模型 {model_id} 未注册")
return False
# 查找并移除变形
deformations = self.procedural_deformations[model_id]
for i, deformation in enumerate(deformations):
if deformation.name == deformation_name:
deformations.pop(i)
print(f"程序化变形已移除: {deformation_name} (模型: {model_id})")
return True
print(f"错误: 程序化变形 {deformation_name} 不存在于模型 {model_id}")
return False
def update(self, dt: float) -> None:
"""
更新Morphing系统
:param dt: 时间增量
"""
if not self.is_updating:
return
current_time = globalClock.getRealTime()
# 更新每个模型
for model_id in self.model_data.keys():
# 更新Morphing动画
self._update_morphing_animations(model_id, current_time, dt)
# 更新顶点变形
self._update_vertex_deformations(model_id, current_time, dt)
# 更新程序化变形
self._update_procedural_deformations(model_id, current_time, dt)
def _update_morphing_animations(self, model_id: str, current_time: float, dt: float) -> None:
"""
更新Morphing动画
:param model_id: 模型ID
:param current_time: 当前时间
:param dt: 时间增量
"""
if model_id not in self.morphing_animations:
return
# 更新每个动画
animations_to_remove = []
for anim_name, animation in self.morphing_animations[model_id].items():
if not animation.playing or animation.paused:
continue
# 更新动画时间
animation.current_time = current_time - animation.start_time
# 检查动画是否完成
if animation.current_time >= animation.duration:
if animation.loop:
# 循环播放
animation.start_time = current_time
animation.current_time = 0.0
else:
# 停止动画
animation.playing = False
continue
# 计算插值因子
t = animation.current_time / animation.duration
if animation.interpolation == InterpolationMode.EASE_IN:
t = t * t
elif animation.interpolation == InterpolationMode.EASE_OUT:
t = 1.0 - (1.0 - t) * (1.0 - t)
elif animation.interpolation == InterpolationMode.EASE_IN_OUT:
t = t * t * (3.0 - 2.0 * t)
elif animation.interpolation == InterpolationMode.SPLINE:
# 简化的样条插值
t = t * t * (3.0 - 2.0 * t)
# 应用每个目标的权重
for target_name, target_weight in animation.target_weights.items():
self.set_morph_target_weight(model_id, target_name, target_weight * t)
def _update_vertex_deformations(self, model_id: str, current_time: float, dt: float) -> None:
"""
更新顶点变形
:param model_id: 模型ID
:param current_time: 当前时间
:param dt: 时间增量
"""
if model_id not in self.vertex_deformations:
return
# 更新每个变形
deformations = self.vertex_deformations[model_id]
active_deformations = [d for d in deformations if d.active]
if not active_deformations:
return
model_data = self.model_data[model_id]
# 对每个GeomNode应用变形
for i, (geom_node, vertex_data) in enumerate(zip(model_data['geom_nodes'], model_data['vertex_data'])):
# 创建可写的顶点数据
writable_vertex_data = vertex_data.copy()
vertex_writer = GeomVertexWriter(writable_vertex_data, 'vertex')
original_vertices = model_data['original_vertices'][i]
# 应用每个活动变形
for deformation in active_deformations:
# 更新变形时间
deformation.current_time = current_time - deformation.start_time
# 检查变形是否完成
if deformation.current_time >= deformation.duration:
deformation.active = False
continue
# 计算插值因子
t = deformation.current_time / deformation.duration
if deformation.interpolation == InterpolationMode.EASE_IN:
t = t * t
elif deformation.interpolation == InterpolationMode.EASE_OUT:
t = 1.0 - (1.0 - t) * (1.0 - t)
elif deformation.interpolation == InterpolationMode.EASE_IN_OUT:
t = t * t * (3.0 - 2.0 * t)
elif deformation.interpolation == InterpolationMode.SPLINE:
t = t * t * (3.0 - 2.0 * t)
# 应用变形到指定顶点
for j, vertex_index in enumerate(deformation.vertex_indices):
if vertex_index < len(original_vertices):
original_vertex = original_vertices[vertex_index]
target_position = deformation.target_positions[j]
# 计算插值位置
interpolated_position = (
original_vertex[0] + (target_position[0] - original_vertex[0]) * t,
original_vertex[1] + (target_position[1] - original_vertex[1]) * t,
original_vertex[2] + (target_position[2] - original_vertex[2]) * t
)
# 更新顶点位置
vertex_writer.setRow(vertex_index)
vertex_writer.setData3f(
interpolated_position[0],
interpolated_position[1],
interpolated_position[2]
)
# 更新GeomNode的顶点数据
geom_node.node().modifyGeom(0).setVertexData(writable_vertex_data)
# 移除已完成的变形
self.vertex_deformations[model_id] = [d for d in deformations if d.active]
def _update_procedural_deformations(self, model_id: str, current_time: float, dt: float) -> None:
"""
更新程序化变形
:param model_id: 模型ID
:param current_time: 当前时间
:param dt: 时间增量
"""
if model_id not in self.procedural_deformations:
return
# 更新每个变形
deformations = self.procedural_deformations[model_id]
active_deformations = [d for d in deformations if d.active]
for deformation in active_deformations:
# 检查更新时间
if current_time - deformation.last_update < 1.0 / deformation.update_rate:
continue
deformation.last_update = current_time
try:
# 调用变形函数
deformation.deformation_function(
self.model_data[model_id]['node_path'],
**deformation.parameters
)
except Exception as e:
print(f"执行程序化变形函数时出错: {e}")
# 移除非活动变形
self.procedural_deformations[model_id] = [d for d in deformations if d.active]
def get_model_info(self, model_id: str) -> Optional[Dict[str, Any]]:
"""
获取模型信息
:param model_id: 模型ID
:return: 模型信息字典
"""
if model_id not in self.model_data:
print(f"错误: 模型 {model_id} 未注册")
return None
model_data = self.model_data[model_id]
return {
'model_id': model_id,
'morph_targets': list(self.morph_targets[model_id].keys()),
'morphing_animations': list(self.morphing_animations[model_id].keys()),
'vertex_deformations': [d.name for d in self.vertex_deformations[model_id]],
'procedural_deformations': [d.name for d in self.procedural_deformations[model_id]],
'vertex_count': sum(len(vertices) for vertices in model_data['original_vertices']),
'geom_node_count': len(model_data['geom_nodes'])
}
def get_morph_target_info(self, model_id: str, target_name: str) -> Optional[Dict[str, Any]]:
"""
获取Morph目标信息
:param model_id: 模型ID
:param target_name: 目标名称
:return: Morph目标信息字典
"""
if (model_id not in self.morph_targets or
target_name not in self.morph_targets[model_id]):
print(f"错误: Morph目标 {target_name} 不存在于模型 {model_id}")
return None
morph_target = self.morph_targets[model_id][target_name]
return {
'name': morph_target.name,
'vertex_count': len(morph_target.vertices),
'has_normals': morph_target.normals is not None,
'has_tangents': morph_target.tangents is not None,
'active': morph_target.active
}
def get_animation_info(self, model_id: str, anim_name: str) -> Optional[Dict[str, Any]]:
"""
获取动画信息
:param model_id: 模型ID
:param anim_name: 动画名称
:return: 动画信息字典
"""
if (model_id not in self.morphing_animations or
anim_name not in self.morphing_animations[model_id]):
print(f"错误: Morphing动画 {anim_name} 不存在于模型 {model_id}")
return None
animation = self.morphing_animations[model_id][anim_name]
return {
'name': animation.name,
'target_weights': animation.target_weights,
'duration': animation.duration,
'interpolation': animation.interpolation.name,
'loop': animation.loop,
'current_time': animation.current_time,
'playing': animation.playing,
'paused': animation.paused
}
def start_updating(self) -> None:
"""
开始更新Morphing系统
"""
if not self.is_updating:
self.is_updating = True
print("Morphing系统更新已启动")
def stop_updating(self) -> None:
"""
停止更新Morphing系统
"""
if self.is_updating:
self.is_updating = False
print("Morphing系统更新已停止")
def get_system_stats(self) -> Dict[str, Any]:
"""
获取系统统计信息
:return: 统计信息字典
"""
cache_stats = {
'size': len(self.cache.cache),
'max_size': self.cache.max_size
}
return {
'models_processed': self.stats['models_processed'],
'morph_targets_created': self.stats['morph_targets_created'],
'animations_played': self.stats['animations_played'],
'deformations_applied': self.stats['deformations_applied'],
'cache_stats': cache_stats,
'registered_models': len(self.model_data),
'total_morph_targets': sum(len(targets) for targets in self.morph_targets.values()),
'total_animations': sum(len(anims) for anims in self.morphing_animations.values())
}
def reset_stats(self) -> None:
"""
重置统计信息
"""
self.stats = {
'models_processed': 0,
'morph_targets_created': 0,
'animations_played': 0,
'deformations_applied': 0,
'cache_hits': 0,
'cache_misses': 0
}
print("统计信息已重置")