forked from Rowland/EG
282 lines
9.5 KiB
Python
282 lines
9.5 KiB
Python
"""
|
||
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') |