""" VR传送系统模块 提供VR传送功能: - 抛物线轨迹计算和可视化 - 传送点有效性检测 - 传送执行 - 可视化反馈(抛物线、落点标记) """ import math from panda3d.core import ( Vec3, Vec4, Mat4, Point3, CollisionRay, CollisionTraverser, CollisionNode, CollisionHandlerQueue, BitMask32, NodePath, CollisionSphere, CollisionPlane, LineSegs, GeomNode, Material, RenderState, TransparencyAttrib, ColorAttrib ) from direct.showbase.DirectObject import DirectObject class VRTeleportSystem(DirectObject): """VR传送系统 - 处理传送功能和可视化""" def __init__(self, vr_manager): """初始化VR传送系统 Args: vr_manager: VR管理器实例 """ super().__init__() self.vr_manager = vr_manager self.world = vr_manager.world if hasattr(vr_manager, 'world') else None # 传送参数 self.teleport_range = 20.0 # 最大传送距离 self.arc_resolution = 50 # 抛物线精度 self.gravity = -9.8 # 重力系数 self.initial_velocity = 10.0 # 初始速度 self.min_teleport_distance = 1.0 # 最小传送距离 self.player_height_offset = 1.7 # 玩家站立高度偏移(米) # 可视化元素 self.teleport_arc_node = None # 抛物线节点 self.teleport_target_node = None # 落点标记节点 self.teleport_invalid_node = None # 无效位置标记 # 传送状态 self.is_teleport_active = False # 是否正在预览传送 self.teleport_target_pos = None # 传送目标位置 self.teleport_valid = False # 传送位置是否有效 self.active_controller = None # 正在使用传送的控制器 # 碰撞检测 self.teleport_collision_traverser = CollisionTraverser() self.teleport_collision_queue = CollisionHandlerQueue() # 可视化颜色 self.valid_arc_color = Vec4(0.2, 0.9, 0.2, 0.8) # 绿色抛物线 self.invalid_arc_color = Vec4(0.9, 0.2, 0.2, 0.8) # 红色抛物线 self.target_color = Vec4(0.2, 0.7, 1.0, 0.9) # 蓝色落点 print("✓ VR传送系统初始化完成") def initialize(self): """初始化传送系统""" try: print("🔧 正在初始化VR传送系统...") # 创建可视化元素 self._create_teleport_visuals() # 设置地面检测 self._setup_ground_detection() print("✅ VR传送系统初始化成功") return True except Exception as e: print(f"❌ VR传送系统初始化失败: {e}") import traceback traceback.print_exc() return False def _create_teleport_visuals(self): """创建传送可视化元素""" if not self.world or not hasattr(self.world, 'render'): print("⚠️ 无法创建传送可视化 - 缺少世界渲染节点") return # 创建抛物线节点 self.teleport_arc_node = self.world.render.attachNewNode("teleport_arc") self.teleport_arc_node.hide() # 初始隐藏 # 创建落点标记 self._create_target_marker() # 创建无效位置标记 self._create_invalid_marker() print("✓ 传送可视化元素已创建") def _create_target_marker(self): """创建传送目标标记""" try: # 创建一个圆形平台作为落点标记 from panda3d.core import CardMaker cm = CardMaker("teleport_target") cm.setFrame(-1, 1, -1, 1) # 2x2的平面 self.teleport_target_node = self.world.render.attachNewNode(cm.generate()) self.teleport_target_node.setScale(1.0) # 2米直径的圆形 self.teleport_target_node.setP(-90) # 平放在地面 self.teleport_target_node.setColor(self.target_color) self.teleport_target_node.setTransparency(TransparencyAttrib.MAlpha) self.teleport_target_node.hide() # 添加PBR材质以兼容RenderPipeline material = Material() material.setBaseColor(self.target_color) material.setRoughness(0.8) material.setMetallic(0.0) material.setRefractiveIndex(1.5) self.teleport_target_node.setMaterial(material, 1) except Exception as e: print(f"⚠️ 创建传送目标标记失败: {e}") def _create_invalid_marker(self): """创建无效位置标记""" try: from panda3d.core import CardMaker cm = CardMaker("teleport_invalid") cm.setFrame(-0.8, 0.8, -0.8, 0.8) # 稍小的红色叉号 self.teleport_invalid_node = self.world.render.attachNewNode(cm.generate()) self.teleport_invalid_node.setScale(1.0) self.teleport_invalid_node.setP(-90) self.teleport_invalid_node.setColor(self.invalid_arc_color) self.teleport_invalid_node.setTransparency(TransparencyAttrib.MAlpha) self.teleport_invalid_node.hide() # 添加PBR材质 material = Material() material.setBaseColor(self.invalid_arc_color) material.setRoughness(0.8) material.setMetallic(0.0) material.setRefractiveIndex(1.5) self.teleport_invalid_node.setMaterial(material, 1) except Exception as e: print(f"⚠️ 创建无效位置标记失败: {e}") def _setup_ground_detection(self): """设置地面检测""" try: # 假设地面在Z=0平面,使用正确的CollisionPlane API from panda3d.core import Plane ground_plane = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0))) ground_node = CollisionNode("ground_detection") ground_node.addSolid(ground_plane) ground_node.setIntoCollideMask(BitMask32.bit(1)) # 地面碰撞掩码 # 附加到渲染节点 if self.world and hasattr(self.world, 'render'): ground_node_path = self.world.render.attachNewNode(ground_node) print("✓ 地面检测设置成功") except Exception as e: print(f"⚠️ 设置地面检测失败: {e}") # 继续运行,不让这个错误阻止传送系统 def start_teleport_preview(self, controller, direction): """开始传送预览 Args: controller: VR控制器实例 direction: 传送方向向量(摇杆输入转换的世界方向) """ if not controller or not controller.anchor_node: return False self.is_teleport_active = True self.active_controller = controller # 计算抛物线轨迹 start_pos = controller.get_world_position() self._calculate_teleport_trajectory(start_pos, direction) # 显示可视化 self._show_teleport_visuals() return True def _calculate_teleport_trajectory(self, start_pos, direction): """计算传送抛物线轨迹 Args: start_pos: 起始位置 direction: 方向向量 """ try: # 确保方向向量已标准化 direction = direction.normalized() # 计算抛物线点 trajectory_points = [] hit_ground = False self.teleport_valid = False # 初始速度向量 velocity = direction * self.initial_velocity velocity.z += 2.0 # 给一些向上的初始速度 dt = 0.05 # 时间步长 current_pos = Vec3(start_pos) current_velocity = Vec3(velocity) for i in range(self.arc_resolution): trajectory_points.append(Point3(current_pos)) # 更新位置和速度 current_pos += current_velocity * dt current_velocity.z += self.gravity * dt # 应用重力 # 检查是否碰撞地面 if current_pos.z <= 0.1: # 假设地面在z=0 # 精确计算落地点 ground_pos = self._calculate_ground_intersection( trajectory_points[-2] if len(trajectory_points) > 1 else start_pos, current_pos ) trajectory_points.append(ground_pos) # 检查传送有效性 distance = (ground_pos - start_pos).length() if (distance >= self.min_teleport_distance and distance <= self.teleport_range): self.teleport_target_pos = ground_pos self.teleport_valid = True else: self.teleport_target_pos = ground_pos self.teleport_valid = False hit_ground = True break # 超出范围 if (current_pos - start_pos).length() > self.teleport_range: break # 如果没有碰撞地面,传送无效 if not hit_ground: if trajectory_points: self.teleport_target_pos = trajectory_points[-1] self.teleport_valid = False # 创建抛物线几何体 self._create_arc_geometry(trajectory_points) except Exception as e: print(f"⚠️ 计算传送轨迹失败: {e}") self.teleport_valid = False def _calculate_ground_intersection(self, p1, p2): """计算与地面的精确交点""" if p1.z == p2.z: return Point3(p2) # 线性插值找到z=0的点 t = -p1.z / (p2.z - p1.z) t = max(0, min(1, t)) # 限制在0-1范围内 intersection = p1 + (p2 - p1) * t intersection.z = 0.1 # 稍微高于地面 return Point3(intersection) def _create_arc_geometry(self, points): """创建抛物线几何体""" if not points or len(points) < 2: return try: # 创建线段 line_segs = LineSegs() line_segs.setThickness(3) # 根据有效性设置颜色 color = self.valid_arc_color if self.teleport_valid else self.invalid_arc_color line_segs.setColor(color) # 添加线段点 line_segs.moveTo(points[0]) for point in points[1:]: line_segs.drawTo(point) # 清除旧的几何体 self.teleport_arc_node.removeNode() self.teleport_arc_node = self.world.render.attachNewNode("teleport_arc") # 创建新的几何体 geom_node = line_segs.create() arc_node_path = self.teleport_arc_node.attachNewNode(geom_node) # 设置材质 material = Material() material.setBaseColor(color) material.setRoughness(0.1) material.setMetallic(0.0) material.setRefractiveIndex(1.5) arc_node_path.setMaterial(material, 1) # 设置透明度 arc_node_path.setTransparency(TransparencyAttrib.MAlpha) except Exception as e: print(f"⚠️ 创建抛物线几何体失败: {e}") def _show_teleport_visuals(self): """显示传送可视化""" if self.teleport_arc_node: self.teleport_arc_node.show() if self.teleport_target_pos: if self.teleport_valid and self.teleport_target_node: # 显示有效的传送目标 self.teleport_target_node.setPos(self.teleport_target_pos) self.teleport_target_node.show() self.teleport_invalid_node.hide() elif self.teleport_invalid_node: # 显示无效的传送位置 self.teleport_invalid_node.setPos(self.teleport_target_pos) self.teleport_invalid_node.show() self.teleport_target_node.hide() def stop_teleport_preview(self): """停止传送预览""" self.is_teleport_active = False self.active_controller = None # 隐藏可视化 if self.teleport_arc_node: self.teleport_arc_node.hide() if self.teleport_target_node: self.teleport_target_node.hide() if self.teleport_invalid_node: self.teleport_invalid_node.hide() def execute_teleport(self): """执行传送""" if not self.teleport_valid or not self.teleport_target_pos: print("⚠️ 传送位置无效,无法执行传送") return False try: # 计算传送偏移 if self.vr_manager.tracking_space: current_pos = self.vr_manager.tracking_space.getPos() # 计算水平偏移(只考虑XY平面) controller_pos = self.active_controller.get_world_position() horizontal_offset = Vec3( self.teleport_target_pos.x - controller_pos.x, self.teleport_target_pos.y - controller_pos.y, 0 ) # 计算新位置:保持水平偏移,设置固定站立高度 new_pos = current_pos + horizontal_offset new_pos.z = self.teleport_target_pos.z + self.player_height_offset # 执行传送 self.vr_manager.tracking_space.setPos(new_pos) print(f"✅ 传送成功: {current_pos} → {new_pos} (高度偏移: {self.player_height_offset}m)") # 停止预览 self.stop_teleport_preview() return True else: print("⚠️ 无法获取VR跟踪空间,传送失败") return False except Exception as e: print(f"❌ 执行传送失败: {e}") return False def update_teleport_preview(self, controller, direction): """更新传送预览(摇杆移动时调用)""" if self.is_teleport_active and controller == self.active_controller: start_pos = controller.get_world_position() self._calculate_teleport_trajectory(start_pos, direction) self._show_teleport_visuals() def cleanup(self): """清理传送系统资源""" try: self.stop_teleport_preview() if self.teleport_arc_node: self.teleport_arc_node.removeNode() self.teleport_arc_node = None if self.teleport_target_node: self.teleport_target_node.removeNode() self.teleport_target_node = None if self.teleport_invalid_node: self.teleport_invalid_node.removeNode() self.teleport_invalid_node = None self.ignoreAll() print("🧹 VR传送系统已清理") except Exception as e: print(f"⚠️ 清理传送系统失败: {e}")