""" VR动作系统模块 基于OpenVR Action系统,提供高级的输入处理和动作映射: - VR动作清单管理 - 按钮和轴输入处理 - 触觉反馈 - 动作集管理 """ import json import os from pathlib import Path from direct.showbase.DirectObject import DirectObject try: import openvr OPENVR_AVAILABLE = True except ImportError: OPENVR_AVAILABLE = False class VRActionManager(DirectObject): """VR动作管理器 - 处理OpenVR动作系统""" def __init__(self, vr_manager): """初始化VR动作管理器 Args: vr_manager: VR管理器实例 """ super().__init__() self.vr_manager = vr_manager self.vr_input = None self.action_set_handles = [] self.action_handles = {} # 预定义的标准动作 self.standard_actions = { # 姿态动作 'pose': '/actions/default/in/Pose', # 按钮动作 'trigger': '/actions/default/in/Trigger', 'grip': '/actions/default/in/Grip', 'menu': '/actions/default/in/Menu', 'system': '/actions/default/in/System', 'trackpad_click': '/actions/default/in/TrackpadClick', 'trackpad_touch': '/actions/default/in/TrackpadTouch', 'a_button': '/actions/default/in/AButton', 'b_button': '/actions/default/in/BButton', # 轴动作 'trackpad': '/actions/default/in/Trackpad', 'joystick': '/actions/default/in/Joystick', 'squeeze': '/actions/default/in/Squeeze', # 震动输出 'haptic': '/actions/default/out/Haptic' } # 动作集 self.default_action_set = '/actions/default' print("✓ VR动作管理器初始化完成") def initialize(self): """初始化VR动作系统""" if not OPENVR_AVAILABLE or not self.vr_manager.vr_system: print("⚠️ VR系统不可用,无法初始化动作系统") return False try: print("🎮 正在初始化VR动作系统...") # 获取VR输入接口 self.vr_input = openvr.VRInput() if not self.vr_input: print("❌ 无法获取VR输入接口") return False # 创建动作清单文件 manifest_path = self._create_action_manifest() if not manifest_path: print("❌ 无法创建动作清单") return False # 加载动作清单 error = self.vr_input.setActionManifestPath(manifest_path) if error != openvr.VRInputError_None: print(f"❌ 加载动作清单失败: {error}") return False # 获取动作句柄 self._load_action_handles() # 创建动作集 self._setup_action_sets() print("✅ VR动作系统初始化成功") return True except Exception as e: print(f"❌ VR动作系统初始化失败: {e}") import traceback traceback.print_exc() return False def _create_action_manifest(self): """创建VR动作清单文件""" try: # 动作清单配置 manifest_data = { "actions": [ # 姿态动作 { "name": "/actions/default/in/Pose", "type": "pose" }, # 数字动作(按钮) { "name": "/actions/default/in/Trigger", "type": "boolean" }, { "name": "/actions/default/in/Grip", "type": "boolean" }, { "name": "/actions/default/in/Menu", "type": "boolean" }, { "name": "/actions/default/in/System", "type": "boolean" }, { "name": "/actions/default/in/TrackpadClick", "type": "boolean" }, { "name": "/actions/default/in/TrackpadTouch", "type": "boolean" }, { "name": "/actions/default/in/AButton", "type": "boolean" }, { "name": "/actions/default/in/BButton", "type": "boolean" }, # 模拟动作(轴) { "name": "/actions/default/in/Trackpad", "type": "vector2" }, { "name": "/actions/default/in/Joystick", "type": "vector2" }, { "name": "/actions/default/in/Squeeze", "type": "vector1" }, # 震动输出 { "name": "/actions/default/out/Haptic", "type": "vibration" } ], "action_sets": [ { "name": "/actions/default", "usage": "single" } ], "default_bindings": [ { "controller_type": "vive_controller", "binding_url": "bindings_vive.json" }, { "controller_type": "oculus_touch", "binding_url": "bindings_oculus.json" }, { "controller_type": "knuckles", "binding_url": "bindings_index.json" } ], "localization": [ { "language_tag": "zh_CN", "/actions/default/in/Trigger": "扳机", "/actions/default/in/Grip": "握把", "/actions/default/in/Menu": "菜单", "/actions/default/in/System": "系统", "/actions/default/in/TrackpadClick": "触摸板点击", "/actions/default/in/TrackpadTouch": "触摸板触摸", "/actions/default/in/Pose": "手部姿态", "/actions/default/out/Haptic": "震动反馈" } ] } # 保存到临时目录 manifest_dir = Path.cwd() / "vr_actions" manifest_dir.mkdir(exist_ok=True) manifest_path = manifest_dir / "actions.json" with open(manifest_path, 'w', encoding='utf-8') as f: json.dump(manifest_data, f, indent=2, ensure_ascii=False) # 创建基本的绑定文件 self._create_default_bindings(manifest_dir) print(f"✓ 动作清单已创建: {manifest_path}") return str(manifest_path) except Exception as e: print(f"❌ 创建动作清单失败: {e}") return None def _create_default_bindings(self, manifest_dir): """创建默认的控制器绑定文件""" # Vive控制器绑定 vive_bindings = { "controller_type": "vive_controller", "description": "Vive控制器绑定", "name": "EG VR Editor - Vive", "bindings": { "/actions/default": { "sources": [ { "inputs": { "click": { "output": "/actions/default/in/Trigger" } }, "mode": "button", "path": "/user/hand/left/input/trigger" }, { "inputs": { "click": { "output": "/actions/default/in/Trigger" } }, "mode": "button", "path": "/user/hand/right/input/trigger" }, { "inputs": { "click": { "output": "/actions/default/in/Grip" } }, "mode": "button", "path": "/user/hand/left/input/grip" }, { "inputs": { "click": { "output": "/actions/default/in/Grip" } }, "mode": "button", "path": "/user/hand/right/input/grip" }, { "inputs": { "click": { "output": "/actions/default/in/Menu" } }, "mode": "button", "path": "/user/hand/left/input/menu" }, { "inputs": { "position": { "output": "/actions/default/in/Trackpad" }, "click": { "output": "/actions/default/in/TrackpadClick" }, "touch": { "output": "/actions/default/in/TrackpadTouch" } }, "mode": "trackpad", "path": "/user/hand/left/input/trackpad" }, { "inputs": { "position": { "output": "/actions/default/in/Trackpad" }, "click": { "output": "/actions/default/in/TrackpadClick" }, "touch": { "output": "/actions/default/in/TrackpadTouch" } }, "mode": "trackpad", "path": "/user/hand/right/input/trackpad" } ], "poses": [ { "output": "/actions/default/in/Pose", "path": "/user/hand/left/pose/raw" }, { "output": "/actions/default/in/Pose", "path": "/user/hand/right/pose/raw" } ], "haptics": [ { "output": "/actions/default/out/Haptic", "path": "/user/hand/left/output/haptic" }, { "output": "/actions/default/out/Haptic", "path": "/user/hand/right/output/haptic" } ] } } } bindings_path = manifest_dir / "bindings_vive.json" with open(bindings_path, 'w', encoding='utf-8') as f: json.dump(vive_bindings, f, indent=2) print(f"✓ Vive控制器绑定已创建: {bindings_path}") def _load_action_handles(self): """加载动作句柄""" if not self.vr_input: return try: for action_name, action_path in self.standard_actions.items(): handle = self.vr_input.getActionHandle(action_path) self.action_handles[action_name] = handle print(f"✓ 加载动作: {action_name} -> {handle}") except Exception as e: print(f"⚠️ 加载动作句柄失败: {e}") def _setup_action_sets(self): """设置动作集""" if not self.vr_input: return try: # 获取默认动作集句柄 action_set_handle = self.vr_input.getActionSetHandle(self.default_action_set) self.action_set_handles = [action_set_handle] print(f"✓ 动作集已设置: {self.default_action_set}") except Exception as e: print(f"⚠️ 设置动作集失败: {e}") def update_actions(self): """更新动作状态 - 每帧调用""" if not self.vr_input or not self.action_set_handles: return try: # 更新动作状态 action_sets = (openvr.VRActiveActionSet_t * len(self.action_set_handles))() for i, action_set_handle in enumerate(self.action_set_handles): action_sets[i].ulActionSet = action_set_handle self.vr_input.updateActionState(action_sets) except Exception as e: # 限制错误输出频率 if not hasattr(self, '_last_action_error_frame'): self._last_action_error_frame = 0 if hasattr(self.vr_manager, 'frame_count'): if self.vr_manager.frame_count - self._last_action_error_frame > 300: # 每5秒输出一次 print(f"⚠️ 更新动作状态失败: {e}") self._last_action_error_frame = self.vr_manager.frame_count def is_digital_action_pressed(self, action_name, device_path=None): """检查数字动作是否被按下 Args: action_name: 动作名称 device_path: 设备路径(可选) Returns: tuple: (是否按下, 设备路径) """ if not self.vr_input or action_name not in self.action_handles: return False, None try: action_handle = self.action_handles[action_name] device_handle = openvr.k_ulInvalidInputValueHandle if device_path: device_handle = self.vr_input.getInputSourceHandle(device_path) action_data = self.vr_input.getDigitalActionData(action_handle, device_handle) if device_path and action_data.bActive: origin_info = self.vr_input.getOriginTrackedDeviceInfo(action_data.activeOrigin) device_path = origin_info.devicePath return action_data.bActive and action_data.bState, device_path except Exception as e: return False, None def is_digital_action_just_pressed(self, action_name, device_path=None): """检查数字动作是否刚刚被按下(上升沿)""" if not self.vr_input or action_name not in self.action_handles: return False, None try: action_handle = self.action_handles[action_name] device_handle = openvr.k_ulInvalidInputValueHandle if device_path: device_handle = self.vr_input.getInputSourceHandle(device_path) action_data = self.vr_input.getDigitalActionData(action_handle, device_handle) if device_path and action_data.bActive: origin_info = self.vr_input.getOriginTrackedDeviceInfo(action_data.activeOrigin) device_path = origin_info.devicePath return action_data.bActive and action_data.bChanged and action_data.bState, device_path except Exception as e: return False, None def is_digital_action_just_released(self, action_name, device_path=None): """检查数字动作是否刚刚被释放(下降沿)""" if not self.vr_input or action_name not in self.action_handles: return False, None try: action_handle = self.action_handles[action_name] device_handle = openvr.k_ulInvalidInputValueHandle if device_path: device_handle = self.vr_input.getInputSourceHandle(device_path) action_data = self.vr_input.getDigitalActionData(action_handle, device_handle) if device_path and action_data.bActive: origin_info = self.vr_input.getOriginTrackedDeviceInfo(action_data.activeOrigin) device_path = origin_info.devicePath return action_data.bActive and action_data.bChanged and not action_data.bState, device_path except Exception as e: return False, None def get_analog_action_value(self, action_name, device_path=None): """获取模拟动作值 Args: action_name: 动作名称 device_path: 设备路径(可选) Returns: tuple: (值, 设备路径) - 值为Vec2(x,y)对于vector2,float对于vector1 """ if not self.vr_input or action_name not in self.action_handles: return None, None try: action_handle = self.action_handles[action_name] device_handle = openvr.k_ulInvalidInputValueHandle if device_path: device_handle = self.vr_input.getInputSourceHandle(device_path) analog_data = self.vr_input.getAnalogActionData(action_handle, device_handle) if device_path and analog_data.bActive: origin_info = self.vr_input.getOriginTrackedDeviceInfo(analog_data.activeOrigin) device_path = origin_info.devicePath if analog_data.bActive: # 根据动作类型返回适当的值 from panda3d.core import Vec2 if action_name in ['trackpad', 'joystick']: return Vec2(analog_data.x, analog_data.y), device_path else: return analog_data.x, device_path return None, device_path except Exception as e: return None, None def get_pose_action_data(self, action_name, device_path=None): """获取姿态动作数据""" if not self.vr_input or action_name not in self.action_handles: return None try: action_handle = self.action_handles[action_name] device_handle = openvr.k_ulInvalidInputValueHandle if device_path: device_handle = self.vr_input.getInputSourceHandle(device_path) pose_data = self.vr_input.getPoseActionDataForNextFrame( action_handle, openvr.TrackingUniverseStanding, device_handle ) return pose_data except Exception as e: return None def trigger_haptic_pulse(self, action_name, duration=0.001, frequency=1.0, amplitude=1.0, device_path=None): """触发震动反馈 Args: action_name: 震动动作名称 duration: 持续时间(秒) frequency: 频率 amplitude: 振幅 (0.0-1.0) device_path: 设备路径(可选) """ if not self.vr_input or action_name not in self.action_handles: return False try: action_handle = self.action_handles[action_name] device_handle = openvr.k_ulInvalidInputValueHandle if device_path: device_handle = self.vr_input.getInputSourceHandle(device_path) # 触发震动 self.vr_input.triggerHapticVibrationAction( action_handle, 0, # 开始时间 duration, frequency, amplitude, device_handle ) return True except Exception as e: print(f"⚠️ 触发震动反馈失败: {e}") return False def cleanup(self): """清理资源""" self.ignoreAll() # 清理动作句柄 self.action_handles.clear() self.action_set_handles.clear() print("🧹 VR动作管理器已清理")