""" 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: # 更新按钮状态 - 使用正确的OpenVR属性名 for i in range(openvr.k_EButton_Max): button_mask = 1 << i # OpenVR Python绑定中使用ulButtonPressed而不是rButtonPressed self.button_states[i] = (state.ulButtonPressed & button_mask) != 0 # 更新轴状态(扳机、握把、触摸板) # OpenVR Python绑定中使用vAxis而不是rAxis if hasattr(state, 'vAxis') and len(state.vAxis) > 0: # 扳机轴通常在axis[1].x if len(state.vAxis) > 1: self.trigger_value = state.vAxis[1].x # 触摸板轴通常在axis[0] if len(state.vAxis) > 0: self.touchpad_pos = Vec3(state.vAxis[0].x, state.vAxis[0].y, 0) # 触摸板触摸状态 - 使用正确的OpenVR属性名 self.touchpad_touched = (state.ulButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 0 except Exception as e: # 减少错误输出频率 if not hasattr(self, '_last_input_error_frame'): self._last_input_error_frame = 0 # 获取当前帧数(通过VR管理器) current_frame = getattr(self.vr_manager, 'frame_count', 0) # 每5秒最多输出一次错误(300帧@60fps) if current_frame - self._last_input_error_frame > 300: print(f"⚠️ 更新{self.name}手柄输入状态失败: {e}") self._last_input_error_frame = current_frame 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')