EG/core/vr_controller.py
2025-09-15 16:41:35 +08:00

282 lines
9.5 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手柄管理模块
基于panda3d-openvr参考实现提供完整的VR手柄追踪和交互功能
- 手柄位置和姿态追踪
- 按钮和触摸板输入处理
- 手柄可视化和射线显示
- 震动反馈支持
"""
from panda3d.core import (
NodePath, PandaNode, Vec3, Mat4, LVector3, LMatrix4,
GeomNode, LineSegs, CardMaker, Texture, RenderState,
TransparencyAttrib, ColorAttrib, Vec4
)
from direct.actor.Actor import Actor
from direct.showbase.DirectObject import DirectObject
try:
import openvr
OPENVR_AVAILABLE = True
except ImportError:
OPENVR_AVAILABLE = False
# 导入可视化器
from .vr_visualization import VRControllerVisualizer
class VRController(DirectObject):
"""VR手柄基类 - 管理单个手柄的追踪和交互"""
def __init__(self, vr_manager, name, hand_path, device_index=None):
"""初始化VR手柄
Args:
vr_manager: VR管理器实例
name: 手柄名称 ('left''right')
hand_path: OpenVR手部路径 ('/user/hand/left''/user/hand/right')
device_index: OpenVR设备索引可选
"""
super().__init__()
self.vr_manager = vr_manager
self.name = name
self.hand_path = hand_path
self.device_index = device_index
# 手柄状态
self.is_connected = False
self.is_pose_valid = False
self.pose = Mat4.identMat()
self.velocity = Vec3(0, 0, 0)
self.angular_velocity = Vec3(0, 0, 0)
# 按钮状态
self.button_states = {}
self.previous_button_states = {}
self.trigger_value = 0.0
self.grip_value = 0.0
self.touchpad_pos = Vec3(0, 0, 0)
self.touchpad_touched = False
# 3D节点和可视化
self.anchor_node = None
self.visualizer = None
self.ray_length = 10.0
# 初始化
self._create_anchor()
self._create_visualizer()
print(f"{name}手柄控制器初始化完成")
def _create_anchor(self):
"""创建手柄锚点节点"""
if self.vr_manager.tracking_space:
self.anchor_node = self.vr_manager.tracking_space.attachNewNode(f'{self.name}-controller')
self.anchor_node.hide() # 初始隐藏,直到获得有效姿态
def _create_visualizer(self):
"""创建手柄可视化器"""
if self.anchor_node and hasattr(self.vr_manager, 'world'):
self.visualizer = VRControllerVisualizer(self, self.vr_manager.world.render)
elif self.anchor_node:
# 如果没有世界对象,使用基础渲染节点
from panda3d.core import NodePath
render = NodePath('render')
self.visualizer = VRControllerVisualizer(self, render)
def set_device_index(self, device_index):
"""设置OpenVR设备索引"""
self.device_index = device_index
self.is_connected = True
print(f"📱 {self.name}手柄连接 (设备索引: {device_index})")
def update_pose(self, pose_data):
"""更新手柄姿态
Args:
pose_data: OpenVR TrackedDevicePose_t数据
"""
if not pose_data.bPoseIsValid:
self.is_pose_valid = False
if self.anchor_node:
self.anchor_node.hide()
return
self.is_pose_valid = True
# 转换OpenVR矩阵到Panda3D
if hasattr(self.vr_manager, 'convert_mat') and hasattr(self.vr_manager, 'coord_mat_inv') and hasattr(self.vr_manager, 'coord_mat'):
modelview = self.vr_manager.convert_mat(pose_data.mDeviceToAbsoluteTracking)
self.pose = self.vr_manager.coord_mat_inv * modelview * self.vr_manager.coord_mat
else:
# 直接使用矩阵数据
m = pose_data.mDeviceToAbsoluteTracking.m
self.pose = LMatrix4(
m[0][0], m[1][0], m[2][0], m[3][0],
m[0][1], m[1][1], m[2][1], m[3][1],
m[0][2], m[1][2], m[2][2], m[3][2],
m[0][3], m[1][3], m[2][3], m[3][3]
)
# 更新锚点变换
if self.anchor_node:
self.anchor_node.setMat(self.pose)
self.anchor_node.show()
# 更新可视化
if self.visualizer:
self.visualizer.update()
# 更新速度信息
vel = pose_data.vVelocity
self.velocity = Vec3(vel[0], vel[1], vel[2])
ang_vel = pose_data.vAngularVelocity
self.angular_velocity = Vec3(ang_vel[0], ang_vel[1], ang_vel[2])
def update_input_state(self, vr_system):
"""更新输入状态
Args:
vr_system: OpenVR系统实例
"""
if not self.is_connected or not OPENVR_AVAILABLE or not vr_system:
return
# 保存上一帧的按钮状态
self.previous_button_states = self.button_states.copy()
# 获取控制器状态
try:
result, state = vr_system.getControllerState(self.device_index)
if result:
# 更新按钮状态
for i in range(openvr.k_EButton_Max):
button_mask = 1 << i
self.button_states[i] = (state.rButtonPressed & button_mask) != 0
# 更新轴状态(扳机、握把、触摸板)
if len(state.rAxis) > 0:
# 扳机轴通常在axis[1].x
if len(state.rAxis) > 1:
self.trigger_value = state.rAxis[1].x
# 触摸板轴通常在axis[0]
if len(state.rAxis) > 0:
self.touchpad_pos = Vec3(state.rAxis[0].x, state.rAxis[0].y, 0)
# 触摸板触摸状态
self.touchpad_touched = (state.rButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 0
except Exception as e:
print(f"⚠️ 更新{self.name}手柄输入状态失败: {e}")
def is_button_pressed(self, button_id):
"""检查按钮是否被按下"""
return self.button_states.get(button_id, False)
def is_button_just_pressed(self, button_id):
"""检查按钮是否刚刚被按下(上升沿)"""
current = self.button_states.get(button_id, False)
previous = self.previous_button_states.get(button_id, False)
return current and not previous
def is_button_just_released(self, button_id):
"""检查按钮是否刚刚被释放(下降沿)"""
current = self.button_states.get(button_id, False)
previous = self.previous_button_states.get(button_id, False)
return not current and previous
def is_trigger_pressed(self, threshold=0.1):
"""检查扳机是否被按下"""
return self.trigger_value > threshold
def is_grip_pressed(self, threshold=0.1):
"""检查握把是否被按下"""
return self.grip_value > threshold
def show_ray(self, show=True):
"""显示或隐藏交互射线"""
if self.visualizer:
if show:
self.visualizer.show_ray()
else:
self.visualizer.hide_ray()
def set_ray_color(self, color):
"""设置射线颜色"""
if self.visualizer and len(color) >= 3:
from panda3d.core import Vec4
color_vec = Vec4(color[0], color[1], color[2], color[3] if len(color) > 3 else 1.0)
self.visualizer.set_ray_color(color_vec)
def trigger_haptic_feedback(self, duration=0.001, strength=1.0):
"""触发震动反馈
Args:
duration: 震动持续时间(秒)
strength: 震动强度 (0.0-1.0)
"""
if not self.is_connected or not OPENVR_AVAILABLE:
return
try:
if hasattr(self.vr_manager, 'vr_system') and self.vr_manager.vr_system:
# OpenVR的震动API
duration_microseconds = int(duration * 1000000)
self.vr_manager.vr_system.triggerHapticPulse(
self.device_index,
0, # axis ID (通常为0)
int(strength * 3999) # 强度 (0-3999)
)
except Exception as e:
print(f"⚠️ {self.name}手柄震动反馈失败: {e}")
def get_world_position(self):
"""获取手柄在世界坐标系中的位置"""
if self.anchor_node:
return self.anchor_node.getPos(self.vr_manager.world.render)
return Vec3(0, 0, 0)
def get_world_rotation(self):
"""获取手柄在世界坐标系中的旋转"""
if self.anchor_node:
return self.anchor_node.getHpr(self.vr_manager.world.render)
return Vec3(0, 0, 0)
def get_forward_direction(self):
"""获取手柄指向的方向向量"""
if self.anchor_node:
# Y轴正方向为前方
return self.anchor_node.getMat().getRow3(1).getXyz().normalized()
return Vec3(0, 1, 0)
def cleanup(self):
"""清理资源"""
self.ignoreAll()
if self.visualizer:
self.visualizer.cleanup()
if self.anchor_node:
self.anchor_node.removeNode()
self.is_connected = False
print(f"🧹 {self.name}手柄控制器已清理")
class LeftController(VRController):
"""左手控制器"""
def __init__(self, vr_manager):
super().__init__(vr_manager, 'left', '/user/hand/left')
class RightController(VRController):
"""右手控制器"""
def __init__(self, vr_manager):
super().__init__(vr_manager, 'right', '/user/hand/right')