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