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

1207 lines
44 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.

"""
高级骨骼动画系统 - 核心模块
提供完整的骨骼动画支持,包括加载、播放、控制等核心功能
"""
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