1207 lines
43 KiB
Python
1207 lines
43 KiB
Python
"""
|
||
高级骨骼动画系统 - 核心模块
|
||
提供完整的骨骼动画支持,包括加载、播放、控制等核心功能
|
||
"""
|
||
|
||
import math
|
||
import json
|
||
import os
|
||
from collections import defaultdict, deque
|
||
from typing import Dict, List, Tuple, Optional, Union, Any, Callable
|
||
from dataclasses import dataclass, field
|
||
from enum import Enum
|
||
|
||
from panda3d.core import *
|
||
from direct.actor.Actor import Actor
|
||
from direct.interval.IntervalGlobal import *
|
||
from direct.showbase.DirectObject import DirectObject
|
||
from direct.distributed.ClockDelta import globalClockDelta
|
||
|
||
# 导入工具模块
|
||
from .tools import AnimationAnalyzer, AnimationEditor, DebugVisualizer, AnimationProfiler
|
||
|
||
# 定义常量
|
||
DEFAULT_ANIMATION_SETTINGS = {
|
||
'default_play_rate': 1.0,
|
||
'default_loop': False,
|
||
'blend_time': 0.3,
|
||
'max_actors': 1000,
|
||
'cache_size': 100,
|
||
'lod_distance_thresholds': [10.0, 25.0, 50.0],
|
||
'lod_frame_rates': [1.0, 0.5, 0.25, 0.1]
|
||
}
|
||
|
||
# 枚举定义
|
||
class AnimationState(Enum):
|
||
"""动画状态枚举"""
|
||
STOPPED = 0
|
||
PLAYING = 1
|
||
PAUSED = 2
|
||
BLENDING = 3
|
||
LOOPING = 4
|
||
|
||
class BlendMode(Enum):
|
||
"""混合模式枚举"""
|
||
LINEAR = 0
|
||
EASE_IN = 1
|
||
EASE_OUT = 2
|
||
EASE_IN_OUT = 3
|
||
OVERWRITE = 4
|
||
|
||
class LODLevel(Enum):
|
||
"""LOD级别枚举"""
|
||
HIGH = 0
|
||
MEDIUM = 1
|
||
LOW = 2
|
||
VERY_LOW = 3
|
||
|
||
@dataclass
|
||
class AnimationInfo:
|
||
"""动画信息数据类"""
|
||
name: str
|
||
frame_rate: float
|
||
num_frames: int
|
||
play_rate: float
|
||
current_frame: float
|
||
state: AnimationState
|
||
loop: bool
|
||
start_time: float
|
||
duration: float
|
||
blend_weight: float = 1.0
|
||
blend_in_time: float = 0.0
|
||
blend_out_time: float = 0.0
|
||
|
||
@dataclass
|
||
class ActorInfo:
|
||
"""Actor信息数据类"""
|
||
actor_id: str
|
||
actor: Actor
|
||
model_path: str
|
||
anim_paths: Dict[str, str]
|
||
position: Tuple[float, float, float]
|
||
scale: Union[float, Tuple[float, float, float]]
|
||
animations: Dict[str, AnimationInfo] = field(default_factory=dict)
|
||
active_animations: List[str] = field(default_factory=list)
|
||
lod_level: LODLevel = LODLevel.HIGH
|
||
last_update_time: float = 0.0
|
||
is_networked: bool = False
|
||
network_id: str = ""
|
||
|
||
@dataclass
|
||
class BlendInfo:
|
||
"""混合信息数据类"""
|
||
from_anim: str
|
||
to_anim: str
|
||
blend_time: float
|
||
blend_mode: BlendMode
|
||
current_time: float = 0.0
|
||
total_time: float = 0.0
|
||
active: bool = False
|
||
|
||
@dataclass
|
||
class IKChain:
|
||
"""IK链数据类"""
|
||
chain_name: str
|
||
end_effector: str
|
||
pole_vector: Optional[Tuple[float, float, float]]
|
||
iterations: int
|
||
enabled: bool = True
|
||
target_position: Optional[Tuple[float, float, float]] = None
|
||
|
||
class AnimationCache:
|
||
"""动画缓存系统"""
|
||
|
||
def __init__(self, max_size: int = 100):
|
||
self.max_size = max_size
|
||
self.cache: Dict[str, Any] = {}
|
||
self.access_order: deque = deque()
|
||
self.hits = 0
|
||
self.misses = 0
|
||
|
||
def get(self, key: str) -> Optional[Any]:
|
||
"""获取缓存项"""
|
||
if key in self.cache:
|
||
self.hits += 1
|
||
# 更新访问顺序
|
||
self.access_order.remove(key)
|
||
self.access_order.append(key)
|
||
return self.cache[key]
|
||
else:
|
||
self.misses += 1
|
||
return None
|
||
|
||
def put(self, key: str, value: Any) -> None:
|
||
"""放入缓存项"""
|
||
# 如果缓存已满,移除最久未使用的项
|
||
if len(self.cache) >= self.max_size:
|
||
oldest_key = self.access_order.popleft()
|
||
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]
|
||
self.access_order.remove(key)
|
||
|
||
def clear(self) -> None:
|
||
"""清空缓存"""
|
||
self.cache.clear()
|
||
self.access_order.clear()
|
||
self.hits = 0
|
||
self.misses = 0
|
||
|
||
def get_stats(self) -> Dict[str, Any]:
|
||
"""获取缓存统计信息"""
|
||
total_requests = self.hits + self.misses
|
||
hit_rate = self.hits / total_requests if total_requests > 0 else 0
|
||
return {
|
||
'size': len(self.cache),
|
||
'max_size': self.max_size,
|
||
'hits': self.hits,
|
||
'misses': self.misses,
|
||
'hit_rate': hit_rate
|
||
}
|
||
|
||
class AnimationCompressor:
|
||
"""动画压缩系统"""
|
||
|
||
def __init__(self):
|
||
self.position_threshold = 0.001
|
||
self.rotation_threshold = 0.01
|
||
self.scale_threshold = 0.001
|
||
|
||
def compress_keyframes(self, keyframes: List[Tuple[float, Tuple[float, float, float]]],
|
||
data_type: str = 'position') -> List[Tuple[float, Tuple[float, float, float]]]:
|
||
"""
|
||
压缩关键帧数据
|
||
:param keyframes: 关键帧列表 [(time, (x, y, z)), ...]
|
||
:param data_type: 数据类型 ('position', 'rotation', 'scale')
|
||
:return: 压缩后的关键帧列表
|
||
"""
|
||
if len(keyframes) <= 2:
|
||
return keyframes
|
||
|
||
compressed = [keyframes[0]] # 总是保留第一帧
|
||
last_keyframe = keyframes[0]
|
||
|
||
# 根据数据类型选择阈值
|
||
threshold = getattr(self, f'{data_type}_threshold', 0.001)
|
||
|
||
for i in range(1, len(keyframes) - 1):
|
||
current_keyframe = keyframes[i]
|
||
next_keyframe = keyframes[i + 1]
|
||
|
||
# 检查当前帧是否可以被简化
|
||
if not self._is_keyframe_necessary(last_keyframe, current_keyframe, next_keyframe, threshold, data_type):
|
||
continue
|
||
|
||
compressed.append(current_keyframe)
|
||
last_keyframe = current_keyframe
|
||
|
||
compressed.append(keyframes[-1]) # 总是保留最后一帧
|
||
return compressed
|
||
|
||
def _is_keyframe_necessary(self, prev_frame: Tuple[float, Tuple[float, float, float]],
|
||
current_frame: Tuple[float, Tuple[float, float, float]],
|
||
next_frame: Tuple[float, Tuple[float, float, float]],
|
||
threshold: float, data_type: str) -> bool:
|
||
"""
|
||
判断关键帧是否必要
|
||
:param prev_frame: 前一帧
|
||
:param current_frame: 当前帧
|
||
:param next_frame: 后一帧
|
||
:param threshold: 阈值
|
||
:param data_type: 数据类型
|
||
:return: 是否必要
|
||
"""
|
||
prev_time, prev_data = prev_frame
|
||
curr_time, curr_data = current_frame
|
||
next_time, next_data = next_frame
|
||
|
||
# 计算线性插值
|
||
t = (curr_time - prev_time) / (next_time - prev_time)
|
||
interpolated = tuple(prev_data[i] + t * (next_data[i] - prev_data[i]) for i in range(3))
|
||
|
||
# 计算与实际值的差异
|
||
diff = math.sqrt(sum((curr_data[i] - interpolated[i]) ** 2 for i in range(3)))
|
||
|
||
return diff > threshold
|
||
|
||
class SkeletalAnimationCore(DirectObject):
|
||
"""
|
||
骨骼动画核心系统
|
||
提供完整的骨骼动画管理功能
|
||
"""
|
||
|
||
def __init__(self, world):
|
||
"""
|
||
初始化骨骼动画核心系统
|
||
:param world: 世界对象
|
||
"""
|
||
super().__init__()
|
||
self.world = world
|
||
self.actors: Dict[str, ActorInfo] = {}
|
||
self.actor_configs: Dict[str, Dict[str, Any]] = {}
|
||
self.blend_infos: Dict[str, Dict[str, BlendInfo]] = defaultdict(dict)
|
||
self.ik_chains: Dict[str, Dict[str, IKChain]] = defaultdict(dict)
|
||
self.animation_cache = AnimationCache(DEFAULT_ANIMATION_SETTINGS['cache_size'])
|
||
self.compressor = AnimationCompressor()
|
||
self.event_handlers: Dict[str, List[Callable]] = defaultdict(list)
|
||
self.is_updating = False
|
||
self.update_task = None
|
||
|
||
# 工具系统
|
||
self.analyzer = AnimationAnalyzer(self)
|
||
self.editor = AnimationEditor(self)
|
||
self.visualizer = DebugVisualizer(world)
|
||
self.profiler = AnimationProfiler(self)
|
||
|
||
# 性能统计
|
||
self.stats = {
|
||
'actors_loaded': 0,
|
||
'animations_played': 0,
|
||
'blends_performed': 0,
|
||
'ik_calculations': 0,
|
||
'cache_hits': 0,
|
||
'cache_misses': 0
|
||
}
|
||
|
||
print("骨骼动画核心系统初始化完成")
|
||
|
||
def load_actor(self, model_path: str, anim_paths: Optional[Dict[str, str]] = None,
|
||
actor_id: Optional[str] = None, position: Tuple[float, float, float] = (0, 0, 0),
|
||
scale: Union[float, Tuple[float, float, float]] = 1.0) -> Tuple[Optional[Actor], str]:
|
||
"""
|
||
加载Actor模型
|
||
:param model_path: 模型文件路径
|
||
:param anim_paths: 动画文件路径字典 {anim_name: anim_path}
|
||
:param actor_id: Actor标识符,如果为None则自动生成
|
||
:param position: 位置坐标 (x, y, z)
|
||
:param scale: 缩放比例
|
||
:return: (Actor对象, actor_id)
|
||
"""
|
||
try:
|
||
# 检查模型文件是否存在
|
||
if not os.path.exists(model_path):
|
||
raise FileNotFoundError(f"模型文件不存在: {model_path}")
|
||
|
||
# 生成Actor ID
|
||
if actor_id is None:
|
||
actor_id = f"actor_{len(self.actors)}"
|
||
|
||
# 检查Actor是否已存在
|
||
if actor_id in self.actors:
|
||
print(f"警告: Actor {actor_id} 已存在,将被替换")
|
||
self.unload_actor(actor_id)
|
||
|
||
# 检查缓存
|
||
cache_key = f"actor_{model_path}_{hash(str(anim_paths))}"
|
||
cached_actor = self.animation_cache.get(cache_key)
|
||
|
||
if cached_actor:
|
||
actor = cached_actor
|
||
print(f"从缓存加载Actor: {actor_id}")
|
||
else:
|
||
# 创建Actor
|
||
if anim_paths:
|
||
# 验证动画文件
|
||
valid_anims = {}
|
||
for anim_name, anim_path in anim_paths.items():
|
||
if os.path.exists(anim_path):
|
||
valid_anims[anim_name] = anim_path
|
||
else:
|
||
print(f"警告: 动画文件不存在: {anim_path}")
|
||
actor = Actor(model_path, valid_anims)
|
||
else:
|
||
actor = Actor(model_path)
|
||
|
||
# 缓存Actor
|
||
self.animation_cache.put(cache_key, actor)
|
||
|
||
# 设置位置和缩放
|
||
actor.setPos(*position)
|
||
if isinstance(scale, (int, float)):
|
||
actor.setScale(scale)
|
||
else:
|
||
actor.setScale(*scale)
|
||
|
||
# 将Actor添加到场景中
|
||
actor.reparentTo(self.world.render)
|
||
actor.setName(actor_id)
|
||
|
||
# 创建Actor信息
|
||
actor_info = ActorInfo(
|
||
actor_id=actor_id,
|
||
actor=actor,
|
||
model_path=model_path,
|
||
anim_paths=anim_paths or {},
|
||
position=position,
|
||
scale=scale
|
||
)
|
||
|
||
# 初始化动画信息
|
||
for anim_name in actor.getAnimNames():
|
||
control = actor.getAnimControl(anim_name)
|
||
if control:
|
||
anim_info = AnimationInfo(
|
||
name=anim_name,
|
||
frame_rate=actor.getFrameRate(anim_name),
|
||
num_frames=actor.getNumFrames(anim_name),
|
||
play_rate=DEFAULT_ANIMATION_SETTINGS['default_play_rate'],
|
||
current_frame=0.0,
|
||
state=AnimationState.STOPPED,
|
||
loop=DEFAULT_ANIMATION_SETTINGS['default_loop'],
|
||
start_time=0.0,
|
||
duration=control.getDuration()
|
||
)
|
||
actor_info.animations[anim_name] = anim_info
|
||
|
||
# 存储Actor信息
|
||
self.actors[actor_id] = actor_info
|
||
self.actor_configs[actor_id] = {
|
||
'model_path': model_path,
|
||
'anim_paths': anim_paths,
|
||
'position': position,
|
||
'scale': scale
|
||
}
|
||
|
||
self.stats['actors_loaded'] += 1
|
||
print(f"成功加载Actor: {actor_id}")
|
||
print(f" 模型: {model_path}")
|
||
print(f" 位置: {position}")
|
||
print(f" 缩放: {scale}")
|
||
print(f" 可用动画: {list(actor_info.animations.keys())}")
|
||
|
||
return actor, actor_id
|
||
|
||
except Exception as e:
|
||
print(f"加载Actor失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None, ""
|
||
|
||
def unload_actor(self, actor_id: str) -> bool:
|
||
"""
|
||
卸载Actor
|
||
:param actor_id: Actor标识符
|
||
:return: 是否成功卸载
|
||
"""
|
||
if actor_id not in self.actors:
|
||
print(f"错误: Actor {actor_id} 不存在")
|
||
return False
|
||
|
||
try:
|
||
actor_info = self.actors[actor_id]
|
||
actor = actor_info.actor
|
||
|
||
# 停止所有动画
|
||
self.stop_all_animations(actor_id)
|
||
|
||
# 清理混合信息
|
||
if actor_id in self.blend_infos:
|
||
del self.blend_infos[actor_id]
|
||
|
||
# 清理IK链
|
||
if actor_id in self.ik_chains:
|
||
del self.ik_chains[actor_id]
|
||
|
||
# 从场景中移除
|
||
actor.cleanup()
|
||
|
||
# 从管理器中移除
|
||
del self.actors[actor_id]
|
||
if actor_id in self.actor_configs:
|
||
del self.actor_configs[actor_id]
|
||
|
||
print(f"已卸载Actor: {actor_id}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"卸载Actor失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def play_animation(self, actor_id: str, anim_name: str, loop: bool = False,
|
||
play_rate: float = 1.0, start_frame: Optional[int] = None,
|
||
end_frame: Optional[int] = None) -> bool:
|
||
"""
|
||
播放动画
|
||
:param actor_id: Actor标识符
|
||
:param anim_name: 动画名称
|
||
:param loop: 是否循环播放
|
||
:param play_rate: 播放速度
|
||
:param start_frame: 起始帧
|
||
:param end_frame: 结束帧
|
||
:return: 是否成功播放
|
||
"""
|
||
if actor_id not in self.actors:
|
||
print(f"错误: Actor {actor_id} 不存在")
|
||
return False
|
||
|
||
actor_info = self.actors[actor_id]
|
||
actor = actor_info.actor
|
||
|
||
# 检查动画是否存在
|
||
if anim_name not in actor.getAnimNames():
|
||
print(f"错误: 动画 {anim_name} 不存在于Actor {actor_id} 中")
|
||
print(f" 可用动画: {actor.getAnimNames()}")
|
||
return False
|
||
|
||
try:
|
||
# 设置播放速度
|
||
actor.setPlayRate(play_rate, anim_name)
|
||
|
||
# 播放动画
|
||
if loop:
|
||
if start_frame is not None and end_frame is not None:
|
||
actor.loop(anim_name, restart=False, fromFrame=start_frame, toFrame=end_frame)
|
||
else:
|
||
actor.loop(anim_name, restart=False)
|
||
else:
|
||
if start_frame is not None and end_frame is not None:
|
||
actor.play(anim_name, fromFrame=start_frame, toFrame=end_frame)
|
||
else:
|
||
actor.play(anim_name)
|
||
|
||
# 更新动画信息
|
||
if anim_name in actor_info.animations:
|
||
anim_info = actor_info.animations[anim_name]
|
||
anim_info.state = AnimationState.LOOPING if loop else AnimationState.PLAYING
|
||
anim_info.loop = loop
|
||
anim_info.play_rate = play_rate
|
||
anim_info.start_time = globalClock.getRealTime()
|
||
|
||
# 添加到活动动画列表
|
||
if anim_name not in actor_info.active_animations:
|
||
actor_info.active_animations.append(anim_name)
|
||
|
||
self.stats['animations_played'] += 1
|
||
print(f"播放动画: {anim_name} (Actor: {actor_id}, 循环: {loop}, 速度: {play_rate}x)")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"播放动画失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def stop_animation(self, actor_id: str, anim_name: Optional[str] = None) -> bool:
|
||
"""
|
||
停止动画
|
||
:param actor_id: Actor标识符
|
||
:param anim_name: 动画名称,如果为None则停止所有动画
|
||
:return: 是否成功停止
|
||
"""
|
||
if actor_id not in self.actors:
|
||
print(f"错误: Actor {actor_id} 不存在")
|
||
return False
|
||
|
||
actor_info = self.actors[actor_id]
|
||
actor = actor_info.actor
|
||
|
||
try:
|
||
if anim_name:
|
||
# 停止指定动画
|
||
if anim_name in actor.getAnimNames():
|
||
actor.stop(anim_name)
|
||
# 从活动动画列表中移除
|
||
if anim_name in actor_info.active_animations:
|
||
actor_info.active_animations.remove(anim_name)
|
||
# 更新动画状态
|
||
if anim_name in actor_info.animations:
|
||
actor_info.animations[anim_name].state = AnimationState.STOPPED
|
||
print(f"停止动画: {anim_name} (Actor: {actor_id})")
|
||
else:
|
||
print(f"警告: 动画 {anim_name} 不存在于Actor {actor_id} 中")
|
||
else:
|
||
# 停止所有动画
|
||
actor.stop()
|
||
# 清空活动动画列表
|
||
actor_info.active_animations.clear()
|
||
# 更新所有动画状态
|
||
for anim_info in actor_info.animations.values():
|
||
anim_info.state = AnimationState.STOPPED
|
||
print(f"停止所有动画 (Actor: {actor_id})")
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"停止动画失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def stop_all_animations(self, actor_id: str) -> bool:
|
||
"""
|
||
停止指定Actor的所有动画
|
||
:param actor_id: Actor标识符
|
||
:return: 是否成功停止
|
||
"""
|
||
return self.stop_animation(actor_id)
|
||
|
||
def pause_animation(self, actor_id: str, anim_name: Optional[str] = None) -> bool:
|
||
"""
|
||
暂停动画
|
||
:param actor_id: Actor标识符
|
||
:param anim_name: 动画名称,如果为None则暂停所有动画
|
||
:return: 是否成功暂停
|
||
"""
|
||
if actor_id not in self.actors:
|
||
print(f"错误: Actor {actor_id} 不存在")
|
||
return False
|
||
|
||
actor_info = self.actors[actor_id]
|
||
actor = actor_info.actor
|
||
|
||
try:
|
||
if anim_name:
|
||
if anim_name in actor.getAnimNames():
|
||
control = actor.getAnimControl(anim_name)
|
||
if control and control.isPlaying():
|
||
# 通过设置播放速度为0来模拟暂停
|
||
current_rate = actor.getPlayRate(anim_name)
|
||
actor.setPlayRate(0, anim_name)
|
||
# 更新动画状态
|
||
if anim_name in actor_info.animations:
|
||
actor_info.animations[anim_name].state = AnimationState.PAUSED
|
||
print(f"暂停动画: {anim_name} (Actor: {actor_id})")
|
||
return True
|
||
else:
|
||
print(f"警告: 动画 {anim_name} 不存在于Actor {actor_id} 中")
|
||
else:
|
||
# 暂停所有活动动画
|
||
success = False
|
||
for active_anim in actor_info.active_animations:
|
||
control = actor.getAnimControl(active_anim)
|
||
if control and control.isPlaying():
|
||
current_rate = actor.getPlayRate(active_anim)
|
||
actor.setPlayRate(0, active_anim)
|
||
if active_anim in actor_info.animations:
|
||
actor_info.animations[active_anim].state = AnimationState.PAUSED
|
||
success = True
|
||
if success:
|
||
print(f"暂停所有动画 (Actor: {actor_id})")
|
||
return success
|
||
|
||
except Exception as e:
|
||
print(f"暂停动画失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def resume_animation(self, actor_id: str, anim_name: Optional[str] = None) -> bool:
|
||
"""
|
||
恢复动画播放
|
||
:param actor_id: Actor标识符
|
||
:param anim_name: 动画名称,如果为None则恢复所有动画
|
||
:return: 是否成功恢复
|
||
"""
|
||
if actor_id not in self.actors:
|
||
print(f"错误: Actor {actor_id} 不存在")
|
||
return False
|
||
|
||
actor_info = self.actors[actor_id]
|
||
actor = actor_info.actor
|
||
|
||
try:
|
||
if anim_name:
|
||
if anim_name in actor.getAnimNames():
|
||
control = actor.getAnimControl(anim_name)
|
||
if control and control.getPlayRate() == 0:
|
||
# 恢复正常播放速度
|
||
actor.setPlayRate(1.0, anim_name)
|
||
# 更新动画状态
|
||
if anim_name in actor_info.animations:
|
||
anim_state = AnimationState.LOOPING if actor_info.animations[anim_name].loop else AnimationState.PLAYING
|
||
actor_info.animations[anim_name].state = anim_state
|
||
print(f"恢复动画: {anim_name} (Actor: {actor_id})")
|
||
return True
|
||
else:
|
||
print(f"警告: 动画 {anim_name} 不存在于Actor {actor_id} 中")
|
||
else:
|
||
# 恢复所有暂停的动画
|
||
success = False
|
||
for active_anim in actor_info.active_animations:
|
||
control = actor.getAnimControl(active_anim)
|
||
if control and control.getPlayRate() == 0:
|
||
actor.setPlayRate(1.0, active_anim)
|
||
if active_anim in actor_info.animations:
|
||
anim_state = AnimationState.LOOPING if actor_info.animations[active_anim].loop else AnimationState.PLAYING
|
||
actor_info.animations[active_anim].state = anim_state
|
||
success = True
|
||
if success:
|
||
print(f"恢复所有动画 (Actor: {actor_id})")
|
||
return success
|
||
|
||
except Exception as e:
|
||
print(f"恢复动画失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def set_animation_speed(self, actor_id: str, anim_name: str, speed: float) -> bool:
|
||
"""
|
||
设置动画播放速度
|
||
:param actor_id: Actor标识符
|
||
:param anim_name: 动画名称
|
||
:param speed: 播放速度
|
||
:return: 是否成功设置
|
||
"""
|
||
if actor_id not in self.actors:
|
||
print(f"错误: Actor {actor_id} 不存在")
|
||
return False
|
||
|
||
actor_info = self.actors[actor_id]
|
||
actor = actor_info.actor
|
||
|
||
try:
|
||
if anim_name in actor.getAnimNames():
|
||
actor.setPlayRate(speed, anim_name)
|
||
# 更新动画信息
|
||
if anim_name in actor_info.animations:
|
||
actor_info.animations[anim_name].play_rate = speed
|
||
print(f"设置动画速度: {anim_name} 为 {speed}x (Actor: {actor_id})")
|
||
return True
|
||
else:
|
||
print(f"警告: 动画 {anim_name} 不存在于Actor {actor_id} 中")
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(f"设置动画速度失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def blend_animations(self, actor_id: str, from_anim: str, to_anim: str,
|
||
blend_time: float = 0.3, blend_mode: BlendMode = BlendMode.LINEAR) -> bool:
|
||
"""
|
||
混合两个动画
|
||
:param actor_id: Actor标识符
|
||
:param from_anim: 起始动画名称
|
||
:param to_anim: 目标动画名称
|
||
:param blend_time: 混合时间
|
||
:param blend_mode: 混合模式
|
||
:return: 是否成功开始混合
|
||
"""
|
||
if actor_id not in self.actors:
|
||
print(f"错误: Actor {actor_id} 不存在")
|
||
return False
|
||
|
||
actor_info = self.actors[actor_id]
|
||
actor = actor_info.actor
|
||
|
||
# 检查动画是否存在
|
||
if from_anim not in actor.getAnimNames():
|
||
print(f"错误: 起始动画 {from_anim} 不存在于Actor {actor_id} 中")
|
||
return False
|
||
|
||
if to_anim not in actor.getAnimNames():
|
||
print(f"错误: 目标动画 {to_anim} 不存在于Actor {actor_id} 中")
|
||
return False
|
||
|
||
try:
|
||
# 创建混合信息
|
||
blend_info = BlendInfo(
|
||
from_anim=from_anim,
|
||
to_anim=to_anim,
|
||
blend_time=blend_time,
|
||
blend_mode=blend_mode,
|
||
current_time=0.0,
|
||
total_time=blend_time,
|
||
active=True
|
||
)
|
||
|
||
# 存储混合信息
|
||
blend_id = f"{from_anim}_to_{to_anim}"
|
||
self.blend_infos[actor_id][blend_id] = blend_info
|
||
|
||
# 开始混合过程
|
||
self._start_animation_blend(actor_info, blend_info)
|
||
|
||
self.stats['blends_performed'] += 1
|
||
print(f"开始动画混合: {from_anim} -> {to_anim} (Actor: {actor_id}, 时间: {blend_time}s)")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"动画混合失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def _start_animation_blend(self, actor_info: ActorInfo, blend_info: BlendInfo) -> None:
|
||
"""
|
||
开始动画混合过程
|
||
:param actor_info: Actor信息
|
||
:param blend_info: 混合信息
|
||
"""
|
||
actor = actor_info.actor
|
||
from_anim = blend_info.from_anim
|
||
to_anim = blend_info.to_anim
|
||
|
||
# 停止起始动画
|
||
actor.stop(from_anim)
|
||
|
||
# 播放目标动画
|
||
actor.play(to_anim)
|
||
|
||
# 设置混合权重(简化实现)
|
||
if to_anim in actor_info.animations:
|
||
actor_info.animations[to_anim].blend_weight = 1.0
|
||
|
||
def get_actor(self, actor_id: str) -> Optional[Actor]:
|
||
"""
|
||
获取Actor对象
|
||
:param actor_id: Actor标识符
|
||
:return: Actor对象或None
|
||
"""
|
||
if actor_id in self.actors:
|
||
return self.actors[actor_id].actor
|
||
return None
|
||
|
||
def get_actor_animations(self, actor_id: str) -> List[str]:
|
||
"""
|
||
获取Actor的所有动画名称
|
||
:param actor_id: Actor标识符
|
||
:return: 动画名称列表
|
||
"""
|
||
if actor_id in self.actors:
|
||
return list(self.actors[actor_id].animations.keys())
|
||
return []
|
||
|
||
def get_animation_info(self, actor_id: str, anim_name: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取动画信息
|
||
:param actor_id: Actor标识符
|
||
:param anim_name: 动画名称
|
||
:return: 动画信息字典
|
||
"""
|
||
if actor_id not in self.actors:
|
||
print(f"错误: Actor {actor_id} 不存在")
|
||
return None
|
||
|
||
actor_info = self.actors[actor_id]
|
||
|
||
if anim_name not in actor_info.animations:
|
||
print(f"错误: 动画 {anim_name} 不存在于Actor {actor_id} 中")
|
||
return None
|
||
|
||
anim_info = actor_info.animations[anim_name]
|
||
actor = actor_info.actor
|
||
control = actor.getAnimControl(anim_name) if anim_name in actor.getAnimNames() else None
|
||
|
||
return {
|
||
'name': anim_info.name,
|
||
'frame_rate': anim_info.frame_rate,
|
||
'num_frames': anim_info.num_frames,
|
||
'play_rate': anim_info.play_rate,
|
||
'current_frame': anim_info.current_frame,
|
||
'state': anim_info.state.name,
|
||
'loop': anim_info.loop,
|
||
'start_time': anim_info.start_time,
|
||
'duration': anim_info.duration,
|
||
'is_playing': control.isPlaying() if control else False,
|
||
'is_finished': control.isFinished() if control else True
|
||
}
|
||
|
||
def get_actor_info(self, actor_id: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取Actor详细信息
|
||
:param actor_id: Actor标识符
|
||
:return: Actor信息字典
|
||
"""
|
||
if actor_id not in self.actors:
|
||
print(f"错误: Actor {actor_id} 不存在")
|
||
return None
|
||
|
||
actor_info = self.actors[actor_id]
|
||
actor = actor_info.actor
|
||
|
||
return {
|
||
'id': actor_info.actor_id,
|
||
'model_path': actor_info.model_path,
|
||
'anim_paths': actor_info.anim_paths,
|
||
'position': actor.getPos().toTuple(),
|
||
'scale': actor.getScale().toTuple(),
|
||
'available_animations': list(actor_info.animations.keys()),
|
||
'active_animations': actor_info.active_animations.copy(),
|
||
'lod_level': actor_info.lod_level.name,
|
||
'has_character': actor.hasCharacter(),
|
||
'num_parts': actor.getNumParts(),
|
||
'bounds': {
|
||
'min': actor.getTightBounds()[0].toTuple() if actor.getTightBounds() else None,
|
||
'max': actor.getTightBounds()[1].toTuple() if actor.getTightBounds() else None
|
||
}
|
||
}
|
||
|
||
def set_actor_position(self, actor_id: str, x: float, y: float, z: float) -> bool:
|
||
"""
|
||
设置Actor位置
|
||
:param actor_id: Actor标识符
|
||
:param x: X坐标
|
||
:param y: Y坐标
|
||
:param z: Z坐标
|
||
:return: 是否成功设置
|
||
"""
|
||
if actor_id in self.actors:
|
||
self.actors[actor_id].actor.setPos(x, y, z)
|
||
self.actors[actor_id].position = (x, y, z)
|
||
if actor_id in self.actor_configs:
|
||
self.actor_configs[actor_id]['position'] = (x, y, z)
|
||
print(f"设置Actor {actor_id} 位置: ({x}, {y}, {z})")
|
||
return True
|
||
return False
|
||
|
||
def set_actor_scale(self, actor_id: str, scale: Union[float, Tuple[float, float, float]]) -> bool:
|
||
"""
|
||
设置Actor缩放
|
||
:param actor_id: Actor标识符
|
||
:param scale: 缩放值 (数值或(x, y, z)元组)
|
||
:return: 是否成功设置
|
||
"""
|
||
if actor_id in self.actors:
|
||
actor = self.actors[actor_id].actor
|
||
if isinstance(scale, (int, float)):
|
||
actor.setScale(scale)
|
||
else:
|
||
actor.setScale(*scale)
|
||
self.actors[actor_id].scale = scale
|
||
if actor_id in self.actor_configs:
|
||
self.actor_configs[actor_id]['scale'] = scale
|
||
print(f"设置Actor {actor_id} 缩放: {scale}")
|
||
return True
|
||
return False
|
||
|
||
def update(self, dt: float) -> None:
|
||
"""
|
||
更新动画系统
|
||
:param dt: 时间增量
|
||
"""
|
||
if not self.is_updating:
|
||
return
|
||
|
||
current_time = globalClock.getRealTime()
|
||
|
||
# 更新所有Actor
|
||
for actor_id, actor_info in self.actors.items():
|
||
# 更新动画信息
|
||
self._update_actor_animations(actor_info, dt, current_time)
|
||
|
||
# 更新混合
|
||
self._update_blends(actor_info, dt)
|
||
|
||
# 更新IK
|
||
self._update_ik(actor_info)
|
||
|
||
# 更新LOD
|
||
self._update_lod(actor_info, current_time)
|
||
|
||
# 更新工具系统
|
||
self._update_tools(dt)
|
||
|
||
def _update_actor_animations(self, actor_info: ActorInfo, dt: float, current_time: float) -> None:
|
||
"""
|
||
更新Actor动画
|
||
:param actor_info: Actor信息
|
||
:param dt: 时间增量
|
||
:param current_time: 当前时间
|
||
"""
|
||
actor = actor_info.actor
|
||
|
||
# 更新活动动画的当前帧信息
|
||
for anim_name in actor_info.active_animations:
|
||
if anim_name in actor_info.animations:
|
||
anim_info = actor_info.animations[anim_name]
|
||
control = actor.getAnimControl(anim_name) if anim_name in actor.getAnimNames() else None
|
||
|
||
if control and control.isPlaying():
|
||
anim_info.current_frame = actor.getCurrentFrame(anim_name)
|
||
anim_info.state = AnimationState.LOOPING if anim_info.loop else AnimationState.PLAYING
|
||
elif control and control.getPlayRate() == 0:
|
||
anim_info.state = AnimationState.PAUSED
|
||
else:
|
||
anim_info.state = AnimationState.STOPPED
|
||
|
||
actor_info.last_update_time = current_time
|
||
|
||
def _update_blends(self, actor_info: ActorInfo, dt: float) -> None:
|
||
"""
|
||
更新动画混合
|
||
:param actor_info: Actor信息
|
||
:param dt: 时间增量
|
||
"""
|
||
actor_id = actor_info.actor_id
|
||
|
||
if actor_id not in self.blend_infos:
|
||
return
|
||
|
||
# 更新所有混合
|
||
finished_blends = []
|
||
for blend_id, blend_info in self.blend_infos[actor_id].items():
|
||
if not blend_info.active:
|
||
continue
|
||
|
||
blend_info.current_time += dt
|
||
|
||
# 计算混合权重
|
||
t = min(1.0, blend_info.current_time / blend_info.total_time)
|
||
|
||
# 根据混合模式应用缓动函数
|
||
if blend_info.blend_mode == BlendMode.EASE_IN:
|
||
t = t * t
|
||
elif blend_info.blend_mode == BlendMode.EASE_OUT:
|
||
t = 1.0 - (1.0 - t) * (1.0 - t)
|
||
elif blend_info.blend_mode == BlendMode.EASE_IN_OUT:
|
||
t = t * t * (3.0 - 2.0 * t)
|
||
|
||
# 更新目标动画的混合权重
|
||
if blend_info.to_anim in actor_info.animations:
|
||
actor_info.animations[blend_info.to_anim].blend_weight = t
|
||
|
||
# 检查混合是否完成
|
||
if blend_info.current_time >= blend_info.total_time:
|
||
blend_info.active = False
|
||
finished_blends.append(blend_id)
|
||
|
||
# 移除已完成的混合
|
||
for blend_id in finished_blends:
|
||
del self.blend_infos[actor_id][blend_id]
|
||
|
||
def _update_ik(self, actor_info: ActorInfo) -> None:
|
||
"""
|
||
更新IK计算
|
||
:param actor_info: Actor信息
|
||
"""
|
||
actor_id = actor_info.actor_id
|
||
|
||
if actor_id not in self.ik_chains:
|
||
return
|
||
|
||
# 更新所有IK链
|
||
for chain_name, ik_chain in self.ik_chains[actor_id].items():
|
||
if not ik_chain.enabled or not ik_chain.target_position:
|
||
continue
|
||
|
||
self._solve_ik_chain(actor_info, ik_chain)
|
||
self.stats['ik_calculations'] += 1
|
||
|
||
def _solve_ik_chain(self, actor_info: ActorInfo, ik_chain: IKChain) -> None:
|
||
"""
|
||
解算IK链
|
||
:param actor_info: Actor信息
|
||
:param ik_chain: IK链信息
|
||
"""
|
||
actor = actor_info.actor
|
||
|
||
# 简化的CCD IK算法实现
|
||
end_effector = actor.exposeJoint(None, "modelRoot", ik_chain.end_effector)
|
||
if not end_effector:
|
||
return
|
||
|
||
target_pos = Point3(*ik_chain.target_position)
|
||
current_pos = end_effector.getPos(actor)
|
||
|
||
# 如果已经足够接近,不需要调整
|
||
distance = (target_pos - current_pos).length()
|
||
if distance < 0.001:
|
||
return
|
||
|
||
# CCD算法迭代
|
||
for _ in range(ik_chain.iterations):
|
||
# 从末端关节向前迭代调整
|
||
# 这里简化处理,实际应该遍历整条骨骼链
|
||
direction = target_pos - current_pos
|
||
direction.normalize()
|
||
|
||
# 简单的朝向调整
|
||
end_effector.lookAt(target_pos)
|
||
|
||
# 更新位置
|
||
current_pos = end_effector.getPos(actor)
|
||
distance = (target_pos - current_pos).length()
|
||
if distance < 0.001:
|
||
break
|
||
|
||
def _update_lod(self, actor_info: ActorInfo, current_time: float) -> None:
|
||
"""
|
||
更新LOD级别
|
||
:param actor_info: Actor信息
|
||
:param current_time: 当前时间
|
||
"""
|
||
# LOD更新可以基于距离、性能或其他因素
|
||
# 这里简化实现,实际项目中可能需要更复杂的逻辑
|
||
pass
|
||
|
||
def _update_tools(self, dt: float) -> None:
|
||
"""
|
||
更新工具系统
|
||
:param dt: 时间增量
|
||
"""
|
||
# 更新性能分析器
|
||
self.profiler.sample_performance()
|
||
|
||
# 更新调试可视化
|
||
self.visualizer.update_visualizations(self.actors)
|
||
|
||
def add_ik_chain(self, actor_id: str, chain_name: str, end_effector: str,
|
||
pole_vector: Optional[Tuple[float, float, float]] = None,
|
||
iterations: int = 10) -> bool:
|
||
"""
|
||
添加IK链
|
||
:param actor_id: Actor标识符
|
||
:param chain_name: 链名称
|
||
:param end_effector: 终端效应器(骨骼名称)
|
||
:param pole_vector: 极向量(用于控制弯曲方向)
|
||
:param iterations: 迭代次数
|
||
:return: 是否成功添加
|
||
"""
|
||
if actor_id not in self.actors:
|
||
print(f"错误: Actor {actor_id} 不存在")
|
||
return False
|
||
|
||
# 检查骨骼是否存在
|
||
actor = self.actors[actor_id].actor
|
||
effector_joint = actor.exposeJoint(None, "modelRoot", end_effector)
|
||
if not effector_joint:
|
||
print(f"错误: 未找到骨骼 {end_effector}")
|
||
return False
|
||
|
||
# 创建IK链
|
||
ik_chain = IKChain(
|
||
chain_name=chain_name,
|
||
end_effector=end_effector,
|
||
pole_vector=pole_vector,
|
||
iterations=iterations,
|
||
enabled=True
|
||
)
|
||
|
||
# 存储IK链
|
||
self.ik_chains[actor_id][chain_name] = ik_chain
|
||
print(f"添加IK链: {chain_name} -> {end_effector} (Actor: {actor_id})")
|
||
return True
|
||
|
||
def set_ik_target(self, actor_id: str, chain_name: str, target_pos: Tuple[float, float, float]) -> bool:
|
||
"""
|
||
设置IK目标位置
|
||
:param actor_id: Actor标识符
|
||
:param chain_name: 链名称
|
||
:param target_pos: 目标位置 (x, y, z)
|
||
:return: 是否成功设置
|
||
"""
|
||
if actor_id not in self.ik_chains or chain_name not in self.ik_chains[actor_id]:
|
||
print(f"错误: IK链 {chain_name} 不存在于Actor {actor_id} 中")
|
||
return False
|
||
|
||
# 设置目标位置
|
||
self.ik_chains[actor_id][chain_name].target_position = target_pos
|
||
return True
|
||
|
||
def enable_ik_chain(self, actor_id: str, chain_name: str, enabled: bool = True) -> bool:
|
||
"""
|
||
启用/禁用IK链
|
||
:param actor_id: Actor标识符
|
||
:param chain_name: 链名称
|
||
:param enabled: 是否启用
|
||
:return: 是否成功设置
|
||
"""
|
||
if actor_id not in self.ik_chains or chain_name not in self.ik_chains[actor_id]:
|
||
print(f"错误: IK链 {chain_name} 不存在于Actor {actor_id} 中")
|
||
return False
|
||
|
||
self.ik_chains[actor_id][chain_name].enabled = enabled
|
||
status = "启用" if enabled else "禁用"
|
||
print(f"{status}IK链: {chain_name} (Actor: {actor_id})")
|
||
return True
|
||
|
||
def get_cache_stats(self) -> Dict[str, Any]:
|
||
"""
|
||
获取缓存统计信息
|
||
:return: 缓存统计
|
||
"""
|
||
return self.animation_cache.get_stats()
|
||
|
||
def get_system_stats(self) -> Dict[str, Any]:
|
||
"""
|
||
获取系统统计信息
|
||
:return: 系统统计
|
||
"""
|
||
cache_stats = self.animation_cache.get_stats()
|
||
return {
|
||
'actors_count': len(self.actors),
|
||
'animations_played': self.stats['animations_played'],
|
||
'blends_performed': self.stats['blends_performed'],
|
||
'ik_calculations': self.stats['ik_calculations'],
|
||
'cache_stats': cache_stats,
|
||
'active_actors': len([a for a in self.actors.values() if a.active_animations])
|
||
}
|
||
|
||
def start_updating(self) -> None:
|
||
"""
|
||
开始更新动画系统
|
||
"""
|
||
if not self.is_updating:
|
||
self.is_updating = True
|
||
print("动画系统更新已启动")
|
||
|
||
def stop_updating(self) -> None:
|
||
"""
|
||
停止更新动画系统
|
||
"""
|
||
if self.is_updating:
|
||
self.is_updating = False
|
||
print("动画系统更新已停止")
|
||
|
||
def clear_cache(self) -> None:
|
||
"""
|
||
清空动画缓存
|
||
"""
|
||
self.animation_cache.clear()
|
||
print("动画缓存已清空")
|
||
|
||
def reset_stats(self) -> None:
|
||
"""
|
||
重置统计信息
|
||
"""
|
||
self.stats = {
|
||
'actors_loaded': 0,
|
||
'animations_played': 0,
|
||
'blends_performed': 0,
|
||
'ik_calculations': 0,
|
||
'cache_hits': 0,
|
||
'cache_misses': 0
|
||
}
|
||
self.animation_cache.hits = 0
|
||
self.animation_cache.misses = 0
|
||
print("统计信息已重置")
|
||
|
||
# 工具系统访问方法
|
||
def get_analyzer(self) -> AnimationAnalyzer:
|
||
"""
|
||
获取动画分析器
|
||
:return: AnimationAnalyzer实例
|
||
"""
|
||
return self.analyzer
|
||
|
||
def get_editor(self) -> AnimationEditor:
|
||
"""
|
||
获取动画编辑器
|
||
:return: AnimationEditor实例
|
||
"""
|
||
return self.editor
|
||
|
||
def get_visualizer(self) -> DebugVisualizer:
|
||
"""
|
||
获取调试可视化工具
|
||
:return: DebugVisualizer实例
|
||
"""
|
||
return self.visualizer
|
||
|
||
def get_profiler(self) -> AnimationProfiler:
|
||
"""
|
||
获取性能分析器
|
||
:return: AnimationProfiler实例
|
||
"""
|
||
return self.profiler |