1
0
forked from Rowland/EG

打包后场景聚焦功能,动画播放

This commit is contained in:
Hector 2025-10-09 09:24:12 +08:00
parent 79b2547446
commit f4d21a62f9
3 changed files with 356 additions and 138 deletions

View File

@ -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])

View File

@ -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)

View File

@ -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<self._double_click_threshold)
current_time - self._last_click_time < self._double_click_threshold)
if is_double_click:
# 双击成功,重置状态
self._last_click_time = 0
self._last_clicked_node = None
print(f"✓ 检测到双击: {nodePath.getName()}")
return True
else:
# 单击,更新状态
self._last_click_time = current_time
self._last_clicked_node = nodePath
return False
except Exception as e:
print(f"双击检测失败{e}")
print(f"双击检测失败: {e}")
return False
def focusCameraOnNode(self,nodePath):
def focusCameraOnNode(self, nodePath):
"""带动画效果的聚焦到节点方法"""
try:
if not nodePath or nodePath.isEmpty():
print("无效的节点")
return
bounds = nodePath.getBounds()
if bounds.isEmpty():
center = nodePath.getPos()
minPoint = Point3()
maxPoint = Point3()
if not nodePath.calcTightBounds(minPoint, maxPoint,self.render):
print("无法计算选中节点的边界框,使用节点为位置作为替代方案")
node_pos = nodePath.getPos(self.render)
optimal_distance = 10.0
current_cam_pos = self.cam.getPos()
view_direction = node_pos - current_cam_pos
if view_direction.length()<0.001:
view_direction = Vec3(5,-5,2)
view_direction.normalize()
target_cam_pos = node_pos-(view_direction * optimal_distance)
temp_node = self.render.attachNewNode("temp_lookat_target")
temp_node.setPos(node_pos)
dummy_cam = self.render.attachNewNode("dummy_camera")
dummy_cam.setPos(target_cam_pos)
dummy_cam.lookAt(temp_node)
target_cam_hpr = Vec3(dummy_cam.getHpr())
temp_node.removeNode()
dummy_cam.removeNode()
current_cam_pos = Point3(self.cam.getPos())
current_cam_hpr = Vec3(self.cam.getHpr())
self._startCameraFocusAnimation(current_cam_pos,target_cam_pos,current_cam_hpr,target_cam_hpr)
print(f"开始聚焦到节点(使用位置): {nodePath.getName()}")
return
center = Point3(
(minPoint.x + maxPoint.x)*0.5,
(minPoint.y+maxPoint.y)*0.5,
(minPoint.z+maxPoint.z)*0.5
)
size = (maxPoint - minPoint).length()
if size < 0.01:
size = 1.0
else:
center = bounds.getCenter()
size = bounds.getRadius()
current_cam_pos = self.cam.getPos()
current_cam_hpr = self.cam.getHpr()
current_cam_pos = Point3(self.cam.getPos())
current_cam_hpr = Vec3(self.cam.getHpr())
view_direction = current_cam_pos - center
if view_direction.length()<0.001:
if view_direction.length() < 0.001:
view_direction = Vec3(5,-5,2)
view_direction.normalize()
optimal_distance = max(size*2,3.0)
optimal_distance = max(size * 2.5,1.0)
target_cam_pos = center + (view_direction * optimal_distance)
@ -871,118 +1057,118 @@ class MainApp(ShowBase):
temp_node.removeNode()
dummy_cam.removeNode()
self._startCameraFocusAnimation(current_cam_pos,target_cam_pos,current_cam_hpr,target_cam_hpr)
self._startCameraFocusAnimation(current_cam_pos,target_cam_pos,
current_cam_hpr,target_cam_hpr)
print(f"开始聚焦到节点: {nodePath.getName()}")
return True
except Exception as e:
print(f"聚焦到节点失败{e}")
print(f"聚焦相机失败: {str(e)}")
import traceback
traceback.print_exc()
def _startCameraFocusAnimation(self,start_pos,end_pos,start_hpr,end_hpr):
def _startCameraFocusAnimation(self, start_pos, end_pos, start_hpr, end_hpr):
"""启动相机聚焦动画 - 改进版本"""
try:
class CameraFocusData:
def __init__(self,start_pos,end_pos,start_hpr,end_hpr):
self.start_pos = start_pos
self.end_pos = end_pos
self.start_hpr = start_hpr
self.end_hpr = end_hpr
self.elapsed_time = 0
self.duration = 0.5
def __init__(self, start_pos, end_pos, start_hpr, end_hpr):
self.start_pos = Point3(start_pos)
self.end_pos = Point3(end_pos)
self.start_hpr = Vec3(start_hpr)
self.end_hpr = Vec3(end_hpr)
self.elapsed_time = 0.0
self.duration = 0.5 # 增加动画时间使移动更平滑
self._camera_focus_data = CameraFocusData(start_pos,end_pos,start_hpr,end_hpr)
self._camera_focus_data = CameraFocusData(start_pos, end_pos, start_hpr, end_hpr)
from direct.task.TaskManagerGlobal import taskMgr
taskMgr.remove("cameraFocusTask")
taskMgr.add(self._cameraFocusTask, "cameraFocusTask")
except Exception as e:
print(f"启动摄像机聚焦动画失败{e}")
print(f"开始相机聚焦动画: {start_pos} -> {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()