diff --git a/RenderPipelineFile/rpcore/util/movement_controller.py b/RenderPipelineFile/rpcore/util/movement_controller.py index 72161c7b..4dbb91e1 100644 --- a/RenderPipelineFile/rpcore/util/movement_controller.py +++ b/RenderPipelineFile/rpcore/util/movement_controller.py @@ -138,8 +138,8 @@ class MovementController(object): self.showbase.accept("j", self.print_position) # mouse - self.showbase.accept("mouse1", self.set_mouse_enabled, [True]) - self.showbase.accept("mouse1-up", self.set_mouse_enabled, [False]) + self.showbase.accept("mouse3", self.set_mouse_enabled, [True]) + self.showbase.accept("mouse3-up", self.set_mouse_enabled, [False]) # arrow mouse navigation self.showbase.accept("arrow_up", self.set_hpr_movement, [1, 1]) diff --git a/main.py b/main.py index d03ddd78..b46c4dcf 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,8 @@ import warnings +from core.Command_System import CommandManager from core.InfoPanelManager import InfoPanelManager +from core.patrol_system import PatrolSystem from demo.video_integration import VideoManager warnings.filterwarnings("ignore", category=DeprecationWarning) diff --git a/templates/main_template.py b/templates/main_template.py index 51ededf4..b7af3776 100644 --- a/templates/main_template.py +++ b/templates/main_template.py @@ -11,7 +11,9 @@ from __future__ import print_function import json from direct.actor.Actor import Actor -from panda3d.core import TextNode, CardMaker, TextureStage, NodePath, Texture, TransparencyAttrib, CollisionTraverser +from direct.showbase.ShowBaseGlobal import globalClock +from panda3d.core import TextNode, CardMaker, TextureStage, NodePath, Texture, TransparencyAttrib, CollisionTraverser, \ + Point3 from core.InfoPanelManager import InfoPanelManager # 获取渲染管线路径 # 在文件开头添加sys导入(如果还没有的话) @@ -79,9 +81,6 @@ class MainApp(ShowBase): from rpcore import RenderPipeline self.render_pipeline = RenderPipeline() self.render_pipeline.create(self) - #self.render_pipeline.pre_show_base_init() - #ShowBase.__init__(self) - except ImportError as e: @@ -103,6 +102,9 @@ class MainApp(ShowBase): try: # 再导入controller模块 from rpcore.util.movement_controller import MovementController + + self.render_pipeline._showbase.camera = self.render_pipeline._showbase.cam + self.controller = MovementController(self) self.controller.set_initial_position( Vec3(0, -50, 20), Vec3(0, 0, 0)) @@ -115,6 +117,12 @@ class MainApp(ShowBase): self._last_clicked_node = None self._double_click_threshold = 0.3 + self.cameraSpeed = 20.0 + self.cameraRotateSpeed=10.0 + + self.lastMouseX=0 + self.lastMouseY=0 + self.mouseRightPressed=False self._loadFont() self.loadFullScene() @@ -122,6 +130,9 @@ class MainApp(ShowBase): self.setupMouseClickHandler() self.cTrav = CollisionTraverser() + self.women_actor = None + self.current_actor = None + if hasattr(self, 'accept'): base.accept("l", self.tour) @@ -182,6 +193,38 @@ class MainApp(ShowBase): import traceback traceback.print_exc() + def checkAnimationStatus(self): + """检查场景中所有Actor的动画状态""" + try: + all_actors = self.render.findAllMatches("**/+ActorNode") + print(f"=== 动画状态检查 ===") + print(f"场景中总共有 {len(all_actors)} 个Actor") + + for i, actor_np in enumerate(all_actors): + actor_node = actor_np.node() + if isinstance(actor_node, Actor): + # 确保Actor可见 + actor_np.show() + + current_anim = actor_node.getCurrentAnim() + anim_names = actor_node.getAnimNames() + print(f"Actor {i + 1} ({actor_np.getName()}):") + print(f" 位置: {actor_np.getPos()}") + print(f" 可见性: {actor_np.isHidden()}") + print(f" 可用动画: {anim_names}") + print(f" 当前播放: {current_anim}") + if current_anim: + frame = actor_node.getCurrentFrame(current_anim) + num_frames = actor_node.getNumFrames(current_anim) + play_rate = actor_node.getPlayRate(current_anim) + print(f" 播放进度: {frame}/{num_frames} 帧") + print(f" 播放速度: {play_rate}") + else: + print(f"节点 {actor_np.getName()} 不是Actor类型") + print("==================") + except Exception as e: + print(f"检查动画状态时出错: {str(e)}") + def _processModelAnimations(self, node_path): """处理节点中的动画模型""" try: @@ -234,19 +277,38 @@ class MainApp(ShowBase): import traceback traceback.print_exc() + # 在 processSceneElements 方法中修改碰撞体创建部分 + # 修复 processSceneElements 方法中的碰撞体创建 def processSceneElements(self, scene): """处理场景中的各种元素""" try: processed_lights = [] loaded_nodes = {} - def processNode(nodePath,depth=0): + + def processNode(nodePath, depth=0): loaded_nodes[nodePath.getName()] = nodePath + # 为模型添加碰撞体 - 修复版本 + if nodePath.hasTag("is_scene_element") and not nodePath.hasTag("is_gizmo"): + # 使用更精确的包围盒 + from panda3d.core import CollisionNode, CollisionBox + bounds = nodePath.getBounds() + if not bounds.isEmpty(): + min_point = bounds.getMin() + max_point = bounds.getMax() + + # 创建碰撞节点 + collision_node = CollisionNode(f'collision_{nodePath.getName()}') + collision_node.addSolid(CollisionBox(min_point, max_point)) + collision_np = nodePath.attachNewNode(collision_node) + # 隐藏碰撞体 + collision_np.hide() + if nodePath.hasTag("scripts_info"): try: import json scripts_info = json.loads(nodePath.getTag("scripts_info")) - self.processScripts(nodePath,scripts_info) + self.processScripts(nodePath, scripts_info) except Exception as e: print(f"处理节点 {nodePath.getName()} 的脚本时出错: {str(e)}") @@ -261,13 +323,10 @@ class MainApp(ShowBase): processed_lights.append(nodePath) for child in nodePath.getChildren(): - processNode(child,depth+1) + processNode(child, depth + 1) processNode(scene) - # 处理GUI元素 - #self.processGUIElements(scene) - except Exception as e: print(f"处理场景元素时出错: {str(e)}") @@ -432,7 +491,7 @@ class MainApp(ShowBase): pos=tuple(absolute_position), text=text, size=absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0, - #command=self.resetWomenModel + command=self.playModelAnimation ) elif gui_type == "label": new_element = self.createGUILabel( @@ -721,45 +780,131 @@ class MainApp(ShowBase): texture.set_loop(True) texture.set_play_rate(1.0) - def resetWomenModel(self): - """调整 Women_1.glb 模型大小,实现从小到大再到小的完整循环效果""" + def playModelAnimation(self): + """播放场景中所有 .glb 模型的动画(仅转换有动画的模型为 Actor)""" try: - # 查找 Women_1.glb 模型 - women_models = self.render.findAllMatches("**/Women_1.glb*") + glb_models = self.render.findAllMatches("**/*.glb*") - if women_models: - for model in women_models: - # 定义完整的缩放级别序列(从0.5到3.0再回到0.5) - scale_levels = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 2.5, 2.0, 1.5, 1.0, 0.5] + # 修复过滤逻辑,确保正确排除碰撞体节点 + filtered_models = [] + for model in glb_models: + model_name = model.getName().lower() + # 排除碰撞体节点 + if ("collision_" not in model_name and + "modelcollision_" not in model_name and + not model_name.endswith("_bounds")): + filtered_models.append(model) - # 获取当前缩放值 - current_scale = model.getScale() + if not filtered_models: + print("⚠️ 场景中没有找到 .glb 模型") + return - # 查找当前最接近的缩放级别索引 - current_index = 0 - min_diff = float('inf') - for i, scale in enumerate(scale_levels): - diff = abs(current_scale.x - scale) - if diff < min_diff: - min_diff = diff - current_index = i + print(f"找到 {len(filtered_models)} 个 glb 模型") - # 计算下一个缩放级别(循环) - next_index = (current_index + 1) % len(scale_levels) - next_scale = scale_levels[next_index] + self.actors = [] # 存储所有 Actor,避免被垃圾回收 - # 应用新的缩放 - model.setScale(next_scale) - print(f"✓ 调整模型 {model.getName()} 大小: {current_scale.x:.1f} -> {next_scale:.1f}") - print(f" 当前索引: {current_index}, 下一个索引: {next_index}") - else: - print("⚠️ 未找到 Women_1.glb 模型") + for model in filtered_models: + print(f"正在处理模型: {model.getName()}") + + actor = None + + # 尝试把模型当作Actor加载,判断是否有动画 + try: + # 在创建Actor之前先检查节点是否可能包含动画 + if not self._isValidAnimationNode(model): + print(f"⚠️ {model.getName()} 不是有效的动画节点,跳过") + continue + + test_actor = Actor(model) + anim_names = test_actor.getAnimNames() + except Exception as e: + print(f"⚠️ {model.getName()} 无法作为Actor加载: {str(e)}") + anim_names = [] + + if anim_names: # ✅ 只有有动画才转为Actor + if not isinstance(model, Actor): + model_parent = model.getParent() + model_pos = model.getPos() + model_hpr = model.getHpr() + model_scale = model.getScale() + + actor = Actor(model) + actor.reparentTo(model_parent) + actor.setPos(model_pos) + actor.setHpr(model_hpr) + actor.setScale(model_scale) + + model.detachNode() + else: + actor = model + + # 播放动画 + actor.show() + self.actors.append(actor) + + print(f"✓ {actor.getName()} 可用动画: {anim_names}") + first_anim = anim_names[0] + + actor.stop() + actor.setPlayRate(1.0, first_anim) + actor.loop(first_anim) + print(f"✓ {actor.getName()} 正在播放动画: {first_anim}") + + actor.update() + + else: # 没有动画 + print(f"⚠️ {model.getName()} 没有动画,不转为Actor") except Exception as e: - print(f"调整模型大小时出错: {str(e)}") + print(f"播放模型动画时出错: {str(e)}") import traceback traceback.print_exc() + def _isValidAnimationNode(self, nodePath): + """检查节点是否可能是有效的动画节点""" + # 排除明显不是动画节点的节点 + name = nodePath.getName().lower() + if ("collision_" in name or + "modelcollision_" in name or + "bound" in name or + name.endswith("_bounds")): + return False + + # 检查节点是否有网格数据(简单判断) + from panda3d.core import GeomNode + geom_nodes = nodePath.findAllMatches("**/+GeomNode") + if not geom_nodes: + return False + + return True + + def focusOnWomenModel(self): + """定位并聚焦到Women模型""" + try: + women_models = self.render.findAllMatches("**/Women_1.glb*") + if women_models: + model = women_models[0] + # 确保模型可见 + model.show() + + # 将模型放置在原点附近 + model.setPos(0, 0, 0) + model.setScale(1.0) + + # 确保是Actor的话设置动画 + if isinstance(model, Actor): + anim_names = model.getAnimNames() + if anim_names: + model.loop(anim_names[0]) + model.setPlayRate(1.0, anim_names[0]) + print(f"为模型设置动画: {anim_names[0]}") + + print(f"已定位模型: {model.getName()}") + else: + print("未找到Women模型") + except Exception as e: + print(f"定位模型时出错: {str(e)}") + def processScripts(self, element, script_info_list): """处理元素上挂载的脚本 - 使用新的脚本系统""" try: @@ -812,51 +957,92 @@ class MainApp(ShowBase): import traceback traceback.print_exc() - def checkDoubleClick(self,nodePath): + def checkDoubleClick(self, nodePath): + """检查是否为双击,返回布尔值 - 改进版本""" try: import time current_time = time.time() - is_double_click = (self._last_clicked_node == nodePath and + # 必须是同一节点且在时间阈值内 + is_double_click = (self._last_clicked_node is not None and + self._last_clicked_node == nodePath and nodePath is not None and - current_time - self._last_click_time {end_pos}") - def _normalizeAngle(self,angle): - while angle >180: + except Exception as e: + print(f"相机聚焦动画启动失败: {str(e)}") + + def _normalizeAngle(self, angle): + """规范化角度到-180到180度之间""" + while angle > 180: angle -= 360 while angle < -180: angle += 360 return angle - def _cameraFocusTask(self,task): + def _cameraFocusTask(self, task): + """摄像机聚焦动画任务""" try: - if not hasattr(self,'_camera_focus_data'): + if not hasattr(self, '_camera_focus_data'): return task.done data = self._camera_focus_data from direct.showbase.ShowBaseGlobal import globalClock - data.elapsed_time += globalClock.getDt() + dt = globalClock.getDt() - t = min(1.0,data.elapsed_time/data.duration) + if dt <= 0: + dt = 0.016 - smooth_t = t*t*(3-2*t) + data.elapsed_time += dt + t = min(1.0, data.elapsed_time / data.duration) + # 使用平滑插值 + smooth_t = t * t * (3 - 2 * t) + + # 插值位置和朝向 current_pos = data.start_pos + (data.end_pos - data.start_pos) * smooth_t + current_hpr = data.start_hpr + (data.end_hpr - data.start_hpr) * smooth_t + self.cam.setPos(current_pos) - - start_h = self._normalizeAngle(data.start_hpr.x) - end_h = self._normalizeAngle(data.end_hpr.x) - start_p = self._normalizeAngle(data.start_hpr.y) - end_p = self._normalizeAngle(data.end_hpr.y) - start_r = self._normalizeAngle(data.start_hpr.z) - end_r = self._normalizeAngle(data.end_hpr.z) - - if abs(end_h - start_h)>180: - if end_h > start_h: - start_h += 360 - else: - end_h += 360 - - if abs(end_p - start_p) > 180: - if end_p > start_p: - start_p += 360 - else: - end_p += 360 - - if abs(end_r - start_r)>180: - if end_r > start_r: - start_r += 360 - else: - end_r += 360 - - current_hpr = Vec3( - start_h + (end_h - start_h)*smooth_t, - start_p + (end_p - start_p)*smooth_t, - start_r + (end_r - start_r)*smooth_t - ) - - current_hpr.x = self._normalizeAngle(current_hpr.x) - current_hpr.y = self._normalizeAngle(current_hpr.y) - current_hpr.z = self._normalizeAngle(current_hpr.z) - self.cam.setHpr(current_hpr) - if t>=1.0: + if t >= 1.0: + self.cam.setPos(data.end_pos) + self.cam.setHpr(data.end_hpr) + + print(f"聚焦完成: 位置 {data.end_pos}, 朝向 {data.end_hpr}") + + if hasattr(self, '_camera_focus_data'): + delattr(self, '_camera_focus_data') + return task.done return task.cont + except Exception as e: - print(f"摄像机聚焦动画失败{e}") + print(f"摄像机聚焦动画任务失败: {e}") + import traceback + traceback.print_exc() return task.done def setupMouseClickHandler(self): + """设置鼠标点击处理器""" try: + # 设置鼠标监听 self.accept("mouse1", self.onMouseClick) - if not hasattr(self,'mouseWatcherNode'): + # 启用鼠标观察器 + if not hasattr(self, 'mouseWatcherNode'): from panda3d.core import MouseWatcher self.mouseWatcherNode = MouseWatcher() + except Exception as e: print(f"设置鼠标点击处理器失败: {e}") def onMouseClick(self): + """处理鼠标左键点击""" try: if not hasattr(self, 'mouseWatcherNode') or not self.mouseWatcherNode.hasMouse(): return + # 获取鼠标位置 mouse_pos = self.mouseWatcherNode.getMouse() + # 创建射线进行点击检测 from panda3d.core import CollisionRay, CollisionNode, CollisionHandlerQueue, BitMask32 - from panda3d.core import GeomNode # 创建射线 ray = CollisionRay() @@ -991,7 +1177,7 @@ class MainApp(ShowBase): # 创建碰撞节点 ray_node = CollisionNode('mouseRay') ray_node.addSolid(ray) - ray_node.setFromCollideMask(BitMask32.bit(0)) + ray_node.setFromCollideMask(BitMask32.allOn()) # 附加到相机 ray_np = self.camera.attachNewNode(ray_node) @@ -999,43 +1185,73 @@ class MainApp(ShowBase): # 创建碰撞队列 handler = CollisionHandlerQueue() - # 修复语法错误:正确初始化碰撞遍历器 - if not hasattr(self, 'cTrav') or self.cTrav is None: + # 确保碰撞遍历器存在 + if not hasattr(self, 'cTrav'): self.cTrav = CollisionTraverser() self.cTrav.addCollider(ray_np, handler) self.cTrav.traverse(self.render) # 检查碰撞结果 + target_node = None if handler.getNumEntries() > 0: # 按距离排序 handler.sortEntries() - # 获取最近的碰撞节点 - entry = handler.getEntry(0) - clicked_node = entry.getIntoNodePath() + # 遍历所有碰撞结果,找到最近的有效节点 + for i in range(handler.getNumEntries()): + entry = handler.getEntry(i) + clicked_node = entry.getIntoNodePath() - # 向上遍历找到可选择的父节点 - current_node = clicked_node - while current_node and not current_node.isEmpty(): - # 检查节点是否应该被选择 - if (current_node.hasTag("is_scene_element") or - current_node.hasTag("element_type") or - current_node.getName() not in ["render", "camera", "ambient_light", "directional_light"]): + # 向上遍历找到可选择的父节点 + current_node = clicked_node + found_valid_node = False - # 检查是否为双击 - if self.checkDoubleClick(current_node): - print(f"双击节点: {current_node.getName()}") - self.focusCameraOnNode(current_node) + while current_node and not current_node.isEmpty(): + # 检查节点是否应该被选择 + if (current_node.hasTag("is_scene_element") and + not current_node.hasTag("is_gizmo") and + current_node.getName() not in ["render", "camera", "ambient_light", + "directional_light", "mouseRay"] and + not current_node.getName().startswith("collision_") and + "ground" not in current_node.getName().lower()): + target_node = current_node + found_valid_node = True break - else: - print(f"单击节点: {current_node.getName()}") - current_node = current_node.getParent() - # 避免无限循环 - if current_node.getName() == "render": + # 如果当前节点是碰撞体,获取其父节点作为目标 + if current_node.getName().startswith("collision_"): + parent = current_node.getParent() + if (parent.hasTag("is_scene_element") and + not parent.hasTag("is_gizmo") and + parent.getName() not in ["render", "camera", "ambient_light", + "directional_light"] and + "ground" not in parent.getName().lower()): + target_node = parent + found_valid_node = True + break + + current_node = current_node.getParent() + if current_node.getName() == "render": + break + + if found_valid_node: break + # 如果找到了目标节点,则进行双击检测 + if target_node: + print(f"找到可选择节点: {target_node.getName()}") + print(f"节点位置: {target_node.getPos()}") + print(f"节点边界: {target_node.getBounds()}") + # 检查是否为双击 + if self.checkDoubleClick(target_node): + print(f"双击节点: {target_node.getName()}") + self.focusCameraOnNode(target_node) + else: + print(f"单击节点: {target_node.getName()}") + else: + print("未找到有效的目标节点") + # 清理碰撞器 ray_np.removeNode()