From a16c4bba13102f228ad34101883eb439f5d35441 Mon Sep 17 00:00:00 2001 From: Rowland <975945824@qq.com> Date: Mon, 29 Sep 2025 10:52:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=8B=E6=9F=84=E4=BA=92=E5=8A=A8=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/vr_controller.py | 164 ++++++++- core/vr_joystick.py | 701 +++++++++++++++++++++++++++++++++++++ core/vr_joystick_config.py | 268 ++++++++++++++ core/vr_manager.py | 175 ++++++++- core/vr_teleport.py | 411 ++++++++++++++++++++++ core/vr_visualization.py | 17 +- 6 files changed, 1708 insertions(+), 28 deletions(-) create mode 100644 core/vr_joystick.py create mode 100644 core/vr_joystick_config.py create mode 100644 core/vr_teleport.py diff --git a/core/vr_controller.py b/core/vr_controller.py index f9e5fd64..e293d07f 100644 --- a/core/vr_controller.py +++ b/core/vr_controller.py @@ -60,6 +60,12 @@ class VRController(DirectObject): self.touchpad_pos = Vec3(0, 0, 0) self.touchpad_touched = False + # 摇杆状态 - 用于传送和转向交互 + self.joystick_pos = Vec3(0, 0, 0) # 摇杆位置 (x, y, 0) + self.joystick_touched = False # 摇杆是否被触摸 + self.joystick_pressed = False # 摇杆是否被按下 + self.previous_joystick_pos = Vec3(0, 0, 0) # 上一帧摇杆位置 + # 3D节点和可视化 self.anchor_node = None self.visualizer = None @@ -146,8 +152,9 @@ class VRController(DirectObject): if not self.is_connected or not OPENVR_AVAILABLE or not vr_system: return - # 保存上一帧的按钮状态 + # 保存上一帧的按钮状态和摇杆位置 self.previous_button_states = self.button_states.copy() + self.previous_joystick_pos = Vec3(self.joystick_pos) # 获取控制器状态 try: @@ -159,20 +166,71 @@ class VRController(DirectObject): # 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: + # 更新轴状态(扳机、握把、触摸板、摇杆) + # 兼容不同版本的OpenVR Python绑定 + axis_data = None + if hasattr(state, 'rAxis'): + axis_data = state.rAxis # 旧版本使用rAxis + elif hasattr(state, 'vAxis'): + axis_data = state.vAxis # 新版本使用vAxis + + if axis_data is not None and len(axis_data) > 0: + # 调试输出 - 显示所有轴数据(仅当有变化时) + self._debug_axis_data(axis_data) + # 扳机轴通常在axis[1].x - if len(state.vAxis) > 1: - self.trigger_value = state.vAxis[1].x + if len(axis_data) > 1: + self.trigger_value = axis_data[1].x - # 触摸板轴通常在axis[0] - if len(state.vAxis) > 0: - self.touchpad_pos = Vec3(state.vAxis[0].x, state.vAxis[0].y, 0) + # 触摸板/摇杆轴通常在axis[0] + if len(axis_data) > 0: + self.touchpad_pos = Vec3(axis_data[0].x, axis_data[0].y, 0) + # 摇杆和触摸板通常使用同一个轴,但可以区分设备类型 + self.joystick_pos = Vec3(axis_data[0].x, axis_data[0].y, 0) - # 触摸板触摸状态 - 使用正确的OpenVR属性名 + # 额外检查其他轴(某些控制器可能将摇杆分配到不同轴) + if len(axis_data) > 2: + # 有些控制器可能在axis[2]有摇杆数据 + axis2_magnitude = abs(axis_data[2].x) + abs(axis_data[2].y) + if axis2_magnitude > 0.1: # 如果有显著输入,使用这个轴作为摇杆 + self.joystick_pos = Vec3(axis_data[2].x, axis_data[2].y, 0) + + # 检查axis[3]和axis[4](Quest控制器可能使用这些轴) + for axis_idx in range(3, min(len(axis_data), 5)): + axis_magnitude = abs(axis_data[axis_idx].x) + abs(axis_data[axis_idx].y) + if axis_magnitude > 0.1: # 如果有显著输入 + # 覆盖之前的摇杆数据,使用最有活动的轴 + self.joystick_pos = Vec3(axis_data[axis_idx].x, axis_data[axis_idx].y, 0) + # 调试输出 + if not hasattr(self, '_last_axis_notify'): + self._last_axis_notify = {} + if self._last_axis_notify.get(axis_idx, 0) == 0: + print(f"🎮 {self.name}手检测到axis[{axis_idx}]活动: ({axis_data[axis_idx].x:.3f}, {axis_data[axis_idx].y:.3f})") + self._last_axis_notify[axis_idx] = 60 # 60帧后再次提醒 + else: + self._last_axis_notify[axis_idx] -= 1 + + # 触摸板和摇杆触摸状态 self.touchpad_touched = (state.ulButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 0 + # 摇杆触摸状态(检查多个可能的按钮) + joystick_touch_mask = 0 + if hasattr(openvr, 'k_EButton_Joystick'): + joystick_touch_mask |= (1 << openvr.k_EButton_Joystick) + if hasattr(openvr, 'k_EButton_Thumbstick'): + joystick_touch_mask |= (1 << openvr.k_EButton_Thumbstick) + + self.joystick_touched = (state.ulButtonTouched & joystick_touch_mask) != 0 or self.touchpad_touched + + # 摇杆按下状态 + joystick_press_mask = 0 + if hasattr(openvr, 'k_EButton_Joystick'): + joystick_press_mask |= (1 << openvr.k_EButton_Joystick) + if hasattr(openvr, 'k_EButton_Thumbstick'): + joystick_press_mask |= (1 << openvr.k_EButton_Thumbstick) + + self.joystick_pressed = (state.ulButtonPressed & joystick_press_mask) != 0 + except Exception as e: # 减少错误输出频率 if not hasattr(self, '_last_input_error_frame'): @@ -260,12 +318,92 @@ class VRController(DirectObject): return Vec3(0, 0, 0) def get_forward_direction(self): - """获取手柄指向的方向向量""" + """获取手柄指向的方向向量(包含视角转向)""" if self.anchor_node: - # Y轴正方向为前方 - return self.anchor_node.getMat().getRow3(1).getXyz().normalized() + # 获取相对于世界坐标系的方向,包含tracking_space的旋转 + if hasattr(self.vr_manager, 'world') and self.vr_manager.world: + # 使用世界变换,包含所有父节点的旋转 + world_transform = self.anchor_node.getMat(self.vr_manager.world.render) + forward = Vec3(world_transform.getRow3(1)) # Y轴 = 前方 + else: + # 备选:使用局部变换 + forward = Vec3(self.anchor_node.getMat().getRow3(1)) + + if forward.length() > 0: + return forward.normalized() return Vec3(0, 1, 0) + def is_joystick_touched(self): + """检查摇杆是否被触摸""" + return self.joystick_touched + + def is_joystick_pressed(self): + """检查摇杆是否被按下""" + return self.joystick_pressed + + def get_joystick_position(self): + """获取摇杆位置 + + Returns: + Vec3: 摇杆位置 (x, y, 0),范围 [-1, 1] + """ + return Vec3(self.joystick_pos) + + def get_joystick_delta(self): + """获取摇杆位置变化 + + Returns: + Vec3: 摇杆位置变化向量 + """ + return self.joystick_pos - self.previous_joystick_pos + + def is_joystick_moved(self, threshold=0.01): + """检查摇杆是否移动 + + Args: + threshold: 移动阈值 + + Returns: + bool: 是否移动 + """ + delta = self.get_joystick_delta() + return delta.length() > threshold + + def _debug_axis_data(self, axis_data): + """调试输出轴数据""" + try: + # 只在有活动时输出调试信息 + has_activity = False + active_axes = [] + + for i, axis in enumerate(axis_data): + magnitude = abs(axis.x) + abs(axis.y) + if magnitude > 0.01: # 检测到活动 + has_activity = True + active_axes.append(f"axis[{i}]: ({axis.x:.3f}, {axis.y:.3f})") + + if has_activity: + # 初始化调试计数器 + if not hasattr(self, '_debug_axis_counter'): + self._debug_axis_counter = 0 + + self._debug_axis_counter += 1 + + # 每30帧输出一次详细信息 + if self._debug_axis_counter % 30 == 1: + print(f"🔍 {self.name}手轴数据调试:") + print(f" 总轴数: {len(axis_data)}") + print(f" 活跃轴: {', '.join(active_axes)}") + + # 显示所有轴的当前值(不管是否活跃) + all_axes = [] + for i, axis in enumerate(axis_data): + all_axes.append(f"[{i}]:({axis.x:.3f},{axis.y:.3f})") + print(f" 所有轴: {' '.join(all_axes)}") + + except Exception as e: + print(f"⚠️ 轴数据调试失败: {e}") + def cleanup(self): """清理资源""" self.ignoreAll() diff --git a/core/vr_joystick.py b/core/vr_joystick.py new file mode 100644 index 00000000..feaa1cae --- /dev/null +++ b/core/vr_joystick.py @@ -0,0 +1,701 @@ +""" +VR摇杆交互系统模块 + +提供类似SteamVR的摇杆交互功能: +- 摇杆左右转向(旋转视角) +- 摇杆向前传送预览(抛物线轨迹) +- 松开摇杆执行传送 +- 死区处理和平滑控制 +""" + +import math +from panda3d.core import Vec2, Vec3, Vec4 +from direct.showbase.DirectObject import DirectObject +try: + import openvr + OPENVR_AVAILABLE = True +except ImportError: + OPENVR_AVAILABLE = False + + +class VRJoystickManager(DirectObject): + """VR摇杆管理器 - 处理手柄摇杆的转向和传送功能""" + + def __init__(self, vr_manager): + """初始化VR摇杆管理器 + + Args: + vr_manager: VR管理器实例 + """ + super().__init__() + + self.vr_manager = vr_manager + self.teleport_system = None # 传送系统引用,稍后初始化 + + # 摇杆参数配置 + self.deadzone = 0.15 # 摇杆死区 (0-1) + self.turn_threshold = 0.3 # 转向激活阈值 + self.teleport_threshold = 0.5 # 传送激活阈值 + self.turn_sensitivity = 250.0 # 转向灵敏度(度/秒)- 增加速度 + self.smooth_turning = True # 是否平滑转向 + self.snap_turn_angle = 30.0 # 分段转向角度(度) + + # 摇杆状态跟踪 + self.left_joystick_state = JoystickState() + self.right_joystick_state = JoystickState() + + # 转向状态 + self.left_turn_cooldown = 0.0 # 分段转向冷却时间 + self.right_turn_cooldown = 0.0 + self.snap_turn_cooldown = 0.3 # 分段转向间隔(秒) + + # 传送状态 + self.active_teleport_controller = None # 正在传送的控制器 + self.teleport_preview_active = False # 传送预览是否激活 + + # 互斥状态管理 - 防止同时触发多种操作 + self.interaction_mode = 'none' # 当前交互模式: 'none', 'turning', 'teleporting' + self.left_controller_mode = 'none' # 左手控制器状态 + self.right_controller_mode = 'none' # 右手控制器状态 + self.mode_lock_timeout = 0.1 # 模式锁定超时时间(秒) + self.left_mode_timer = 0.0 # 左手模式计时器 + self.right_mode_timer = 0.0 # 右手模式计时器 + + print("✓ VR摇杆管理器初始化完成") + + def initialize(self, teleport_system): + """初始化摇杆系统 + + Args: + teleport_system: VR传送系统实例 + """ + self.teleport_system = teleport_system + print("✅ VR摇杆系统初始化成功") + + def update(self, dt): + """更新摇杆系统 - 每帧调用 + + Args: + dt: 帧间隔时间(秒) + """ + if not self.vr_manager.are_controllers_connected(): + return + + # 调试计数器 + if not hasattr(self, '_debug_frame_count'): + self._debug_frame_count = 0 + print("🎮 VR摇杆系统开始更新") + + self._debug_frame_count += 1 + + # 更新转向冷却时间 + if self.left_turn_cooldown > 0: + self.left_turn_cooldown -= dt + if self.right_turn_cooldown > 0: + self.right_turn_cooldown -= dt + + # 更新互斥状态计时器 + self._update_interaction_modes(dt) + + # 处理左手控制器摇杆 + if self.vr_manager.left_controller: + self._update_controller_joystick( + self.vr_manager.left_controller, + self.left_joystick_state, + 'left', + dt + ) + + # 处理右手控制器摇杆 + if self.vr_manager.right_controller: + self._update_controller_joystick( + self.vr_manager.right_controller, + self.right_joystick_state, + 'right', + dt + ) + + # 每5秒输出一次状态报告 + if self._debug_frame_count % 300 == 1: # 假设60fps + self._print_debug_status() + + def _update_controller_joystick(self, controller, joystick_state, hand, dt): + """更新单个控制器的摇杆状态 + + Args: + controller: VR控制器实例 + joystick_state: 摇杆状态对象 + hand: 'left' 或 'right' + dt: 帧间隔时间 + """ + # 获取摇杆输入 + joystick_input = self._get_joystick_input(controller, hand) + if joystick_input is None: + return + + # 应用死区 + filtered_input = self._apply_deadzone(joystick_input) + + # 更新摇杆状态 + joystick_state.update(filtered_input) + + # 处理转向(左右移动) + self._handle_turning(filtered_input, hand, dt) + + # 处理传送(向前移动) + self._handle_teleport(controller, filtered_input, joystick_state, hand) + + def _update_interaction_modes(self, dt): + """更新互斥交互模式状态""" + # 更新左手模式计时器 + if self.left_mode_timer > 0: + self.left_mode_timer -= dt + if self.left_mode_timer <= 0: + self.left_controller_mode = 'none' + + # 更新右手模式计时器 + if self.right_mode_timer > 0: + self.right_mode_timer -= dt + if self.right_mode_timer <= 0: + self.right_controller_mode = 'none' + + # 更新全局交互模式 + if self.left_controller_mode == 'none' and self.right_controller_mode == 'none': + if self.interaction_mode != 'none': + # 所有操作结束,恢复自由模式 + self.interaction_mode = 'none' + print("🔓 摇杆交互模式解锁,恢复自由操作") + + def _set_controller_mode(self, hand, mode): + """设置控制器交互模式 + + Args: + hand: 'left' 或 'right' + mode: 'turning', 'teleporting', 'none' + """ + if hand == 'left': + if self.left_controller_mode != mode: + old_mode = self.left_controller_mode + self.left_controller_mode = mode + self.left_mode_timer = self.mode_lock_timeout + if mode != 'none': + print(f"🔒 左手控制器: {old_mode} → {mode}模式") + else: + print(f"🔓 左手控制器解锁: {old_mode} → 自由") + else: + if self.right_controller_mode != mode: + old_mode = self.right_controller_mode + self.right_controller_mode = mode + self.right_mode_timer = self.mode_lock_timeout + if mode != 'none': + print(f"🔒 右手控制器: {old_mode} → {mode}模式") + else: + print(f"🔓 右手控制器解锁: {old_mode} → 自由") + + # 更新全局模式 + if mode != 'none' and self.interaction_mode != mode: + self.interaction_mode = mode + + def _can_use_mode(self, hand, requested_mode): + """检查是否可以使用指定的交互模式 + + Args: + hand: 'left' 或 'right' + requested_mode: 'turning' 或 'teleporting' + + Returns: + bool: 是否可以使用该模式 + """ + current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode + + # 如果当前控制器已经是该模式,允许继续 + if current_mode == requested_mode: + return True + + # 如果当前控制器是空闲的,且全局模式兼容,允许切换 + if current_mode == 'none': + if self.interaction_mode == 'none' or self.interaction_mode == requested_mode: + return True + + # 其他情况不允许 + return False + + def _get_joystick_input(self, controller, hand): + """获取摇杆输入 + + Args: + controller: VR控制器实例 + hand: 'left' 或 'right' + + Returns: + Vec2: 摇杆位置 (x, y) 或 None + """ + # 直接从控制器读取摇杆输入(绕过动作系统) + joystick_input = Vec2(0, 0) + + # 优先读取joystick_pos + if hasattr(controller, 'joystick_pos') and controller.joystick_pos: + joystick_input = Vec2(controller.joystick_pos.x, controller.joystick_pos.y) + # 检查是否有有效输入 + if joystick_input.length() > 0.01: + # 调试输出 - 仅在有输入时显示 + if not hasattr(self, '_last_debug_time'): + self._last_debug_time = 0 + self._debug_counter = 0 + + self._debug_counter += 1 + # 每30帧输出一次调试信息 + if self._debug_counter % 30 == 1: + print(f"🎮 {hand}手摇杆输入: ({joystick_input.x:.3f}, {joystick_input.y:.3f})") + + return joystick_input + + # 备选方案:读取touchpad_pos(Quest等设备) + if hasattr(controller, 'touchpad_pos') and controller.touchpad_pos: + touchpad_input = Vec2(controller.touchpad_pos.x, controller.touchpad_pos.y) + # 检查是否有有效输入 + if touchpad_input.length() > 0.01: + # 调试输出 - 仅在有输入时显示 + if not hasattr(self, '_last_debug_time'): + self._last_debug_time = 0 + self._debug_counter = 0 + + self._debug_counter += 1 + # 每30帧输出一次调试信息 + if self._debug_counter % 30 == 1: + print(f"🎮 {hand}手触摸板输入: ({touchpad_input.x:.3f}, {touchpad_input.y:.3f})") + + return touchpad_input + + # 可选:尝试从动作系统获取(如果可用) + if (self.vr_manager.action_manager and + hasattr(self.vr_manager.action_manager, 'get_analog_action_value')): + try: + device_path = f'/user/hand/{hand}' + + # 尝试摇杆 + joystick_value, _ = self.vr_manager.action_manager.get_analog_action_value('joystick', device_path) + if joystick_value is not None: + return Vec2(joystick_value.x, joystick_value.y) + + # 尝试触摸板 + trackpad_value, _ = self.vr_manager.action_manager.get_analog_action_value('trackpad', device_path) + if trackpad_value is not None: + return Vec2(trackpad_value.x, trackpad_value.y) + except Exception: + # 静默忽略动作系统错误 + pass + + return Vec2(0, 0) + + def _apply_deadzone(self, input_vec): + """应用摇杆死区 + + Args: + input_vec: 原始摇杆输入 + + Returns: + Vec2: 应用死区后的输入 + """ + magnitude = input_vec.length() + + if magnitude < self.deadzone: + return Vec2(0, 0) + + # 重新映射到 [0, 1] 范围 + normalized_magnitude = (magnitude - self.deadzone) / (1.0 - self.deadzone) + normalized_magnitude = min(normalized_magnitude, 1.0) + + if magnitude > 0: + direction = input_vec / magnitude + return direction * normalized_magnitude + + return Vec2(0, 0) + + def _handle_turning(self, input_vec, hand, dt): + """处理摇杆转向 + + Args: + input_vec: 摇杆输入向量 + hand: 'left' 或 'right' + dt: 帧间隔时间 + """ + # 检查是否超过转向阈值 + if abs(input_vec.x) < self.turn_threshold: + # 没有转向输入,重置该控制器的转向模式 + current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode + if current_mode == 'turning': + self._set_controller_mode(hand, 'none') + return + + # 检查是否可以使用转向模式 + if not self._can_use_mode(hand, 'turning'): + # 当前控制器被传送锁定,忽略转向输入 + current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode + if current_mode != 'turning': + print(f"⛔ {hand}手转向被阻止 - 当前模式: {current_mode}") + return + + # 激活转向模式 + self._set_controller_mode(hand, 'turning') + + # 检查冷却时间(分段转向) + cooldown = self.left_turn_cooldown if hand == 'left' else self.right_turn_cooldown + + if self.smooth_turning: + # 平滑转向 - 反转方向:摇杆右移(+x)应该向右转(-angle) + turn_amount = -input_vec.x * self.turn_sensitivity * dt + self._apply_rotation(turn_amount) + + # 调试输出转向 + if not hasattr(self, '_turn_debug_counter'): + self._turn_debug_counter = 0 + self._turn_debug_counter += 1 + if self._turn_debug_counter % 60 == 1: # 每秒输出一次 + print(f"🔄 {hand}手转向: 输入={input_vec.x:.3f}, 角度={turn_amount:.1f}°") + else: + # 分段转向 + if cooldown <= 0: + # 反转方向:摇杆右移应该向右转 + turn_amount = -self.snap_turn_angle if input_vec.x > 0 else self.snap_turn_angle + self._apply_rotation(turn_amount) + + print(f"🔄 {hand}手分段转向: 输入={input_vec.x:.3f}, 角度={turn_amount:.1f}°") + + # 设置冷却时间 + if hand == 'left': + self.left_turn_cooldown = self.snap_turn_cooldown + else: + self.right_turn_cooldown = self.snap_turn_cooldown + + def _apply_rotation(self, angle_degrees): + """应用旋转到VR跟踪空间 + + Args: + angle_degrees: 旋转角度(度) + """ + if not self.vr_manager.tracking_space: + return + + # 绕Z轴旋转(垂直轴) + current_h = self.vr_manager.tracking_space.getH() + new_h = current_h + angle_degrees + self.vr_manager.tracking_space.setH(new_h) + + def _handle_teleport(self, controller, input_vec, joystick_state, hand): + """处理摇杆传送 + + Args: + controller: VR控制器实例 + input_vec: 摇杆输入向量 + joystick_state: 摇杆状态对象 + hand: 'left' 或 'right' + """ + # 检查Y轴向前移动是否超过阈值 + forward_input = input_vec.y + + if forward_input > self.teleport_threshold: + # 检查是否可以使用传送模式 + if not self._can_use_mode(hand, 'teleporting'): + # 当前控制器被转向锁定,忽略传送输入 + current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode + if current_mode != 'teleporting': + print(f"⛔ {hand}手传送被阻止 - 当前模式: {current_mode}") + return + + # 激活传送模式 + self._set_controller_mode(hand, 'teleporting') + + # 开始或更新传送预览 + if not joystick_state.teleport_active: + joystick_state.teleport_active = True + self.active_teleport_controller = controller + self.teleport_preview_active = True + + # 触发震动反馈 + controller.trigger_haptic_feedback(0.002, 0.3) + + # 计算传送方向 + direction = self._calculate_teleport_direction(controller, input_vec) + + # 更新传送预览 + if self.teleport_system: + if joystick_state.teleport_just_started: + self.teleport_system.start_teleport_preview(controller, direction) + joystick_state.teleport_just_started = False + else: + self.teleport_system.update_teleport_preview(controller, direction) + + else: + # 检查是否需要执行传送 + if joystick_state.teleport_active: + # 松开摇杆,执行传送 + self._execute_teleport(controller) + joystick_state.teleport_active = False + joystick_state.teleport_just_started = True + + # 重置该控制器的传送模式 + self._set_controller_mode(hand, 'none') + else: + # 没有传送输入,检查是否需要重置传送模式 + current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode + if current_mode == 'teleporting': + self._set_controller_mode(hand, 'none') + + def _calculate_teleport_direction(self, controller, input_vec): + """计算传送方向向量 - 基于手柄姿态 + + Args: + controller: VR控制器实例 + input_vec: 摇杆输入向量(仅用于激活,不影响方向) + + Returns: + Vec3: 世界坐标中的传送方向 + """ + # 方法1:直接使用手柄指向(推荐) + if controller and hasattr(controller, 'get_forward_direction'): + try: + # 获取手柄的前向方向 + controller_forward = controller.get_forward_direction() + + # 确保方向向量在水平面上 + horizontal_direction = Vec3(controller_forward.x, controller_forward.y, 0) + + if horizontal_direction.length() > 0: + horizontal_direction.normalize() + + # 调试输出(每30帧输出一次) + if not hasattr(self, '_direction_debug_counter'): + self._direction_debug_counter = 0 + + self._direction_debug_counter += 1 + if self._direction_debug_counter % 30 == 1: + # 获取当前tracking_space的旋转角度用于调试 + tracking_rotation = 0 + if self.vr_manager.tracking_space: + tracking_rotation = self.vr_manager.tracking_space.getH() + + print(f"🎯 综合传送方向: 视角旋转({tracking_rotation:.1f}°) + 手柄姿态({controller_forward.x:.2f},{controller_forward.y:.2f},{controller_forward.z:.2f}) → 最终方向({horizontal_direction.x:.2f},{horizontal_direction.y:.2f})") + + return horizontal_direction + + except Exception as e: + print(f"⚠️ 获取手柄方向失败: {e}") + + # 备选方案:使用玩家朝向(保留原逻辑作为备选) + print("🔄 使用备选传送方向计算...") + + # 获取玩家当前朝向的变换矩阵 + player_transform = None + + # 优先使用头显的世界变换 + if self.vr_manager.hmd_anchor and hasattr(self.vr_manager, 'world') and self.vr_manager.world: + player_transform = self.vr_manager.hmd_anchor.getMat(self.vr_manager.world.render) + # 备选:使用tracking_space的世界变换 + elif self.vr_manager.tracking_space and hasattr(self.vr_manager, 'world') and self.vr_manager.world: + player_transform = self.vr_manager.tracking_space.getMat(self.vr_manager.world.render) + + if player_transform: + # 从变换矩阵提取前向向量 + forward = Vec3(player_transform.getRow3(1)) # Y轴 = 前向 + + # 确保向量在水平面上 + forward.z = 0 + if forward.length() > 0: + forward.normalize() + return forward + + # 最终备选方案:默认前向 + print("⚠️ 无法获取任何朝向,使用默认方向") + return Vec3(0, 1, 0) + + def _execute_teleport(self, controller): + """执行传送 + + Args: + controller: VR控制器实例 + """ + if self.teleport_system and self.teleport_preview_active: + success = self.teleport_system.execute_teleport() + + if success: + # 传送成功,触发震动反馈 + controller.trigger_haptic_feedback(0.005, 0.8) + else: + # 传送失败,触发不同的震动反馈 + controller.trigger_haptic_feedback(0.001, 0.2) + + # 停止传送预览 + self.teleport_system.stop_teleport_preview() + self.teleport_preview_active = False + self.active_teleport_controller = None + + def set_turning_mode(self, smooth=True): + """设置转向模式 + + Args: + smooth: True为平滑转向,False为分段转向 + """ + self.smooth_turning = smooth + print(f"✓ 转向模式设置为: {'平滑转向' if smooth else '分段转向'}") + + def set_turn_sensitivity(self, sensitivity): + """设置转向灵敏度 + + Args: + sensitivity: 转向灵敏度(度/秒) + """ + self.turn_sensitivity = max(10.0, min(180.0, sensitivity)) + print(f"✓ 转向灵敏度设置为: {self.turn_sensitivity}度/秒") + + def apply_config(self, config): + """应用配置 + + Args: + config: VRJoystickConfig配置实例 + """ + try: + self.deadzone = config.deadzone + self.turn_threshold = config.turn_threshold + self.teleport_threshold = config.teleport_threshold + self.turn_sensitivity = config.turn_sensitivity + self.smooth_turning = (config.turn_mode.value == 'smooth') + self.snap_turn_angle = config.snap_turn_angle + self.snap_turn_cooldown = config.snap_turn_cooldown + + # 应用传送系统配置 + if self.teleport_system and hasattr(config, 'teleport_range'): + self.teleport_system.teleport_range = config.teleport_range + if hasattr(config, 'teleport_arc_resolution'): + self.teleport_system.arc_resolution = config.teleport_arc_resolution + if hasattr(config, 'teleport_initial_velocity'): + self.teleport_system.initial_velocity = config.teleport_initial_velocity + if hasattr(config, 'min_teleport_distance'): + self.teleport_system.min_teleport_distance = config.min_teleport_distance + + print("✅ 摇杆配置已成功应用") + + except Exception as e: + print(f"⚠️ 应用配置失败: {e}") + + def get_current_config(self): + """获取当前配置 + + Returns: + dict: 当前配置参数 + """ + return { + 'deadzone': self.deadzone, + 'turn_threshold': self.turn_threshold, + 'teleport_threshold': self.teleport_threshold, + 'turn_sensitivity': self.turn_sensitivity, + 'smooth_turning': self.smooth_turning, + 'snap_turn_angle': self.snap_turn_angle, + 'snap_turn_cooldown': self.snap_turn_cooldown + } + + def _print_debug_status(self): + """打印调试状态信息""" + try: + print("🔍 ======= VR摇杆调试状态 =======") + + # 控制器连接状态 + left_connected = self.vr_manager.left_controller is not None + right_connected = self.vr_manager.right_controller is not None + print(f"📱 控制器状态: 左手={left_connected}, 右手={right_connected}") + + # 检查控制器属性 + if left_connected: + left_ctrl = self.vr_manager.left_controller + has_joystick = hasattr(left_ctrl, 'joystick_pos') + has_touchpad = hasattr(left_ctrl, 'touchpad_pos') + print(f"📊 左手控制器: joystick_pos={has_joystick}, touchpad_pos={has_touchpad}") + + if has_joystick and left_ctrl.joystick_pos: + pos = left_ctrl.joystick_pos + print(f" 当前摇杆位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})") + + if has_touchpad and left_ctrl.touchpad_pos: + pos = left_ctrl.touchpad_pos + print(f" 当前触摸板位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})") + + if right_connected: + right_ctrl = self.vr_manager.right_controller + has_joystick = hasattr(right_ctrl, 'joystick_pos') + has_touchpad = hasattr(right_ctrl, 'touchpad_pos') + print(f"📊 右手控制器: joystick_pos={has_joystick}, touchpad_pos={has_touchpad}") + + if has_joystick and right_ctrl.joystick_pos: + pos = right_ctrl.joystick_pos + print(f" 当前摇杆位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})") + + if has_touchpad and right_ctrl.touchpad_pos: + pos = right_ctrl.touchpad_pos + print(f" 当前触摸板位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})") + + # 摇杆配置 + print(f"⚙️ 摇杆配置:") + print(f" 死区: {self.deadzone}") + print(f" 转向阈值: {self.turn_threshold}") + print(f" 传送阈值: {self.teleport_threshold}") + print(f" 转向模式: {'平滑' if self.smooth_turning else '分段'}") + + # 动作系统状态 + action_mgr_available = (self.vr_manager.action_manager is not None) + print(f"🎯 动作系统: {'可用' if action_mgr_available else '不可用'}") + + # 传送系统状态 + teleport_available = (self.teleport_system is not None) + print(f"🚀 传送系统: {'可用' if teleport_available else '不可用'}") + + print("🔍 ==============================") + + except Exception as e: + print(f"⚠️ 调试状态输出失败: {e}") + + def cleanup(self): + """清理摇杆系统资源""" + try: + # 停止任何活跃的传送预览 + if self.teleport_preview_active and self.teleport_system: + self.teleport_system.stop_teleport_preview() + + self.teleport_preview_active = False + self.active_teleport_controller = None + + self.ignoreAll() + print("🧹 VR摇杆系统已清理") + + except Exception as e: + print(f"⚠️ 清理摇杆系统失败: {e}") + + +class JoystickState: + """摇杆状态跟踪类""" + + def __init__(self): + self.current_input = Vec2(0, 0) # 当前摇杆输入 + self.previous_input = Vec2(0, 0) # 上一帧摇杆输入 + self.teleport_active = False # 传送是否激活 + self.teleport_just_started = True # 传送是否刚开始 + + def update(self, new_input): + """更新摇杆状态 + + Args: + new_input: 新的摇杆输入 + """ + self.previous_input = Vec2(self.current_input) + self.current_input = Vec2(new_input) + + def is_input_changed(self, threshold=0.01): + """检查输入是否发生变化 + + Args: + threshold: 变化阈值 + + Returns: + bool: 是否发生变化 + """ + diff = self.current_input - self.previous_input + return diff.length() > threshold \ No newline at end of file diff --git a/core/vr_joystick_config.py b/core/vr_joystick_config.py new file mode 100644 index 00000000..13401e59 --- /dev/null +++ b/core/vr_joystick_config.py @@ -0,0 +1,268 @@ +""" +VR摇杆配置模块 + +提供摇杆交互的配置选项和预设: +- 不同的转向模式和灵敏度设置 +- 传送参数调整 +- 用户体验优化选项 +""" + +from enum import Enum + + +class TurnMode(Enum): + """转向模式枚举""" + SMOOTH = "smooth" # 平滑转向 + SNAP = "snap" # 分段转向 + + +class JoystickProfile(Enum): + """摇杆配置预设""" + COMFORTABLE = "comfortable" # 舒适模式 - 低灵敏度,平滑转向 + STANDARD = "standard" # 标准模式 - 中等灵敏度,分段转向 + GAMING = "gaming" # 游戏模式 - 高灵敏度,快速响应 + ACCESSIBLE = "accessible" # 无障碍模式 - 更大死区,更高阈值 + + +class VRJoystickConfig: + """VR摇杆配置类""" + + def __init__(self): + """初始化默认配置""" + # 基础参数 + self.deadzone = 0.15 # 摇杆死区 (0-1) + self.turn_threshold = 0.3 # 转向激活阈值 + self.teleport_threshold = 0.5 # 传送激活阈值 + + # 转向设置 + self.turn_mode = TurnMode.SNAP # 转向模式 + self.turn_sensitivity = 250.0 # 转向灵敏度(度/秒)- 增加速度 + self.snap_turn_angle = 30.0 # 分段转向角度(度) + self.snap_turn_cooldown = 0.3 # 分段转向间隔(秒) + + # 传送设置 + self.teleport_range = 20.0 # 最大传送距离 + self.teleport_arc_resolution = 50 # 抛物线精度 + self.teleport_initial_velocity = 10.0 # 传送初始速度 + self.min_teleport_distance = 1.0 # 最小传送距离 + + # 反馈设置 + self.haptic_feedback_enabled = True # 是否启用震动反馈 + self.teleport_start_haptic = (0.002, 0.3) # 传送开始震动 (时长, 强度) + self.teleport_success_haptic = (0.005, 0.8) # 传送成功震动 + self.teleport_fail_haptic = (0.001, 0.2) # 传送失败震动 + + # 可视化设置 + self.show_teleport_arc = True # 显示传送抛物线 + self.show_teleport_target = True # 显示传送目标标记 + self.arc_valid_color = (0.2, 0.9, 0.2, 0.8) # 有效抛物线颜色 + self.arc_invalid_color = (0.9, 0.2, 0.2, 0.8) # 无效抛物线颜色 + + def apply_profile(self, profile: JoystickProfile): + """应用预设配置 + + Args: + profile: 配置预设 + """ + if profile == JoystickProfile.COMFORTABLE: + self._apply_comfortable_profile() + elif profile == JoystickProfile.STANDARD: + self._apply_standard_profile() + elif profile == JoystickProfile.GAMING: + self._apply_gaming_profile() + elif profile == JoystickProfile.ACCESSIBLE: + self._apply_accessible_profile() + + def _apply_comfortable_profile(self): + """舒适模式 - 适合长时间使用""" + self.deadzone = 0.2 + self.turn_threshold = 0.4 + self.teleport_threshold = 0.6 + self.turn_mode = TurnMode.SMOOTH + self.turn_sensitivity = 100.0 # 适中的转向速度 + self.snap_turn_cooldown = 0.4 + print("✓ 已应用舒适模式配置") + + def _apply_standard_profile(self): + """标准模式 - 平衡的体验""" + self.deadzone = 0.15 + self.turn_threshold = 0.3 + self.teleport_threshold = 0.5 + self.turn_mode = TurnMode.SNAP + self.turn_sensitivity = 150.0 # 更快的转向速度 + self.snap_turn_angle = 30.0 + self.snap_turn_cooldown = 0.3 + print("✓ 已应用标准模式配置") + + def _apply_gaming_profile(self): + """游戏模式 - 快速响应""" + self.deadzone = 0.1 + self.turn_threshold = 0.2 + self.teleport_threshold = 0.4 + self.turn_mode = TurnMode.SMOOTH + self.turn_sensitivity = 200.0 # 高速转向 + self.snap_turn_cooldown = 0.2 + print("✓ 已应用游戏模式配置") + + def _apply_accessible_profile(self): + """无障碍模式 - 更容易控制""" + self.deadzone = 0.25 + self.turn_threshold = 0.5 + self.teleport_threshold = 0.7 + self.turn_mode = TurnMode.SNAP + self.turn_sensitivity = 80.0 # 较慢但可控的转向 + self.snap_turn_angle = 45.0 + self.snap_turn_cooldown = 0.5 + print("✓ 已应用无障碍模式配置") + + def set_turn_mode(self, mode: TurnMode): + """设置转向模式 + + Args: + mode: 转向模式 + """ + self.turn_mode = mode + print(f"✓ 转向模式设置为: {mode.value}") + + def set_turn_sensitivity(self, sensitivity: float): + """设置转向灵敏度 + + Args: + sensitivity: 转向灵敏度(度/秒) + """ + self.turn_sensitivity = max(10.0, min(180.0, sensitivity)) + print(f"✓ 转向灵敏度设置为: {self.turn_sensitivity}度/秒") + + def set_deadzone(self, deadzone: float): + """设置摇杆死区 + + Args: + deadzone: 死区大小 (0-1) + """ + self.deadzone = max(0.0, min(0.5, deadzone)) + print(f"✓ 摇杆死区设置为: {self.deadzone}") + + def set_teleport_range(self, range_meters: float): + """设置传送范围 + + Args: + range_meters: 传送范围(米) + """ + self.teleport_range = max(5.0, min(50.0, range_meters)) + print(f"✓ 传送范围设置为: {self.teleport_range}米") + + def enable_haptic_feedback(self, enabled: bool): + """启用或禁用震动反馈 + + Args: + enabled: 是否启用 + """ + self.haptic_feedback_enabled = enabled + print(f"✓ 震动反馈: {'启用' if enabled else '禁用'}") + + def get_config_dict(self): + """获取配置字典 + + Returns: + dict: 配置参数字典 + """ + return { + 'deadzone': self.deadzone, + 'turn_threshold': self.turn_threshold, + 'teleport_threshold': self.teleport_threshold, + 'turn_mode': self.turn_mode.value, + 'turn_sensitivity': self.turn_sensitivity, + 'snap_turn_angle': self.snap_turn_angle, + 'snap_turn_cooldown': self.snap_turn_cooldown, + 'teleport_range': self.teleport_range, + 'haptic_feedback_enabled': self.haptic_feedback_enabled + } + + def apply_to_joystick_manager(self, joystick_manager): + """将配置应用到摇杆管理器 + + Args: + joystick_manager: VRJoystickManager实例 + """ + try: + joystick_manager.deadzone = self.deadzone + joystick_manager.turn_threshold = self.turn_threshold + joystick_manager.teleport_threshold = self.teleport_threshold + joystick_manager.turn_sensitivity = self.turn_sensitivity + joystick_manager.smooth_turning = (self.turn_mode == TurnMode.SMOOTH) + joystick_manager.snap_turn_angle = self.snap_turn_angle + joystick_manager.snap_turn_cooldown = self.snap_turn_cooldown + + # 应用传送系统配置 + if hasattr(joystick_manager, 'teleport_system') and joystick_manager.teleport_system: + teleport_sys = joystick_manager.teleport_system + teleport_sys.teleport_range = self.teleport_range + teleport_sys.arc_resolution = self.teleport_arc_resolution + teleport_sys.initial_velocity = self.teleport_initial_velocity + teleport_sys.min_teleport_distance = self.min_teleport_distance + + print("✅ 配置已成功应用到摇杆管理器") + + except Exception as e: + print(f"⚠️ 应用配置失败: {e}") + + +# 预定义的配置实例 +COMFORTABLE_CONFIG = VRJoystickConfig() +COMFORTABLE_CONFIG.apply_profile(JoystickProfile.COMFORTABLE) + +STANDARD_CONFIG = VRJoystickConfig() +STANDARD_CONFIG.apply_profile(JoystickProfile.STANDARD) + +GAMING_CONFIG = VRJoystickConfig() +GAMING_CONFIG.apply_profile(JoystickProfile.GAMING) + +ACCESSIBLE_CONFIG = VRJoystickConfig() +ACCESSIBLE_CONFIG.apply_profile(JoystickProfile.ACCESSIBLE) + + +def create_custom_config(**kwargs): + """创建自定义配置 + + Args: + **kwargs: 配置参数 + + Returns: + VRJoystickConfig: 自定义配置实例 + """ + config = VRJoystickConfig() + + for key, value in kwargs.items(): + if hasattr(config, key): + setattr(config, key, value) + else: + print(f"⚠️ 未知的配置参数: {key}") + + return config + + +def print_usage_guide(): + """打印使用指南""" + print("🎮 ======= VR摇杆交互使用指南 =======") + print() + print("📋 基本操作:") + print(" • 摇杆左右移动 → 转向(旋转视角)") + print(" • 摇杆向前推 → 显示传送抛物线") + print(" • 松开摇杆 → 执行传送到落点") + print() + print("⚙️ 配置选项:") + print(" • 舒适模式 → 低灵敏度,适合长时间使用") + print(" • 标准模式 → 平衡体验,推荐大多数用户") + print(" • 游戏模式 → 高灵敏度,快速响应") + print(" • 无障碍模式 → 更大死区,容易控制") + print() + print("🎯 使用建议:") + print(" • 首次使用建议从标准模式开始") + print(" • 根据个人喜好调整转向模式(平滑/分段)") + print(" • 可以随时调整死区和灵敏度") + print(" • 传送范围可根据场景大小调整") + print() + + +if __name__ == "__main__": + print_usage_guide() \ No newline at end of file diff --git a/core/vr_manager.py b/core/vr_manager.py index 82929b7e..ab1bc036 100644 --- a/core/vr_manager.py +++ b/core/vr_manager.py @@ -28,10 +28,12 @@ except ImportError: OPENVR_AVAILABLE = False print("警告: OpenVR未安装,VR功能将不可用") -# 导入手柄控制器、动作系统和交互系统 +# 导入手柄控制器、动作系统、交互系统、摇杆系统和传送系统 from .vr_controller import LeftController, RightController from .vr_actions import VRActionManager from .vr_interaction import VRInteractionManager +from .vr_joystick import VRJoystickManager +from .vr_teleport import VRTeleportSystem class VRManager(DirectObject): @@ -236,13 +238,19 @@ class VRManager(DirectObject): self.controllers = {} # 设备索引到控制器的映射 self.tracked_device_anchors = {} # 跟踪设备锚点 - # VR动作系统 - 添加异常保护 - try: - self.action_manager = VRActionManager(self) - print("✓ VR动作管理器初始化完成") - except Exception as e: - print(f"⚠️ VR动作管理器初始化失败: {e}") + # VR动作系统 - 可选择禁用(用于API兼容性问题) + self.disable_action_system = True # 禁用动作系统,使用直接控制器输入 + + if not self.disable_action_system: + try: + self.action_manager = VRActionManager(self) + print("✓ VR动作管理器初始化完成") + except Exception as e: + print(f"⚠️ VR动作管理器初始化失败: {e}") + self.action_manager = None + else: self.action_manager = None + print("🚫 VR动作系统已禁用,使用直接控制器输入") # VR交互系统 - 添加异常保护 try: @@ -252,6 +260,22 @@ class VRManager(DirectObject): print(f"⚠️ VR交互管理器初始化失败: {e}") self.interaction_manager = None + # VR传送系统 - 添加异常保护 + try: + self.teleport_system = VRTeleportSystem(self) + print("✓ VR传送系统初始化完成") + except Exception as e: + print(f"⚠️ VR传送系统初始化失败: {e}") + self.teleport_system = None + + # VR摇杆系统 - 添加异常保护 + try: + self.joystick_manager = VRJoystickManager(self) + print("✓ VR摇杆管理器初始化完成") + except Exception as e: + print(f"⚠️ VR摇杆管理器初始化失败: {e}") + self.joystick_manager = None + print("✓ VR管理器初始化完成") def _initialize_object_pools(self): @@ -522,15 +546,18 @@ class VRManager(DirectObject): # 初始化手柄控制器 self._initialize_controllers() - # 初始化动作系统 - 检查是否存在 - if self.action_manager: + # 初始化动作系统 - 仅在启用时初始化 + if not self.disable_action_system and self.action_manager: try: if not self.action_manager.initialize(): print("⚠️ VR动作系统初始化失败,但VR系统将继续运行") except Exception as e: print(f"⚠️ VR动作系统初始化异常: {e}") else: - print("⚠️ VR动作管理器未创建,跳过动作系统初始化") + if self.disable_action_system: + print("🚫 VR动作系统已禁用,跳过初始化") + else: + print("⚠️ VR动作管理器未创建,跳过动作系统初始化") # 初始化交互系统 - 检查是否存在 if self.interaction_manager: @@ -542,6 +569,27 @@ class VRManager(DirectObject): else: print("⚠️ VR交互管理器未创建,跳过交互系统初始化") + # 初始化VR传送系统 + if self.teleport_system: + try: + if not self.teleport_system.initialize(): + print("⚠️ VR传送系统初始化失败,但VR系统将继续运行") + except Exception as e: + print(f"⚠️ VR传送系统初始化异常: {e}") + else: + print("⚠️ VR传送系统未创建,跳过传送系统初始化") + + # 初始化VR摇杆系统 + if self.joystick_manager: + try: + # 传入传送系统引用给摇杆管理器 + self.joystick_manager.initialize(self.teleport_system) + print("✓ VR摇杆系统初始化成功") + except Exception as e: + print(f"⚠️ VR摇杆系统初始化异常: {e}") + else: + print("⚠️ VR摇杆管理器未创建,跳过摇杆系统初始化") + # 可选:禁用异步重投影(备选方案) if self.disable_async_reprojection: self._disable_async_reprojection() @@ -956,11 +1004,29 @@ class VRManager(DirectObject): print(" 这确保每个 VSync 周期只渲染一次,解决 AlreadySubmitted 错误") self._new_architecture_logged = True - # 更新VR动作状态 - self.action_manager.update_actions() + # 更新VR动作状态 - 仅在启用时更新 + if not self.disable_action_system and self.action_manager: + self.action_manager.update_actions() # 更新VR交互系统 - self.interaction_manager.update() + if self.interaction_manager: + self.interaction_manager.update() + + # 更新VR摇杆系统 + if self.joystick_manager: + # 计算帧间隔时间 + import time + if not hasattr(self, '_last_frame_time'): + self._last_frame_time = time.time() + dt = 0.016 # 默认60fps + else: + current_time = time.time() + dt = current_time - self._last_frame_time + self._last_frame_time = current_time + # 限制dt范围,避免异常情况 + dt = max(0.001, min(0.1, dt)) + + self.joystick_manager.update(dt) # 更新系统性能指标(减少频率) if self.frame_count % 30 == 1: # 每30帧更新一次,减少开销 @@ -1527,6 +1593,32 @@ class VRManager(DirectObject): """禁用VR模式""" self.vr_enabled = False + # 清理手柄可视化(但保留控制器对象以便重新启用) + if hasattr(self, 'left_controller') and self.left_controller: + if hasattr(self.left_controller, 'visualizer') and self.left_controller.visualizer: + try: + self.left_controller.visualizer.cleanup() + self.left_controller.visualizer = None + print("✓ 左手控制器可视化已清理") + except Exception as e: + print(f"⚠️ 清理左手控制器可视化失败: {e}") + + if hasattr(self, 'right_controller') and self.right_controller: + if hasattr(self.right_controller, 'visualizer') and self.right_controller.visualizer: + try: + self.right_controller.visualizer.cleanup() + self.right_controller.visualizer = None + print("✓ 右手控制器可视化已清理") + except Exception as e: + print(f"⚠️ 清理右手控制器可视化失败: {e}") + + # 隐藏手柄锚点节点 + if hasattr(self, 'left_controller') and self.left_controller and self.left_controller.anchor_node: + self.left_controller.anchor_node.hide() + + if hasattr(self, 'right_controller') and self.right_controller and self.right_controller.anchor_node: + self.right_controller.anchor_node.hide() + # 恢复主相机 self._enable_main_cam() @@ -1536,7 +1628,7 @@ class VRManager(DirectObject): self.world.qtWidget.synchronizer.setInterval(int(1000/60)) print("✓ Qt Timer恢复为60Hz") - print("✅ VR模式已禁用") + print("✅ VR模式已禁用,手柄模型已隐藏") def cleanup(self): """清理VR资源""" @@ -1598,6 +1690,61 @@ class VRManager(DirectObject): self.vr_right_camera.removeNode() self.vr_right_camera = None + # 清理控制器 + if hasattr(self, 'left_controller') and self.left_controller: + try: + self.left_controller.cleanup() + self.left_controller = None + print("✓ 左手控制器已清理") + except Exception as e: + print(f"⚠️ 清理左手控制器失败: {e}") + + if hasattr(self, 'right_controller') and self.right_controller: + try: + self.right_controller.cleanup() + self.right_controller = None + print("✓ 右手控制器已清理") + except Exception as e: + print(f"⚠️ 清理右手控制器失败: {e}") + + # 清理控制器字典 + if hasattr(self, 'controllers'): + self.controllers.clear() + print("✓ 控制器字典已清理") + + # 清理VR子系统 + if hasattr(self, 'joystick_manager') and self.joystick_manager: + try: + self.joystick_manager.cleanup() + self.joystick_manager = None + print("✓ VR摇杆系统已清理") + except Exception as e: + print(f"⚠️ 清理VR摇杆系统失败: {e}") + + if hasattr(self, 'teleport_system') and self.teleport_system: + try: + self.teleport_system.cleanup() + self.teleport_system = None + print("✓ VR传送系统已清理") + except Exception as e: + print(f"⚠️ 清理VR传送系统失败: {e}") + + if hasattr(self, 'interaction_manager') and self.interaction_manager: + try: + self.interaction_manager.cleanup() + self.interaction_manager = None + print("✓ VR交互系统已清理") + except Exception as e: + print(f"⚠️ 清理VR交互系统失败: {e}") + + if hasattr(self, 'action_manager') and self.action_manager: + try: + self.action_manager.cleanup() + self.action_manager = None + print("✓ VR动作系统已清理") + except Exception as e: + print(f"⚠️ 清理VR动作系统失败: {e}") + # 关闭OpenVR if self.vr_system and OPENVR_AVAILABLE: try: diff --git a/core/vr_teleport.py b/core/vr_teleport.py new file mode 100644 index 00000000..25dd7b53 --- /dev/null +++ b/core/vr_teleport.py @@ -0,0 +1,411 @@ +""" +VR传送系统模块 + +提供VR传送功能: +- 抛物线轨迹计算和可视化 +- 传送点有效性检测 +- 传送执行 +- 可视化反馈(抛物线、落点标记) +""" + +import math +from panda3d.core import ( + Vec3, Vec4, Mat4, Point3, CollisionRay, CollisionTraverser, + CollisionNode, CollisionHandlerQueue, BitMask32, NodePath, + CollisionSphere, CollisionPlane, LineSegs, GeomNode, Material, + RenderState, TransparencyAttrib, ColorAttrib +) +from direct.showbase.DirectObject import DirectObject + + +class VRTeleportSystem(DirectObject): + """VR传送系统 - 处理传送功能和可视化""" + + def __init__(self, vr_manager): + """初始化VR传送系统 + + Args: + vr_manager: VR管理器实例 + """ + super().__init__() + + self.vr_manager = vr_manager + self.world = vr_manager.world if hasattr(vr_manager, 'world') else None + + # 传送参数 + self.teleport_range = 20.0 # 最大传送距离 + self.arc_resolution = 50 # 抛物线精度 + self.gravity = -9.8 # 重力系数 + self.initial_velocity = 10.0 # 初始速度 + self.min_teleport_distance = 1.0 # 最小传送距离 + + # 可视化元素 + self.teleport_arc_node = None # 抛物线节点 + self.teleport_target_node = None # 落点标记节点 + self.teleport_invalid_node = None # 无效位置标记 + + # 传送状态 + self.is_teleport_active = False # 是否正在预览传送 + self.teleport_target_pos = None # 传送目标位置 + self.teleport_valid = False # 传送位置是否有效 + self.active_controller = None # 正在使用传送的控制器 + + # 碰撞检测 + self.teleport_collision_traverser = CollisionTraverser() + self.teleport_collision_queue = CollisionHandlerQueue() + + # 可视化颜色 + self.valid_arc_color = Vec4(0.2, 0.9, 0.2, 0.8) # 绿色抛物线 + self.invalid_arc_color = Vec4(0.9, 0.2, 0.2, 0.8) # 红色抛物线 + self.target_color = Vec4(0.2, 0.7, 1.0, 0.9) # 蓝色落点 + + print("✓ VR传送系统初始化完成") + + def initialize(self): + """初始化传送系统""" + try: + print("🔧 正在初始化VR传送系统...") + + # 创建可视化元素 + self._create_teleport_visuals() + + # 设置地面检测 + self._setup_ground_detection() + + print("✅ VR传送系统初始化成功") + return True + + except Exception as e: + print(f"❌ VR传送系统初始化失败: {e}") + import traceback + traceback.print_exc() + return False + + def _create_teleport_visuals(self): + """创建传送可视化元素""" + if not self.world or not hasattr(self.world, 'render'): + print("⚠️ 无法创建传送可视化 - 缺少世界渲染节点") + return + + # 创建抛物线节点 + self.teleport_arc_node = self.world.render.attachNewNode("teleport_arc") + self.teleport_arc_node.hide() # 初始隐藏 + + # 创建落点标记 + self._create_target_marker() + + # 创建无效位置标记 + self._create_invalid_marker() + + print("✓ 传送可视化元素已创建") + + def _create_target_marker(self): + """创建传送目标标记""" + try: + # 创建一个圆形平台作为落点标记 + from panda3d.core import CardMaker + + cm = CardMaker("teleport_target") + cm.setFrame(-1, 1, -1, 1) # 2x2的平面 + + self.teleport_target_node = self.world.render.attachNewNode(cm.generate()) + self.teleport_target_node.setScale(1.0) # 2米直径的圆形 + self.teleport_target_node.setP(-90) # 平放在地面 + self.teleport_target_node.setColor(self.target_color) + self.teleport_target_node.setTransparency(TransparencyAttrib.MAlpha) + self.teleport_target_node.hide() + + # 添加PBR材质以兼容RenderPipeline + material = Material() + material.setBaseColor(self.target_color) + material.setRoughness(0.8) + material.setMetallic(0.0) + material.setRefractiveIndex(1.5) + self.teleport_target_node.setMaterial(material, 1) + + except Exception as e: + print(f"⚠️ 创建传送目标标记失败: {e}") + + def _create_invalid_marker(self): + """创建无效位置标记""" + try: + from panda3d.core import CardMaker + + cm = CardMaker("teleport_invalid") + cm.setFrame(-0.8, 0.8, -0.8, 0.8) # 稍小的红色叉号 + + self.teleport_invalid_node = self.world.render.attachNewNode(cm.generate()) + self.teleport_invalid_node.setScale(1.0) + self.teleport_invalid_node.setP(-90) + self.teleport_invalid_node.setColor(self.invalid_arc_color) + self.teleport_invalid_node.setTransparency(TransparencyAttrib.MAlpha) + self.teleport_invalid_node.hide() + + # 添加PBR材质 + material = Material() + material.setBaseColor(self.invalid_arc_color) + material.setRoughness(0.8) + material.setMetallic(0.0) + material.setRefractiveIndex(1.5) + self.teleport_invalid_node.setMaterial(material, 1) + + except Exception as e: + print(f"⚠️ 创建无效位置标记失败: {e}") + + def _setup_ground_detection(self): + """设置地面检测""" + try: + # 假设地面在Z=0平面,使用正确的CollisionPlane API + from panda3d.core import Plane + ground_plane = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0))) + + ground_node = CollisionNode("ground_detection") + ground_node.addSolid(ground_plane) + ground_node.setIntoCollideMask(BitMask32.bit(1)) # 地面碰撞掩码 + + # 附加到渲染节点 + if self.world and hasattr(self.world, 'render'): + ground_node_path = self.world.render.attachNewNode(ground_node) + print("✓ 地面检测设置成功") + + except Exception as e: + print(f"⚠️ 设置地面检测失败: {e}") + # 继续运行,不让这个错误阻止传送系统 + + def start_teleport_preview(self, controller, direction): + """开始传送预览 + + Args: + controller: VR控制器实例 + direction: 传送方向向量(摇杆输入转换的世界方向) + """ + if not controller or not controller.anchor_node: + return False + + self.is_teleport_active = True + self.active_controller = controller + + # 计算抛物线轨迹 + start_pos = controller.get_world_position() + self._calculate_teleport_trajectory(start_pos, direction) + + # 显示可视化 + self._show_teleport_visuals() + + return True + + def _calculate_teleport_trajectory(self, start_pos, direction): + """计算传送抛物线轨迹 + + Args: + start_pos: 起始位置 + direction: 方向向量 + """ + try: + # 确保方向向量已标准化 + direction = direction.normalized() + + # 计算抛物线点 + trajectory_points = [] + hit_ground = False + self.teleport_valid = False + + # 初始速度向量 + velocity = direction * self.initial_velocity + velocity.z += 2.0 # 给一些向上的初始速度 + + dt = 0.05 # 时间步长 + current_pos = Vec3(start_pos) + current_velocity = Vec3(velocity) + + for i in range(self.arc_resolution): + trajectory_points.append(Point3(current_pos)) + + # 更新位置和速度 + current_pos += current_velocity * dt + current_velocity.z += self.gravity * dt # 应用重力 + + # 检查是否碰撞地面 + if current_pos.z <= 0.1: # 假设地面在z=0 + # 精确计算落地点 + ground_pos = self._calculate_ground_intersection( + trajectory_points[-2] if len(trajectory_points) > 1 else start_pos, + current_pos + ) + trajectory_points.append(ground_pos) + + # 检查传送有效性 + distance = (ground_pos - start_pos).length() + if (distance >= self.min_teleport_distance and + distance <= self.teleport_range): + self.teleport_target_pos = ground_pos + self.teleport_valid = True + else: + self.teleport_target_pos = ground_pos + self.teleport_valid = False + + hit_ground = True + break + + # 超出范围 + if (current_pos - start_pos).length() > self.teleport_range: + break + + # 如果没有碰撞地面,传送无效 + if not hit_ground: + if trajectory_points: + self.teleport_target_pos = trajectory_points[-1] + self.teleport_valid = False + + # 创建抛物线几何体 + self._create_arc_geometry(trajectory_points) + + except Exception as e: + print(f"⚠️ 计算传送轨迹失败: {e}") + self.teleport_valid = False + + def _calculate_ground_intersection(self, p1, p2): + """计算与地面的精确交点""" + if p1.z == p2.z: + return Point3(p2) + + # 线性插值找到z=0的点 + t = -p1.z / (p2.z - p1.z) + t = max(0, min(1, t)) # 限制在0-1范围内 + + intersection = p1 + (p2 - p1) * t + intersection.z = 0.1 # 稍微高于地面 + + return Point3(intersection) + + def _create_arc_geometry(self, points): + """创建抛物线几何体""" + if not points or len(points) < 2: + return + + try: + # 创建线段 + line_segs = LineSegs() + line_segs.setThickness(3) + + # 根据有效性设置颜色 + color = self.valid_arc_color if self.teleport_valid else self.invalid_arc_color + line_segs.setColor(color) + + # 添加线段点 + line_segs.moveTo(points[0]) + for point in points[1:]: + line_segs.drawTo(point) + + # 清除旧的几何体 + self.teleport_arc_node.removeNode() + self.teleport_arc_node = self.world.render.attachNewNode("teleport_arc") + + # 创建新的几何体 + geom_node = line_segs.create() + arc_node_path = self.teleport_arc_node.attachNewNode(geom_node) + + # 设置材质 + material = Material() + material.setBaseColor(color) + material.setRoughness(0.1) + material.setMetallic(0.0) + material.setRefractiveIndex(1.5) + arc_node_path.setMaterial(material, 1) + + # 设置透明度 + arc_node_path.setTransparency(TransparencyAttrib.MAlpha) + + except Exception as e: + print(f"⚠️ 创建抛物线几何体失败: {e}") + + def _show_teleport_visuals(self): + """显示传送可视化""" + if self.teleport_arc_node: + self.teleport_arc_node.show() + + if self.teleport_target_pos: + if self.teleport_valid and self.teleport_target_node: + # 显示有效的传送目标 + self.teleport_target_node.setPos(self.teleport_target_pos) + self.teleport_target_node.show() + self.teleport_invalid_node.hide() + elif self.teleport_invalid_node: + # 显示无效的传送位置 + self.teleport_invalid_node.setPos(self.teleport_target_pos) + self.teleport_invalid_node.show() + self.teleport_target_node.hide() + + def stop_teleport_preview(self): + """停止传送预览""" + self.is_teleport_active = False + self.active_controller = None + + # 隐藏可视化 + if self.teleport_arc_node: + self.teleport_arc_node.hide() + if self.teleport_target_node: + self.teleport_target_node.hide() + if self.teleport_invalid_node: + self.teleport_invalid_node.hide() + + def execute_teleport(self): + """执行传送""" + if not self.teleport_valid or not self.teleport_target_pos: + print("⚠️ 传送位置无效,无法执行传送") + return False + + try: + # 计算传送偏移 + if self.vr_manager.tracking_space: + current_pos = self.vr_manager.tracking_space.getPos() + target_offset = self.teleport_target_pos - self.active_controller.get_world_position() + new_pos = current_pos + target_offset + + # 执行传送 + self.vr_manager.tracking_space.setPos(new_pos) + + print(f"✅ 传送成功: {current_pos} → {new_pos}") + + # 停止预览 + self.stop_teleport_preview() + + return True + else: + print("⚠️ 无法获取VR跟踪空间,传送失败") + return False + + except Exception as e: + print(f"❌ 执行传送失败: {e}") + return False + + def update_teleport_preview(self, controller, direction): + """更新传送预览(摇杆移动时调用)""" + if self.is_teleport_active and controller == self.active_controller: + start_pos = controller.get_world_position() + self._calculate_teleport_trajectory(start_pos, direction) + self._show_teleport_visuals() + + def cleanup(self): + """清理传送系统资源""" + try: + self.stop_teleport_preview() + + if self.teleport_arc_node: + self.teleport_arc_node.removeNode() + self.teleport_arc_node = None + + if self.teleport_target_node: + self.teleport_target_node.removeNode() + self.teleport_target_node = None + + if self.teleport_invalid_node: + self.teleport_invalid_node.removeNode() + self.teleport_invalid_node = None + + self.ignoreAll() + + print("🧹 VR传送系统已清理") + + except Exception as e: + print(f"⚠️ 清理传送系统失败: {e}") \ No newline at end of file diff --git a/core/vr_visualization.py b/core/vr_visualization.py index 9906d97a..c331a299 100644 --- a/core/vr_visualization.py +++ b/core/vr_visualization.py @@ -708,7 +708,22 @@ class VRControllerVisualizer: def cleanup(self): """清理资源""" + # 清理射线节点 + if hasattr(self, 'ray_node') and self.ray_node: + self.ray_node.removeNode() + self.ray_node = None + print(f"🧹 {self.controller.name}手柄射线已清理") + + # 清理模型节点 + if hasattr(self, 'model_node') and self.model_node: + self.model_node.removeNode() + self.model_node = None + print(f"🧹 {self.controller.name}手柄模型已清理") + + # 清理主可视化节点 if self.visual_node: self.visual_node.removeNode() + self.visual_node = None + print(f"🧹 {self.controller.name}手柄主节点已清理") - print(f"🧹 {self.controller.name}手柄可视化已清理") \ No newline at end of file + print(f"✅ {self.controller.name}手柄可视化已彻底清理") \ No newline at end of file