992 lines
40 KiB
Python
992 lines
40 KiB
Python
"""
|
||
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("统计信息已重置") |