422 lines
15 KiB
Python
422 lines
15 KiB
Python
"""
|
||
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}") |