forked from Rowland/EG
1279 lines
48 KiB
Python
1279 lines
48 KiB
Python
"""
|
||
VR管理器模块
|
||
|
||
负责VR功能的初始化、渲染和交互:
|
||
- OpenVR/SteamVR集成
|
||
- VR头显跟踪和渲染
|
||
- VR控制器交互
|
||
- VR模式切换
|
||
"""
|
||
|
||
import sys
|
||
import numpy as np
|
||
from panda3d.core import (
|
||
WindowProperties, GraphicsPipe, FrameBufferProperties,
|
||
GraphicsOutput, Texture, Camera, PerspectiveLens, MatrixLens,
|
||
Mat4, Vec3, TransformState, RenderState, CardMaker,
|
||
BitMask32, PandaNode, NodePath, LMatrix4, LVector3, LVector4,
|
||
CS_yup_right, CS_default, PythonCallbackObject
|
||
)
|
||
from direct.task import Task
|
||
from direct.showbase.DirectObject import DirectObject
|
||
|
||
try:
|
||
import openvr
|
||
OPENVR_AVAILABLE = True
|
||
except ImportError:
|
||
OPENVR_AVAILABLE = False
|
||
print("警告: OpenVR未安装,VR功能将不可用")
|
||
|
||
# 导入手柄控制器、动作系统和交互系统
|
||
from .vr_controller import LeftController, RightController
|
||
from .vr_actions import VRActionManager
|
||
from .vr_interaction import VRInteractionManager
|
||
|
||
|
||
class VRManager(DirectObject):
|
||
"""VR管理器类 - 处理所有VR相关功能"""
|
||
|
||
def __init__(self, world):
|
||
"""初始化VR管理器
|
||
|
||
Args:
|
||
world: 主世界对象引用
|
||
"""
|
||
super().__init__()
|
||
|
||
self.world = world
|
||
self.vr_system = None
|
||
self.vr_enabled = False
|
||
self.vr_initialized = False
|
||
|
||
# VR渲染相关
|
||
self.vr_left_eye_buffer = None
|
||
self.vr_right_eye_buffer = None
|
||
self.vr_left_camera = None
|
||
self.vr_right_camera = None
|
||
self.vr_compositor = None
|
||
|
||
# VR跟踪数据
|
||
self.hmd_pose = Mat4.identMat()
|
||
self.controller_poses = {}
|
||
self.tracked_device_poses = []
|
||
self.poses = None # OpenVR姿态数组
|
||
|
||
# VR渲染参数
|
||
self.eye_width = 1080
|
||
self.eye_height = 1200
|
||
self.near_clip = 0.1
|
||
self.far_clip = 1000.0
|
||
|
||
# VR任务
|
||
self.vr_task = None
|
||
|
||
# VR锚点层级系统
|
||
self.tracking_space = None
|
||
self.hmd_anchor = None
|
||
self.left_eye_anchor = None
|
||
self.right_eye_anchor = None
|
||
|
||
# 坐标系转换矩阵 - 使用Panda3D内置方法
|
||
self.coord_mat = LMatrix4.convert_mat(CS_yup_right, CS_default)
|
||
self.coord_mat_inv = LMatrix4.convert_mat(CS_default, CS_yup_right)
|
||
|
||
# 性能监控
|
||
self.frame_count = 0
|
||
self.last_fps_check = 0
|
||
self.last_fps_time = 0
|
||
self.vr_fps = 0
|
||
self.submit_failures = 0
|
||
self.pose_failures = 0
|
||
|
||
# VR提交策略 - 基于参考实现
|
||
self.submit_together = True # 是否在right_cb中同时提交左右眼
|
||
|
||
# VR手柄控制器
|
||
self.left_controller = None
|
||
self.right_controller = None
|
||
self.controllers = {} # 设备索引到控制器的映射
|
||
self.tracked_device_anchors = {} # 跟踪设备锚点
|
||
|
||
# VR动作系统
|
||
self.action_manager = VRActionManager(self)
|
||
|
||
# VR交互系统
|
||
self.interaction_manager = VRInteractionManager(self)
|
||
|
||
print("✓ VR管理器初始化完成")
|
||
|
||
def convert_mat(self, mat):
|
||
"""
|
||
将OpenVR矩阵转换为Panda3D矩阵 - 基于参考实现
|
||
"""
|
||
if len(mat.m) == 4:
|
||
result = LMatrix4(
|
||
mat.m[0][0], mat.m[1][0], mat.m[2][0], mat.m[3][0],
|
||
mat.m[0][1], mat.m[1][1], mat.m[2][1], mat.m[3][1],
|
||
mat.m[0][2], mat.m[1][2], mat.m[2][2], mat.m[3][2],
|
||
mat.m[0][3], mat.m[1][3], mat.m[2][3], mat.m[3][3])
|
||
elif len(mat.m) == 3:
|
||
result = LMatrix4(
|
||
mat.m[0][0], mat.m[1][0], mat.m[2][0], 0.0,
|
||
mat.m[0][1], mat.m[1][1], mat.m[2][1], 0.0,
|
||
mat.m[0][2], mat.m[1][2], mat.m[2][2], 0.0,
|
||
mat.m[0][3], mat.m[1][3], mat.m[2][3], 1.0)
|
||
return result
|
||
|
||
def is_vr_available(self):
|
||
"""检查VR系统是否可用"""
|
||
if not OPENVR_AVAILABLE:
|
||
return False
|
||
|
||
try:
|
||
# 检查SteamVR是否运行
|
||
return openvr.isRuntimeInstalled() and openvr.isHmdPresent()
|
||
except Exception as e:
|
||
print(f"VR检查失败: {e}")
|
||
return False
|
||
|
||
def initialize_vr(self):
|
||
"""初始化VR系统"""
|
||
if not OPENVR_AVAILABLE:
|
||
print("❌ OpenVR不可用,无法初始化VR")
|
||
return False
|
||
|
||
if self.vr_initialized:
|
||
print("VR系统已经初始化")
|
||
return True
|
||
|
||
try:
|
||
print("🔄 正在初始化VR系统...")
|
||
|
||
# 初始化OpenVR - 使用Scene应用类型确保正确的焦点管理
|
||
self.vr_system = openvr.init(openvr.VRApplication_Scene)
|
||
if not self.vr_system:
|
||
print("❌ 无法初始化OpenVR系统")
|
||
return False
|
||
|
||
# 获取compositor
|
||
self.vr_compositor = openvr.VRCompositor()
|
||
if not self.vr_compositor:
|
||
print("❌ 无法获取VR Compositor")
|
||
return False
|
||
|
||
# 获取推荐的渲染目标尺寸
|
||
self.eye_width, self.eye_height = self.vr_system.getRecommendedRenderTargetSize()
|
||
print(f"✓ VR渲染目标尺寸: {self.eye_width}x{self.eye_height}")
|
||
|
||
# 创建OpenVR姿态数组
|
||
poses_t = openvr.TrackedDevicePose_t * openvr.k_unMaxTrackedDeviceCount
|
||
self.poses = poses_t()
|
||
print("✓ VR姿态数组已创建")
|
||
|
||
# 创建VR渲染缓冲区
|
||
if not self._create_vr_buffers():
|
||
print("❌ 创建VR渲染缓冲区失败")
|
||
return False
|
||
|
||
# 设置VR相机
|
||
if not self._setup_vr_cameras():
|
||
print("❌ 设置VR相机失败")
|
||
return False
|
||
|
||
# 初始化手柄控制器
|
||
self._initialize_controllers()
|
||
|
||
# 初始化动作系统
|
||
if not self.action_manager.initialize():
|
||
print("⚠️ VR动作系统初始化失败,但VR系统将继续运行")
|
||
|
||
# 初始化交互系统
|
||
if not self.interaction_manager.initialize():
|
||
print("⚠️ VR交互系统初始化失败,但VR系统将继续运行")
|
||
|
||
# 启动VR更新任务
|
||
self._start_vr_task()
|
||
|
||
self.vr_initialized = True
|
||
print("✅ VR系统初始化成功")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"❌ VR初始化失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def _create_vr_buffers(self):
|
||
"""创建VR渲染缓冲区 - 基于参考实现"""
|
||
try:
|
||
# 创建左眼纹理和缓冲区
|
||
self.vr_left_texture = self._create_vr_texture("VR Left Eye Texture")
|
||
self.vr_left_eye_buffer = self._create_vr_buffer(
|
||
"VR Left Eye",
|
||
self.vr_left_texture,
|
||
self.eye_width,
|
||
self.eye_height
|
||
)
|
||
|
||
if not self.vr_left_eye_buffer:
|
||
print("❌ 创建左眼缓冲区失败")
|
||
return False
|
||
|
||
# 设置左眼缓冲区属性
|
||
self.vr_left_eye_buffer.setSort(-100)
|
||
self.vr_left_eye_buffer.setClearColor((0.1, 0.2, 0.4, 1)) # 深蓝色背景便于调试
|
||
self.vr_left_eye_buffer.setActive(True)
|
||
|
||
# 创建右眼纹理和缓冲区
|
||
self.vr_right_texture = self._create_vr_texture("VR Right Eye Texture")
|
||
self.vr_right_eye_buffer = self._create_vr_buffer(
|
||
"VR Right Eye",
|
||
self.vr_right_texture,
|
||
self.eye_width,
|
||
self.eye_height
|
||
)
|
||
|
||
if not self.vr_right_eye_buffer:
|
||
print("❌ 创建右眼缓冲区失败")
|
||
return False
|
||
|
||
# 设置右眼缓冲区属性
|
||
self.vr_right_eye_buffer.setSort(-99)
|
||
self.vr_right_eye_buffer.setClearColor((0.1, 0.2, 0.4, 1)) # 深蓝色背景便于调试
|
||
self.vr_right_eye_buffer.setActive(True)
|
||
|
||
print("✓ VR渲染缓冲区创建成功")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"❌ 创建VR缓冲区失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def _create_vr_texture(self, name):
|
||
"""创建VR纹理对象 - 基于参考实现"""
|
||
texture = Texture(name)
|
||
texture.setWrapU(Texture.WMClamp)
|
||
texture.setWrapV(Texture.WMClamp)
|
||
texture.setMinfilter(Texture.FTLinear)
|
||
texture.setMagfilter(Texture.FTLinear)
|
||
return texture
|
||
|
||
def _create_vr_buffer(self, name, texture, width, height):
|
||
"""创建VR渲染缓冲区 - 基于参考实现"""
|
||
# 设置帧缓冲属性
|
||
fbprops = FrameBufferProperties()
|
||
fbprops.setRgbaBits(1, 1, 1, 1)
|
||
# 可以在这里添加多重采样抗锯齿
|
||
# fbprops.setMultisamples(4)
|
||
|
||
# 创建缓冲区
|
||
buffer = self.world.win.makeTextureBuffer(name, width, height, to_ram=False, fbp=fbprops)
|
||
|
||
if buffer:
|
||
# 清除默认渲染纹理
|
||
buffer.clearRenderTextures()
|
||
# 添加我们的纹理
|
||
buffer.addRenderTexture(texture, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
|
||
|
||
return buffer
|
||
|
||
def _setup_vr_cameras(self):
|
||
"""设置VR相机 - 使用锚点层级系统"""
|
||
try:
|
||
# 创建VR追踪空间锚点层级
|
||
self.tracking_space = self.world.render.attachNewNode('tracking-space')
|
||
self.hmd_anchor = self.tracking_space.attachNewNode('hmd-anchor')
|
||
self.left_eye_anchor = self.hmd_anchor.attachNewNode('left-eye')
|
||
self.right_eye_anchor = self.hmd_anchor.attachNewNode('right-eye')
|
||
|
||
# 获取投影矩阵
|
||
projection_left = self.coord_mat_inv * self.convert_mat(
|
||
self.vr_system.getProjectionMatrix(openvr.Eye_Left, self.near_clip, self.far_clip))
|
||
projection_right = self.coord_mat_inv * self.convert_mat(
|
||
self.vr_system.getProjectionMatrix(openvr.Eye_Right, self.near_clip, self.far_clip))
|
||
|
||
# 创建左眼相机节点
|
||
left_cam_node = Camera('left-cam')
|
||
left_lens = MatrixLens()
|
||
left_lens.setUserMat(projection_left)
|
||
left_cam_node.setLens(left_lens)
|
||
|
||
# 创建右眼相机节点
|
||
right_cam_node = Camera('right-cam')
|
||
right_lens = MatrixLens()
|
||
right_lens.setUserMat(projection_right)
|
||
right_cam_node.setLens(right_lens)
|
||
|
||
# 附加相机到眼睛锚点
|
||
self.vr_left_camera = self.left_eye_anchor.attachNewNode(left_cam_node)
|
||
self.vr_right_camera = self.right_eye_anchor.attachNewNode(right_cam_node)
|
||
|
||
# 设置显示区域并添加渲染回调
|
||
left_dr = self.vr_left_eye_buffer.makeDisplayRegion()
|
||
left_dr.setCamera(self.vr_left_camera)
|
||
left_dr.setActive(True)
|
||
left_dr.setDrawCallback(PythonCallbackObject(self.left_cb))
|
||
|
||
right_dr = self.vr_right_eye_buffer.makeDisplayRegion()
|
||
right_dr.setCamera(self.vr_right_camera)
|
||
right_dr.setActive(True)
|
||
right_dr.setDrawCallback(PythonCallbackObject(self.right_cb))
|
||
|
||
print("✓ VR相机锚点层级系统设置完成")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"❌ 设置VR相机失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def _get_eye_offset(self, eye):
|
||
"""获取眼睛相对于头显的偏移"""
|
||
try:
|
||
if not self.vr_system:
|
||
# 使用标准IPD(瞳距)估算值
|
||
ipd = 0.064 # 64mm,平均IPD
|
||
if eye == openvr.Eye_Left:
|
||
return Vec3(-ipd/2, 0, 0)
|
||
else:
|
||
return Vec3(ipd/2, 0, 0)
|
||
|
||
# 从OpenVR获取眼睛到头显的变换矩阵
|
||
eye_transform = self.vr_system.getEyeToHeadTransform(eye)
|
||
|
||
# 提取位移信息
|
||
x = eye_transform[0][3]
|
||
y = eye_transform[1][3]
|
||
z = eye_transform[2][3]
|
||
|
||
return Vec3(x, y, z)
|
||
|
||
except Exception as e:
|
||
print(f"❌ 获取眼睛偏移失败: {e}")
|
||
# 返回默认值
|
||
ipd = 0.064
|
||
if eye == openvr.Eye_Left:
|
||
return Vec3(-ipd/2, 0, 0)
|
||
else:
|
||
return Vec3(ipd/2, 0, 0)
|
||
|
||
def _start_vr_task(self):
|
||
"""启动VR更新任务"""
|
||
if self.vr_task:
|
||
self.world.taskMgr.remove(self.vr_task)
|
||
|
||
self.vr_task = self.world.taskMgr.add(self._update_vr, "update_vr")
|
||
print("✓ VR更新任务已启动")
|
||
|
||
def _update_vr(self, task):
|
||
"""VR更新任务 - 每帧调用"""
|
||
if not self.vr_enabled or not self.vr_system:
|
||
return task.cont
|
||
|
||
try:
|
||
# 性能监控
|
||
self.frame_count += 1
|
||
|
||
# 计算VR FPS
|
||
import time
|
||
current_time = time.time()
|
||
if self.last_fps_time == 0:
|
||
self.last_fps_time = current_time
|
||
elif current_time - self.last_fps_time >= 1.0: # 每秒更新一次FPS
|
||
self.vr_fps = (self.frame_count - self.last_fps_check) / (current_time - self.last_fps_time)
|
||
self.last_fps_check = self.frame_count
|
||
self.last_fps_time = current_time
|
||
|
||
# 优化的VR更新顺序:
|
||
# 1. 立即调用 waitGetPoses 获取最新的姿态数据
|
||
# 这确保我们使用最新数据而不是上一帧的数据
|
||
self._wait_get_poses()
|
||
|
||
# 2. 更新相机位置(使用刚获取的最新姿态数据)
|
||
self._update_camera_poses()
|
||
|
||
# 3. 更新手柄和其他跟踪设备
|
||
self.update_tracked_devices()
|
||
|
||
# 4. 更新VR动作状态
|
||
self.action_manager.update_actions()
|
||
|
||
# 5. 更新VR交互系统
|
||
self.interaction_manager.update()
|
||
|
||
# 注意:纹理提交现在通过渲染回调自动处理
|
||
|
||
# 定期输出性能报告
|
||
if self.frame_count % 1800 == 1: # 每30秒@60fps输出一次性能报告
|
||
self._print_performance_report()
|
||
|
||
except Exception as e:
|
||
print(f"VR更新错误: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
return task.cont
|
||
|
||
def _wait_get_poses(self):
|
||
"""调用VRCompositor的waitGetPoses来获取焦点和姿态数据"""
|
||
try:
|
||
if not self.vr_compositor or not self.poses:
|
||
return
|
||
|
||
# 调用waitGetPoses获取焦点和姿态数据
|
||
# 这个调用可能会阻塞直到下一个VR同步点
|
||
result = self.vr_compositor.waitGetPoses(self.poses, None)
|
||
|
||
# 检查姿态数据的有效性
|
||
valid_poses = 0
|
||
|
||
# 更新HMD姿态(设备0通常是头显)
|
||
if len(self.poses) > 0 and self.poses[0].bPoseIsValid:
|
||
valid_poses += 1
|
||
else:
|
||
# 如果HMD姿态无效,不要频繁输出错误信息
|
||
if not hasattr(self, '_hmd_invalid_warning_shown'):
|
||
print("⚠️ HMD姿态数据无效")
|
||
self._hmd_invalid_warning_shown = True
|
||
|
||
# 更新控制器姿态
|
||
self.controller_poses.clear()
|
||
for device_id in range(1, min(len(self.poses), openvr.k_unMaxTrackedDeviceCount)):
|
||
if self.poses[device_id].bPoseIsValid:
|
||
device_class = self.vr_system.getTrackedDeviceClass(device_id)
|
||
if device_class == openvr.TrackedDeviceClass_Controller:
|
||
controller_matrix = self.poses[device_id].mDeviceToAbsoluteTracking
|
||
self.controller_poses[device_id] = self._convert_openvr_matrix_to_panda(controller_matrix)
|
||
valid_poses += 1
|
||
|
||
# 性能监控 - 偶尔输出姿态状态
|
||
if self.frame_count % 600 == 1: # 每10秒输出一次@60fps
|
||
print(f"📊 VR姿态状态 - 有效姿态数: {valid_poses}, 总帧数: {self.frame_count}")
|
||
|
||
except Exception as e:
|
||
# 限制错误输出频率
|
||
if not hasattr(self, '_last_error_frame'):
|
||
self._last_error_frame = 0
|
||
|
||
if self.frame_count - self._last_error_frame > 300: # 每5秒最多输出一次错误
|
||
print(f"waitGetPoses失败: {e}")
|
||
self._last_error_frame = self.frame_count
|
||
|
||
# 记录姿态失败次数
|
||
self.pose_failures += 1
|
||
|
||
def _update_tracking_data(self):
|
||
"""更新VR追踪数据"""
|
||
try:
|
||
# 获取设备姿态
|
||
poses = self.vr_system.getDeviceToAbsoluteTrackingPose(
|
||
openvr.TrackingUniverseStanding, 0.0, openvr.k_unMaxTrackedDeviceCount
|
||
)
|
||
|
||
# 更新HMD姿态(设备0通常是头显)
|
||
if poses[0].bPoseIsValid:
|
||
hmd_matrix = poses[0].mDeviceToAbsoluteTracking
|
||
self.hmd_pose = self._convert_openvr_matrix_to_panda(hmd_matrix)
|
||
|
||
# 更新控制器姿态
|
||
for device_id in range(1, openvr.k_unMaxTrackedDeviceCount):
|
||
if poses[device_id].bPoseIsValid:
|
||
device_class = self.vr_system.getTrackedDeviceClass(device_id)
|
||
if device_class == openvr.TrackedDeviceClass_Controller:
|
||
controller_matrix = poses[device_id].mDeviceToAbsoluteTracking
|
||
self.controller_poses[device_id] = self._convert_openvr_matrix_to_panda(controller_matrix)
|
||
|
||
except Exception as e:
|
||
print(f"更新追踪数据失败: {e}")
|
||
|
||
def _convert_openvr_matrix_to_panda(self, ovr_matrix):
|
||
"""将OpenVR矩阵转换为Panda3D矩阵
|
||
|
||
坐标系转换:
|
||
OpenVR: X右, Y上, -Z前(右手坐标系)
|
||
Panda3D: X右, Y前, Z上(右手坐标系)
|
||
|
||
转换规则:
|
||
OpenVR X → Panda3D X
|
||
OpenVR Y → Panda3D Z
|
||
OpenVR -Z → Panda3D Y
|
||
"""
|
||
mat = Mat4()
|
||
|
||
# 修正的坐标转换矩阵
|
||
# OpenVR: X右, Y上, -Z前 → Panda3D: X右, Y前, Z上
|
||
# 转换规则: (ovr_x, ovr_y, ovr_z) → (panda_x, panda_y, panda_z)
|
||
# (ovr_x, ovr_y, ovr_z) → (ovr_x, -ovr_z, ovr_y)
|
||
|
||
# X轴行:Panda3D的X轴对应OpenVR的X轴
|
||
mat.setCell(0, 0, ovr_matrix[0][0]) # X_x → X_x
|
||
mat.setCell(0, 1, ovr_matrix[0][1]) # X_y → X_y
|
||
mat.setCell(0, 2, ovr_matrix[0][2]) # X_z → X_z
|
||
mat.setCell(0, 3, ovr_matrix[0][3]) # 位移X分量
|
||
|
||
# Y轴行:Panda3D的Y轴对应OpenVR的-Z轴
|
||
mat.setCell(1, 0, -ovr_matrix[2][0]) # -Z_x → Y_x
|
||
mat.setCell(1, 1, -ovr_matrix[2][1]) # -Z_y → Y_y
|
||
mat.setCell(1, 2, -ovr_matrix[2][2]) # -Z_z → Y_z
|
||
mat.setCell(1, 3, -ovr_matrix[2][3]) # 位移Y分量(-Z位移)
|
||
|
||
# Z轴行:Panda3D的Z轴对应OpenVR的Y轴
|
||
mat.setCell(2, 0, ovr_matrix[1][0]) # Y_x → Z_x
|
||
mat.setCell(2, 1, ovr_matrix[1][1]) # Y_y → Z_y
|
||
mat.setCell(2, 2, ovr_matrix[1][2]) # Y_z → Z_z
|
||
mat.setCell(2, 3, ovr_matrix[1][3]) # 位移Z分量(Y位移)
|
||
|
||
# 齐次坐标
|
||
mat.setCell(3, 0, 0)
|
||
mat.setCell(3, 1, 0)
|
||
mat.setCell(3, 2, 0)
|
||
mat.setCell(3, 3, 1)
|
||
|
||
# 调试信息 - 验证坐标系转换
|
||
if not hasattr(self, '_coord_debug_counter'):
|
||
self._coord_debug_counter = 0
|
||
self._coord_debug_counter += 1
|
||
|
||
if self._coord_debug_counter % 600 == 1: # 每10秒输出一次@60fps
|
||
print(f"🔄 坐标系转换调试 (第{self._coord_debug_counter}帧)")
|
||
|
||
# 输出原始OpenVR矩阵信息
|
||
ovr_pos = Vec3(ovr_matrix[0][3], ovr_matrix[1][3], ovr_matrix[2][3])
|
||
print(f" OpenVR原始位置: {ovr_pos}")
|
||
|
||
# 输出转换后的Panda3D矩阵信息
|
||
# 正确的方法:从矩阵中读取位移(第4列,前3行)
|
||
panda_pos = Vec3(mat.getCell(0, 3), mat.getCell(1, 3), mat.getCell(2, 3))
|
||
print(f" Panda3D转换位置: {panda_pos}")
|
||
|
||
# 检查矩阵设置是否正确
|
||
# 手动验证位置转换:OpenVR (x,y,z) → Panda3D (x,-z,y)
|
||
manual_converted_pos = Vec3(ovr_pos.x, -ovr_pos.z, ovr_pos.y)
|
||
print(f" 手动转换结果: {manual_converted_pos}")
|
||
|
||
# 检查我们的矩阵是否正确设置了位置
|
||
print(f" 矩阵位置元素: [{mat.getCell(0,3)}, {mat.getCell(1,3)}, {mat.getCell(2,3)}]")
|
||
|
||
# 验证转换是否正确
|
||
expected_panda_pos = manual_converted_pos
|
||
print(f" 预期Panda3D位置: {expected_panda_pos}")
|
||
|
||
# 检查转换是否正确
|
||
diff = panda_pos - expected_panda_pos
|
||
diff_magnitude = diff.length()
|
||
if diff_magnitude < 0.001:
|
||
print(f" ✅ 坐标转换正确 (误差: {diff_magnitude:.6f})")
|
||
else:
|
||
print(f" ⚠️ 坐标转换可能有误 (误差: {diff_magnitude:.6f})")
|
||
print(f" 差异向量: {diff}")
|
||
print(f" 实际矩阵第4行: [{mat.getCell(3,0)}, {mat.getCell(3,1)}, {mat.getCell(3,2)}, {mat.getCell(3,3)}]")
|
||
|
||
return mat
|
||
|
||
def update_hmd(self, pose):
|
||
"""
|
||
更新HMD锚点 - 基于参考实现
|
||
"""
|
||
try:
|
||
# 将OpenVR姿态转换为Panda3D矩阵
|
||
modelview = self.convert_mat(pose.mDeviceToAbsoluteTracking)
|
||
|
||
# 应用坐标系转换并设置HMD锚点
|
||
self.hmd_anchor.setMat(self.coord_mat_inv * modelview * self.coord_mat)
|
||
|
||
# 获取眼睛到头部的变换
|
||
view_left = self.convert_mat(self.vr_system.getEyeToHeadTransform(openvr.Eye_Left))
|
||
view_right = self.convert_mat(self.vr_system.getEyeToHeadTransform(openvr.Eye_Right))
|
||
|
||
# 设置眼睛锚点
|
||
self.left_eye_anchor.setMat(self.coord_mat_inv * view_left * self.coord_mat)
|
||
self.right_eye_anchor.setMat(self.coord_mat_inv * view_right * self.coord_mat)
|
||
|
||
except Exception as e:
|
||
print(f"更新HMD姿态失败: {e}")
|
||
|
||
def _update_camera_poses(self):
|
||
"""更新相机姿态 - 使用锚点系统简化处理"""
|
||
try:
|
||
# 使用锚点系统后,相机位置自动跟随锚点
|
||
# 只需要获取HMD姿态并更新锚点即可
|
||
|
||
# 从poses数组中获取HMD姿态
|
||
if hasattr(self, 'poses') and len(self.poses) > 0:
|
||
hmd_pose = self.poses[openvr.k_unTrackedDeviceIndex_Hmd]
|
||
if hmd_pose.bPoseIsValid:
|
||
self.update_hmd(hmd_pose)
|
||
else:
|
||
print("⚠️ HMD姿态数据无效")
|
||
|
||
except Exception as e:
|
||
print(f"更新相机姿态失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _submit_frames_to_vr(self):
|
||
"""将渲染帧提交给VR系统"""
|
||
try:
|
||
if not self.vr_compositor:
|
||
return
|
||
|
||
# 使用我们创建的纹理对象
|
||
left_texture = self.vr_left_texture
|
||
right_texture = self.vr_right_texture
|
||
|
||
if left_texture and right_texture:
|
||
# 确保纹理已准备并获取OpenGL纹理ID
|
||
gsg = self.world.win.getGsg()
|
||
prepared_objects = gsg.getPreparedObjects()
|
||
|
||
# 准备纹理 - 使用更简单的方法
|
||
try:
|
||
# 方法1: 尝试prepareNow
|
||
if hasattr(left_texture, 'prepareNow'):
|
||
left_texture.prepareNow(0, prepared_objects, gsg)
|
||
if hasattr(right_texture, 'prepareNow'):
|
||
right_texture.prepareNow(0, prepared_objects, gsg)
|
||
except Exception as prep_error:
|
||
print(f"纹理准备失败: {prep_error}")
|
||
# 继续尝试,可能纹理已经准备好了
|
||
|
||
# 获取OpenGL纹理ID
|
||
left_texture_id = self._get_texture_opengl_id(left_texture, prepared_objects, gsg)
|
||
right_texture_id = self._get_texture_opengl_id(right_texture, prepared_objects, gsg)
|
||
|
||
# 检查是否成功获取了纹理ID
|
||
if left_texture_id is not None and left_texture_id > 0 and right_texture_id is not None and right_texture_id > 0:
|
||
try:
|
||
# 创建OpenVR纹理结构
|
||
left_eye_texture = openvr.Texture_t()
|
||
left_eye_texture.handle = int(left_texture_id)
|
||
left_eye_texture.eType = openvr.TextureType_OpenGL
|
||
left_eye_texture.eColorSpace = openvr.ColorSpace_Gamma
|
||
|
||
right_eye_texture = openvr.Texture_t()
|
||
right_eye_texture.handle = int(right_texture_id)
|
||
right_eye_texture.eType = openvr.TextureType_OpenGL
|
||
right_eye_texture.eColorSpace = openvr.ColorSpace_Gamma
|
||
|
||
# 提交到VR系统
|
||
error_left = self.vr_compositor.submit(openvr.Eye_Left, left_eye_texture)
|
||
error_right = self.vr_compositor.submit(openvr.Eye_Right, right_eye_texture)
|
||
|
||
# 检查提交结果 - 在Python OpenVR中,None表示成功
|
||
left_success = (error_left is None or error_left == openvr.VRCompositorError_None)
|
||
right_success = (error_right is None or error_right == openvr.VRCompositorError_None)
|
||
|
||
if not left_success:
|
||
print(f"⚠️ 左眼纹理提交错误: {error_left}")
|
||
self.submit_failures += 1
|
||
if not right_success:
|
||
print(f"⚠️ 右眼纹理提交错误: {error_right}")
|
||
self.submit_failures += 1
|
||
|
||
# 如果两个都成功了,输出成功信息(仅第一次)
|
||
if not hasattr(self, '_first_submit_success'):
|
||
if left_success and right_success:
|
||
print("✅ VR纹理提交成功!缓冲区应该显示场景内容。")
|
||
print(f" 左眼纹理ID: {left_texture_id}, 右眼纹理ID: {right_texture_id}")
|
||
self._first_submit_success = True
|
||
|
||
except Exception as submit_error:
|
||
print(f"❌ VR纹理提交过程失败: {submit_error}")
|
||
if "DoNotHaveFocus" in str(submit_error):
|
||
print("🔍 这通常意味着另一个VR应用程序正在使用VR系统")
|
||
self.submit_failures += 1
|
||
else:
|
||
# 只在第一次失败时输出警告,避免太多日志
|
||
if not hasattr(self, '_texture_id_warning_shown'):
|
||
print("⚠️ 无法获取有效的纹理OpenGL ID,跳过VR帧提交")
|
||
print(" 这可能是因为纹理尚未正确准备到GPU")
|
||
self._texture_id_warning_shown = True
|
||
|
||
except Exception as e:
|
||
print(f"提交VR帧失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _get_texture_opengl_id(self, texture, prepared_objects, gsg):
|
||
"""获取纹理的OpenGL ID"""
|
||
try:
|
||
# 方法1: 使用prepareNow获取正确的纹理上下文
|
||
texture_context = texture.prepareNow(0, prepared_objects, gsg)
|
||
if texture_context and hasattr(texture_context, 'getNativeId'):
|
||
native_id = texture_context.getNativeId()
|
||
if native_id > 0:
|
||
print(f"✓ 成功获取纹理 {texture.getName()} 的OpenGL ID: {native_id}")
|
||
return native_id
|
||
|
||
# 方法2: 备选方案 - 通过prepared_objects获取texture context
|
||
if hasattr(prepared_objects, 'getTextureContext'):
|
||
texture_context = prepared_objects.getTextureContext(texture)
|
||
if texture_context and hasattr(texture_context, 'getNativeId'):
|
||
native_id = texture_context.getNativeId()
|
||
if native_id > 0:
|
||
print(f"✓ 备选方法获取纹理 {texture.getName()} 的OpenGL ID: {native_id}")
|
||
return native_id
|
||
|
||
# 方法3: 尝试texture对象本身的方法
|
||
if hasattr(texture, 'getNativeId'):
|
||
native_id = texture.getNativeId()
|
||
if native_id > 0:
|
||
print(f"✓ 直接获取纹理 {texture.getName()} 的OpenGL ID: {native_id}")
|
||
return native_id
|
||
|
||
# 如果所有方法都失败
|
||
print(f"❌ 无法获取纹理 {texture.getName()} 的OpenGL ID")
|
||
return None
|
||
|
||
except Exception as e:
|
||
print(f"获取纹理OpenGL ID失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def enable_vr(self):
|
||
"""启用VR模式"""
|
||
if not self.is_vr_available():
|
||
print("❌ VR系统不可用")
|
||
return False
|
||
|
||
if not self.vr_initialized:
|
||
if not self.initialize_vr():
|
||
return False
|
||
|
||
self.vr_enabled = True
|
||
|
||
# 禁用主相机避免干扰VR渲染
|
||
self._disable_main_cam()
|
||
|
||
print("✅ VR模式已启用")
|
||
return True
|
||
|
||
def disable_vr(self):
|
||
"""禁用VR模式"""
|
||
self.vr_enabled = False
|
||
|
||
# 恢复主相机
|
||
self._enable_main_cam()
|
||
|
||
print("✅ VR模式已禁用")
|
||
|
||
def cleanup(self):
|
||
"""清理VR资源"""
|
||
try:
|
||
print("🔄 正在清理VR资源...")
|
||
|
||
# 停止VR任务
|
||
if self.vr_task:
|
||
self.world.taskMgr.remove(self.vr_task)
|
||
self.vr_task = None
|
||
|
||
# 清理渲染缓冲区
|
||
if self.vr_left_eye_buffer:
|
||
self.vr_left_eye_buffer.removeAllDisplayRegions()
|
||
self.world.graphicsEngine.removeWindow(self.vr_left_eye_buffer)
|
||
self.vr_left_eye_buffer = None
|
||
|
||
if self.vr_right_eye_buffer:
|
||
self.vr_right_eye_buffer.removeAllDisplayRegions()
|
||
self.world.graphicsEngine.removeWindow(self.vr_right_eye_buffer)
|
||
self.vr_right_eye_buffer = None
|
||
|
||
# 清理相机
|
||
if self.vr_left_camera:
|
||
self.vr_left_camera.removeNode()
|
||
self.vr_left_camera = None
|
||
|
||
if self.vr_right_camera:
|
||
self.vr_right_camera.removeNode()
|
||
self.vr_right_camera = None
|
||
|
||
# 关闭OpenVR
|
||
if self.vr_system and OPENVR_AVAILABLE:
|
||
try:
|
||
openvr.shutdown()
|
||
except:
|
||
pass
|
||
self.vr_system = None
|
||
|
||
self.vr_enabled = False
|
||
self.vr_initialized = False
|
||
|
||
print("✅ VR资源清理完成")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ VR清理过程中出错: {e}")
|
||
|
||
def get_vr_status(self):
|
||
"""获取VR状态信息"""
|
||
return {
|
||
'available': self.is_vr_available(),
|
||
'initialized': self.vr_initialized,
|
||
'enabled': self.vr_enabled,
|
||
'eye_resolution': (self.eye_width, self.eye_height),
|
||
'device_count': len(self.controller_poses) + (1 if self.vr_enabled else 0),
|
||
'vr_fps': self.vr_fps,
|
||
'frame_count': self.frame_count,
|
||
'submit_failures': self.submit_failures,
|
||
'pose_failures': self.pose_failures
|
||
}
|
||
|
||
def _print_performance_report(self):
|
||
"""输出VR性能报告"""
|
||
print("📊 === VR性能报告 ===")
|
||
print(f" VR帧率: {self.vr_fps:.1f} FPS")
|
||
print(f" 总帧数: {self.frame_count}")
|
||
print(f" 提交失败: {self.submit_failures}")
|
||
print(f" 姿态失败: {self.pose_failures}")
|
||
|
||
# 计算失败率
|
||
if self.frame_count > 0:
|
||
submit_fail_rate = (self.submit_failures / self.frame_count) * 100
|
||
pose_fail_rate = (self.pose_failures / self.frame_count) * 100
|
||
print(f" 提交失败率: {submit_fail_rate:.2f}%")
|
||
print(f" 姿态失败率: {pose_fail_rate:.2f}%")
|
||
|
||
print("========================")
|
||
|
||
def left_cb(self, cbdata):
|
||
"""左眼渲染回调 - 基于参考实现"""
|
||
# 执行实际的渲染工作
|
||
cbdata.upcall()
|
||
# 根据提交策略决定是否立即提交
|
||
if not self.submit_together:
|
||
# 分别提交模式:左眼渲染完成后立即提交
|
||
self.submit_texture(openvr.Eye_Left, self.vr_left_texture)
|
||
|
||
def right_cb(self, cbdata):
|
||
"""右眼渲染回调 - 基于参考实现"""
|
||
# 执行实际的渲染工作
|
||
cbdata.upcall()
|
||
# 根据提交策略决定提交方式
|
||
if self.submit_together:
|
||
# 同时提交模式:右眼渲染完成后同时提交左右眼
|
||
self.submit_texture(openvr.Eye_Left, self.vr_left_texture)
|
||
self.submit_texture(openvr.Eye_Right, self.vr_right_texture)
|
||
else:
|
||
# 分别提交模式:只提交右眼
|
||
self.submit_texture(openvr.Eye_Right, self.vr_right_texture)
|
||
|
||
def submit_texture(self, eye, texture):
|
||
"""提交纹理到VR - 基于参考实现,增强调试信息"""
|
||
try:
|
||
if not self.vr_compositor:
|
||
print("❌ VR compositor不可用")
|
||
self.submit_failures += 1
|
||
return
|
||
|
||
# 获取graphics state guardian和prepared objects
|
||
gsg = self.world.win.getGsg()
|
||
if not gsg:
|
||
print("❌ 无法获取GraphicsStateGuardian")
|
||
self.submit_failures += 1
|
||
return
|
||
|
||
prepared_objects = gsg.getPreparedObjects()
|
||
if not prepared_objects:
|
||
print("❌ 无法获取PreparedObjects")
|
||
self.submit_failures += 1
|
||
return
|
||
|
||
# 准备纹理并获取更详细的错误信息
|
||
if not texture:
|
||
print("❌ 纹理对象为空")
|
||
self.submit_failures += 1
|
||
return
|
||
|
||
print(f"🔍 准备纹理: {texture.getName()}, 大小: {texture.getXSize()}x{texture.getYSize()}")
|
||
|
||
texture_context = texture.prepareNow(0, prepared_objects, gsg)
|
||
if not texture_context:
|
||
print("❌ prepareNow返回空的texture_context")
|
||
self.submit_failures += 1
|
||
return
|
||
|
||
handle = texture_context.getNativeId()
|
||
print(f"🔍 获取OpenGL纹理句柄: {handle}")
|
||
|
||
if handle != 0:
|
||
ovr_texture = openvr.Texture_t()
|
||
ovr_texture.handle = handle
|
||
ovr_texture.eType = openvr.TextureType_OpenGL
|
||
ovr_texture.eColorSpace = openvr.ColorSpace_Gamma
|
||
|
||
eye_name = "左眼" if eye == openvr.Eye_Left else "右眼"
|
||
print(f"🔍 提交{eye_name}纹理到VR, 句柄: {handle}")
|
||
|
||
# 提交到VR系统
|
||
error = self.vr_compositor.submit(eye, ovr_texture)
|
||
|
||
print(f"🔍 VR提交结果: {error}")
|
||
|
||
# 检查错误
|
||
if error and error != openvr.VRCompositorError_None:
|
||
print(f"⚠️ VR纹理提交错误代码: {error}")
|
||
self.submit_failures += 1
|
||
else:
|
||
# 只在第一次成功时输出
|
||
if not hasattr(self, '_submit_success_logged'):
|
||
print(f"✅ VR纹理提交成功! {eye_name}")
|
||
self._submit_success_logged = True
|
||
else:
|
||
print(f"❌ 无法获取纹理OpenGL句柄: handle = {handle}")
|
||
print(f" 纹理状态: 已准备={texture.isPrepared(prepared_objects)}")
|
||
print(f" 纹理格式: {texture.getFormat()}")
|
||
self.submit_failures += 1
|
||
|
||
except Exception as e:
|
||
print(f"❌ VR纹理提交异常: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
self.submit_failures += 1
|
||
|
||
def _disable_main_cam(self):
|
||
"""禁用主相机 - 基于参考实现"""
|
||
try:
|
||
# 保存原始相机状态
|
||
if not hasattr(self, '_original_camera_parent'):
|
||
self._original_camera_parent = self.world.camera.getParent()
|
||
|
||
# 创建空节点并将主相机重新附加到它
|
||
self._empty_world = NodePath("empty_world")
|
||
self.world.camera.reparentTo(self._empty_world)
|
||
|
||
print("✓ 主相机已禁用")
|
||
except Exception as e:
|
||
print(f"⚠️ 禁用主相机失败: {e}")
|
||
|
||
def _enable_main_cam(self):
|
||
"""恢复主相机 - 基于参考实现"""
|
||
try:
|
||
# 恢复原始相机状态
|
||
if hasattr(self, '_original_camera_parent') and self._original_camera_parent:
|
||
self.world.camera.reparentTo(self._original_camera_parent)
|
||
else:
|
||
# 如果没有保存的父节点,重新附加到render
|
||
self.world.camera.reparentTo(self.world.render)
|
||
|
||
# 清理空世界节点
|
||
if hasattr(self, '_empty_world'):
|
||
self._empty_world.removeNode()
|
||
delattr(self, '_empty_world')
|
||
|
||
print("✓ 主相机已恢复")
|
||
except Exception as e:
|
||
print(f"⚠️ 恢复主相机失败: {e}")
|
||
|
||
def _initialize_controllers(self):
|
||
"""初始化VR手柄控制器"""
|
||
try:
|
||
print("🎮 正在初始化VR手柄控制器...")
|
||
|
||
# 创建左右手柄控制器实例
|
||
self.left_controller = LeftController(self)
|
||
self.right_controller = RightController(self)
|
||
|
||
# 检测现有连接的控制器
|
||
self._detect_controllers()
|
||
|
||
print("✓ VR手柄控制器初始化完成")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ VR手柄初始化失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _detect_controllers(self):
|
||
"""检测并连接VR控制器"""
|
||
if not self.vr_system:
|
||
return
|
||
|
||
try:
|
||
for device_index in range(openvr.k_unMaxTrackedDeviceCount):
|
||
# 检查设备是否已连接
|
||
if not self.vr_system.isTrackedDeviceConnected(device_index):
|
||
continue
|
||
|
||
# 获取设备类型
|
||
device_class = self.vr_system.getTrackedDeviceClass(device_index)
|
||
|
||
if device_class == openvr.TrackedDeviceClass_Controller:
|
||
# 获取控制器角色
|
||
role = self.vr_system.getControllerRoleForTrackedDeviceIndex(device_index)
|
||
|
||
if role == openvr.TrackedControllerRole_LeftHand and self.left_controller:
|
||
self.left_controller.set_device_index(device_index)
|
||
self.controllers[device_index] = self.left_controller
|
||
# 为设备创建锚点
|
||
self._create_tracked_device_anchor(device_index, 'left_controller')
|
||
|
||
elif role == openvr.TrackedControllerRole_RightHand and self.right_controller:
|
||
self.right_controller.set_device_index(device_index)
|
||
self.controllers[device_index] = self.right_controller
|
||
# 为设备创建锚点
|
||
self._create_tracked_device_anchor(device_index, 'right_controller')
|
||
|
||
print(f"🎮 检测到 {len(self.controllers)} 个控制器")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 控制器检测失败: {e}")
|
||
|
||
def _create_tracked_device_anchor(self, device_index, name):
|
||
"""为跟踪设备创建锚点节点"""
|
||
if not self.tracking_space:
|
||
print(f"⚠️ 无法为设备 {device_index} 创建锚点 - tracking_space未初始化")
|
||
return
|
||
|
||
try:
|
||
# 获取设备模型名称
|
||
if self.vr_system:
|
||
model_name = self.vr_system.getStringTrackedDeviceProperty(
|
||
device_index,
|
||
openvr.Prop_RenderModelName_String
|
||
)
|
||
anchor_name = f"{device_index}:{model_name}:{name}"
|
||
else:
|
||
anchor_name = f"{device_index}:{name}"
|
||
|
||
# 创建锚点节点
|
||
device_anchor = self.tracking_space.attachNewNode(anchor_name)
|
||
self.tracked_device_anchors[device_index] = device_anchor
|
||
|
||
print(f"✓ 为设备 {device_index} 创建锚点: {anchor_name}")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 创建设备锚点失败: {e}")
|
||
|
||
def update_tracked_devices(self):
|
||
"""更新所有跟踪设备的姿态 - 基于参考实现"""
|
||
if not self.poses or not self.vr_system:
|
||
return
|
||
|
||
try:
|
||
# 更新每个已连接的控制器
|
||
for device_index, controller in self.controllers.items():
|
||
if device_index < len(self.poses):
|
||
pose_data = self.poses[device_index]
|
||
|
||
# 更新控制器姿态
|
||
controller.update_pose(pose_data)
|
||
|
||
# 更新控制器输入状态
|
||
controller.update_input_state(self.vr_system)
|
||
|
||
# 更新其他跟踪设备的锚点
|
||
for device_index in range(1, min(len(self.poses), openvr.k_unMaxTrackedDeviceCount)):
|
||
if device_index in self.tracked_device_anchors:
|
||
pose_data = self.poses[device_index]
|
||
if pose_data.bPoseIsValid:
|
||
# 转换姿态矩阵
|
||
modelview = self.convert_mat(pose_data.mDeviceToAbsoluteTracking)
|
||
final_matrix = self.coord_mat_inv * modelview * self.coord_mat
|
||
|
||
# 更新锚点变换
|
||
anchor = self.tracked_device_anchors[device_index]
|
||
anchor.setMat(final_matrix)
|
||
anchor.show()
|
||
else:
|
||
# 姿态无效,隐藏锚点
|
||
self.tracked_device_anchors[device_index].hide()
|
||
|
||
except Exception as e:
|
||
if self.frame_count % 300 == 0: # 每5秒输出一次错误
|
||
print(f"⚠️ 更新跟踪设备失败: {e}")
|
||
|
||
def get_controller_by_role(self, role):
|
||
"""根据角色获取控制器
|
||
|
||
Args:
|
||
role: 'left' 或 'right'
|
||
|
||
Returns:
|
||
VRController实例或None
|
||
"""
|
||
if role == 'left':
|
||
return self.left_controller
|
||
elif role == 'right':
|
||
return self.right_controller
|
||
return None
|
||
|
||
def are_controllers_connected(self):
|
||
"""检查是否有控制器连接"""
|
||
return len(self.controllers) > 0
|
||
|
||
def get_connected_controllers(self):
|
||
"""获取所有连接的控制器列表"""
|
||
return list(self.controllers.values())
|
||
|
||
def trigger_controller_haptic(self, role, duration=0.001, strength=1.0):
|
||
"""触发控制器震动反馈
|
||
|
||
Args:
|
||
role: 'left', 'right' 或 'both'
|
||
duration: 震动持续时间(秒)
|
||
strength: 震动强度 (0.0-1.0)
|
||
"""
|
||
if role in ['left', 'both'] and self.left_controller:
|
||
self.left_controller.trigger_haptic_feedback(duration, strength)
|
||
|
||
if role in ['right', 'both'] and self.right_controller:
|
||
self.right_controller.trigger_haptic_feedback(duration, strength)
|
||
|
||
# VR动作系统便捷方法
|
||
def is_trigger_pressed(self, hand='any'):
|
||
"""检查扳机是否被按下
|
||
|
||
Args:
|
||
hand: 'left', 'right', 'any'
|
||
"""
|
||
device_path = None
|
||
if hand == 'left':
|
||
device_path = '/user/hand/left'
|
||
elif hand == 'right':
|
||
device_path = '/user/hand/right'
|
||
|
||
pressed, _ = self.action_manager.is_digital_action_pressed('trigger', device_path)
|
||
return pressed
|
||
|
||
def is_trigger_just_pressed(self, hand='any'):
|
||
"""检查扳机是否刚刚被按下"""
|
||
device_path = None
|
||
if hand == 'left':
|
||
device_path = '/user/hand/left'
|
||
elif hand == 'right':
|
||
device_path = '/user/hand/right'
|
||
|
||
pressed, _ = self.action_manager.is_digital_action_just_pressed('trigger', device_path)
|
||
return pressed
|
||
|
||
def is_grip_pressed(self, hand='any'):
|
||
"""检查握把是否被按下"""
|
||
device_path = None
|
||
if hand == 'left':
|
||
device_path = '/user/hand/left'
|
||
elif hand == 'right':
|
||
device_path = '/user/hand/right'
|
||
|
||
pressed, _ = self.action_manager.is_digital_action_pressed('grip', device_path)
|
||
return pressed
|
||
|
||
def is_grip_just_pressed(self, hand='any'):
|
||
"""检查握把是否刚刚被按下"""
|
||
device_path = None
|
||
if hand == 'left':
|
||
device_path = '/user/hand/left'
|
||
elif hand == 'right':
|
||
device_path = '/user/hand/right'
|
||
|
||
pressed, _ = self.action_manager.is_digital_action_just_pressed('grip', device_path)
|
||
return pressed
|
||
|
||
def is_menu_pressed(self, hand='any'):
|
||
"""检查菜单按钮是否被按下"""
|
||
device_path = None
|
||
if hand == 'left':
|
||
device_path = '/user/hand/left'
|
||
elif hand == 'right':
|
||
device_path = '/user/hand/right'
|
||
|
||
pressed, _ = self.action_manager.is_digital_action_pressed('menu', device_path)
|
||
return pressed
|
||
|
||
def is_trackpad_touched(self, hand='any'):
|
||
"""检查触摸板是否被触摸"""
|
||
device_path = None
|
||
if hand == 'left':
|
||
device_path = '/user/hand/left'
|
||
elif hand == 'right':
|
||
device_path = '/user/hand/right'
|
||
|
||
touched, _ = self.action_manager.is_digital_action_pressed('trackpad_touch', device_path)
|
||
return touched
|
||
|
||
def get_trackpad_position(self, hand='any'):
|
||
"""获取触摸板位置
|
||
|
||
Returns:
|
||
Vec2或None: 触摸板位置 (-1到1的范围)
|
||
"""
|
||
device_path = None
|
||
if hand == 'left':
|
||
device_path = '/user/hand/left'
|
||
elif hand == 'right':
|
||
device_path = '/user/hand/right'
|
||
|
||
value, _ = self.action_manager.get_analog_action_value('trackpad', device_path)
|
||
return value
|
||
|
||
# VR交互系统便捷方法
|
||
def get_selected_object(self, hand='any'):
|
||
"""获取指定手选中的对象
|
||
|
||
Args:
|
||
hand: 'left', 'right', 'any'
|
||
|
||
Returns:
|
||
选中的对象节点或None
|
||
"""
|
||
if hand == 'any':
|
||
# 返回任意手选中的对象
|
||
for controller in self.get_connected_controllers():
|
||
selected = self.interaction_manager.get_selected_object(controller.name)
|
||
if selected:
|
||
return selected
|
||
return None
|
||
else:
|
||
return self.interaction_manager.get_selected_object(hand)
|
||
|
||
def get_grabbed_object(self, hand='any'):
|
||
"""获取指定手抓取的对象
|
||
|
||
Args:
|
||
hand: 'left', 'right', 'any'
|
||
|
||
Returns:
|
||
抓取的对象节点或None
|
||
"""
|
||
if hand == 'any':
|
||
# 返回任意手抓取的对象
|
||
for controller in self.get_connected_controllers():
|
||
grabbed = self.interaction_manager.get_grabbed_object(controller.name)
|
||
if grabbed:
|
||
return grabbed
|
||
return None
|
||
else:
|
||
return self.interaction_manager.get_grabbed_object(hand)
|
||
|
||
def is_grabbing_object(self, hand='any'):
|
||
"""检查是否正在抓取对象
|
||
|
||
Args:
|
||
hand: 'left', 'right', 'any'
|
||
|
||
Returns:
|
||
bool: 是否正在抓取
|
||
"""
|
||
if hand == 'any':
|
||
# 检查任意手是否正在抓取
|
||
for controller in self.get_connected_controllers():
|
||
if self.interaction_manager.is_grabbing(controller.name):
|
||
return True
|
||
return False
|
||
else:
|
||
return self.interaction_manager.is_grabbing(hand)
|
||
|
||
def force_release_all_grabs(self):
|
||
"""强制释放所有抓取的对象"""
|
||
self.interaction_manager.force_release_all()
|
||
|
||
def add_interactable_object(self, object_node):
|
||
"""将对象标记为可交互
|
||
|
||
Args:
|
||
object_node: 要标记的对象节点
|
||
"""
|
||
self.interaction_manager._add_collision_to_object(object_node) |