EG/core/vr/interaction/teleport.py
2025-10-11 16:41:59 +08:00

422 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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}")