VR #4
@ -70,6 +70,14 @@ class Panda3DWorld(ShowBase):
|
||||
loadPrcFileData("", f"win-size {width} {height}")
|
||||
loadPrcFileData("", "win-fixed-size #f") # 允许窗口调整大小
|
||||
|
||||
# 🚀 VR性能优化配置
|
||||
loadPrcFileData("", "prefer-single-buffer true") # 减少缓冲区交换开销
|
||||
loadPrcFileData("", "gl-force-flush false") # 避免强制glFlush导致的性能损失
|
||||
loadPrcFileData("", "sync-video false") # 禁用默认VSync,让OpenVR控制
|
||||
loadPrcFileData("", "support-stencil false") # 禁用不必要的模板缓冲区
|
||||
loadPrcFileData("", "clock-mode non-real-time") # 禁用Panda3D帧率控制,让OpenVR控制
|
||||
# loadPrcFileData("", "gl-debug true") # 调试时可启用OpenGL调试
|
||||
|
||||
if (is_fullscreen):
|
||||
loadPrcFileData("", "fullscreen #t")
|
||||
|
||||
|
||||
@ -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,33 +152,97 @@ 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:
|
||||
result, state = vr_system.getControllerState(self.device_index)
|
||||
if result:
|
||||
# 更新按钮状态
|
||||
# 更新按钮状态 - 使用正确的OpenVR属性名
|
||||
for i in range(openvr.k_EButton_Max):
|
||||
button_mask = 1 << i
|
||||
self.button_states[i] = (state.rButtonPressed & button_mask) != 0
|
||||
# OpenVR Python绑定中使用ulButtonPressed而不是rButtonPressed
|
||||
self.button_states[i] = (state.ulButtonPressed & button_mask) != 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)
|
||||
|
||||
# 更新轴状态(扳机、握把、触摸板)
|
||||
if len(state.rAxis) > 0:
|
||||
# 扳机轴通常在axis[1].x
|
||||
if len(state.rAxis) > 1:
|
||||
self.trigger_value = state.rAxis[1].x
|
||||
if len(axis_data) > 1:
|
||||
self.trigger_value = axis_data[1].x
|
||||
|
||||
# 触摸板轴通常在axis[0]
|
||||
if len(state.rAxis) > 0:
|
||||
self.touchpad_pos = Vec3(state.rAxis[0].x, state.rAxis[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)
|
||||
|
||||
# 触摸板触摸状态
|
||||
self.touchpad_touched = (state.rButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 0
|
||||
# 额外检查其他轴(某些控制器可能将摇杆分配到不同轴)
|
||||
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:
|
||||
print(f"⚠️ 更新{self.name}手柄输入状态失败: {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):
|
||||
"""检查按钮是否被按下"""
|
||||
@ -248,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()
|
||||
|
||||
701
core/vr_joystick.py
Normal file
701
core/vr_joystick.py
Normal file
@ -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
|
||||
268
core/vr_joystick_config.py
Normal file
268
core/vr_joystick_config.py
Normal file
@ -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()
|
||||
3422
core/vr_manager.py
3422
core/vr_manager.py
File diff suppressed because it is too large
Load Diff
411
core/vr_teleport.py
Normal file
411
core/vr_teleport.py
Normal file
@ -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}")
|
||||
@ -178,36 +178,38 @@ class VRControllerVisualizer:
|
||||
return None
|
||||
|
||||
def _fix_model_material(self, model):
|
||||
"""修复模型材质,解决纯黑色问题同时保持纹理"""
|
||||
from panda3d.core import Material, MaterialAttrib
|
||||
"""修复模型材质,使用RenderPipeline兼容的新Material API"""
|
||||
from panda3d.core import Material, Vec4
|
||||
|
||||
# 检查模型是否有纹理
|
||||
has_texture = model.hasTexture()
|
||||
|
||||
# 创建新的材质
|
||||
# 创建新的材质,使用RenderPipeline兼容的API
|
||||
material = Material()
|
||||
|
||||
if has_texture:
|
||||
# 有纹理时,设置材质以增强纹理效果
|
||||
material.setDiffuse((1.0, 1.0, 1.0, 1.0)) # 白色漫反射让纹理完全显示
|
||||
material.setAmbient((0.4, 0.4, 0.4, 1.0)) # 适度环境光
|
||||
material.setSpecular((0.3, 0.3, 0.3, 1.0)) # 轻度高光
|
||||
material.setShininess(16.0) # 中等光泽度
|
||||
print(f"🎨 {self.controller.name}手柄:已修复材质(保持纹理效果)")
|
||||
# 有纹理时,设置白色基础颜色让纹理完全显示
|
||||
material.setBaseColor(Vec4(1.0, 1.0, 1.0, 1.0)) # 白色让纹理完全显示
|
||||
material.setRoughness(0.4) # 中等粗糙度
|
||||
material.setMetallic(0.3) # 轻度金属感
|
||||
material.setRefractiveIndex(1.5) # 标准折射率
|
||||
print(f"🎨 {self.controller.name}手柄:已设置材质(纹理+PBR)")
|
||||
else:
|
||||
# 无纹理时,设置合适的基础颜色
|
||||
material.setDiffuse((0.7, 0.7, 0.8, 1.0)) # 略偏蓝的灰色
|
||||
material.setAmbient((0.3, 0.3, 0.3, 1.0)) # 环境光
|
||||
material.setSpecular((0.5, 0.5, 0.5, 1.0)) # 高光
|
||||
material.setShininess(32.0) # 光泽度
|
||||
print(f"🎨 {self.controller.name}手柄:已修复材质(使用颜色)")
|
||||
# 无纹理时,设置手柄颜色
|
||||
material.setBaseColor(Vec4(0.7, 0.7, 0.8, 1.0)) # 略偏蓝的灰色
|
||||
material.setRoughness(0.5) # 中等粗糙度
|
||||
material.setMetallic(0.2) # 轻度金属感
|
||||
material.setRefractiveIndex(1.5) # 标准折射率
|
||||
print(f"🎨 {self.controller.name}手柄:已设置材质(颜色+PBR)")
|
||||
|
||||
# 应用材质到模型
|
||||
model.setMaterial(material)
|
||||
# 应用材质到模型,使用优先级1确保覆盖默认材质
|
||||
model.setMaterial(material, 1)
|
||||
|
||||
# 确保模型能正确渲染
|
||||
# 确保模型能正确渲染(双面渲染是NodePath的方法)
|
||||
model.setTwoSided(False)
|
||||
|
||||
print(f"🔧 {self.controller.name}手柄:已使用RenderPipeline兼容的Material API")
|
||||
|
||||
def _apply_controller_identity_marker(self, model):
|
||||
"""为控制器添加身份标记,区分左右手"""
|
||||
from panda3d.core import RenderModeAttrib
|
||||
@ -494,10 +496,33 @@ class VRControllerVisualizer:
|
||||
# 设置透明度
|
||||
self.ray_node.setTransparency(TransparencyAttrib.MAlpha)
|
||||
|
||||
# 使用RenderPipeline兼容的Material API设置射线材质
|
||||
from panda3d.core import Material, Vec4
|
||||
ray_material = Material()
|
||||
ray_material.setBaseColor(Vec4(self.ray_color.x, self.ray_color.y, self.ray_color.z, self.ray_color.w))
|
||||
ray_material.setRoughness(0.1) # 光滑射线
|
||||
ray_material.setMetallic(0.0) # 非金属
|
||||
ray_material.setRefractiveIndex(1.5) # 标准折射率
|
||||
|
||||
# 为射线应用材质
|
||||
self.ray_node.setMaterial(ray_material, 1)
|
||||
self.ray_node.setTwoSided(False)
|
||||
|
||||
# 为端点球设置相同的材质
|
||||
end_material = Material()
|
||||
end_material.setBaseColor(Vec4(self.ray_color.x, self.ray_color.y, self.ray_color.z, self.ray_color.w))
|
||||
end_material.setRoughness(0.2) # 略粗糙
|
||||
end_material.setMetallic(0.0) # 非金属
|
||||
end_material.setRefractiveIndex(1.5) # 标准折射率
|
||||
|
||||
# 为端点球应用材质
|
||||
end_node.setMaterial(end_material, 1)
|
||||
end_node.setTwoSided(False)
|
||||
|
||||
# 默认隐藏射线
|
||||
self.ray_node.hide()
|
||||
|
||||
print(f"✓ {self.controller.name}手柄交互射线已创建")
|
||||
print(f"✓ {self.controller.name}手柄交互射线已创建(含PBR支持)")
|
||||
|
||||
def _create_sphere_geometry(self, radius):
|
||||
"""创建球体几何体"""
|
||||
@ -530,6 +555,19 @@ class VRControllerVisualizer:
|
||||
self.trackpad_indicator.setPos(0, -0.02, 0.045)
|
||||
self.trackpad_indicator.setColor(0.2, 0.2, 0.2, 1.0)
|
||||
|
||||
# 为所有按钮指示器设置RenderPipeline兼容的材质
|
||||
from panda3d.core import Material, Vec4
|
||||
indicator_material = Material()
|
||||
indicator_material.setBaseColor(Vec4(0.2, 0.2, 0.2, 1.0)) # 深灰色
|
||||
indicator_material.setRoughness(0.8) # 比较粗糙的表面
|
||||
indicator_material.setMetallic(0.1) # 轻微金属感
|
||||
indicator_material.setRefractiveIndex(1.5) # 标准折射率
|
||||
|
||||
# 为所有指示器应用材质
|
||||
for indicator in [self.trigger_indicator, self.grip_indicator, self.trackpad_indicator]:
|
||||
indicator.setMaterial(indicator_material, 1)
|
||||
indicator.setTwoSided(False)
|
||||
|
||||
print(f"✓ {self.controller.name}手柄按钮指示器已创建")
|
||||
|
||||
def update(self):
|
||||
@ -619,6 +657,24 @@ class VRControllerVisualizer:
|
||||
if self.ray_node:
|
||||
self.ray_node.setColor(color)
|
||||
|
||||
# 更新射线材质的baseColor
|
||||
from panda3d.core import Material, Vec4
|
||||
if isinstance(color, (list, tuple)) and len(color) >= 3:
|
||||
alpha = color[3] if len(color) > 3 else 0.8
|
||||
ray_color = Vec4(color[0], color[1], color[2], alpha)
|
||||
elif isinstance(color, Vec4):
|
||||
ray_color = color
|
||||
else:
|
||||
ray_color = Vec4(0.9, 0.9, 0.2, 0.8) # 默认黄色
|
||||
|
||||
# 更新射线材质
|
||||
ray_material = Material()
|
||||
ray_material.setBaseColor(ray_color)
|
||||
ray_material.setRoughness(0.1)
|
||||
ray_material.setMetallic(0.0)
|
||||
ray_material.setRefractiveIndex(1.5)
|
||||
self.ray_node.setMaterial(ray_material, 1)
|
||||
|
||||
def set_ray_length(self, length):
|
||||
"""设置射线长度"""
|
||||
self.ray_length = length
|
||||
@ -652,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}手柄可视化已清理")
|
||||
print(f"✅ {self.controller.name}手柄可视化已彻底清理")
|
||||
123
run_vr_test.sh
Executable file
123
run_vr_test.sh
Executable file
@ -0,0 +1,123 @@
|
||||
#!/bin/bash
|
||||
|
||||
# VR性能测试启动脚本
|
||||
# 使用方法: ./run_vr_test.sh
|
||||
|
||||
echo "🚀 VR性能测试启动脚本"
|
||||
echo "======================"
|
||||
|
||||
# 检查是否在正确的目录
|
||||
if [ ! -f "vr_performance_test.py" ]; then
|
||||
echo "❌ 错误:找不到vr_performance_test.py文件"
|
||||
echo "请在EG项目根目录运行此脚本"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查Python是否可用
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
if ! command -v python &> /dev/null; then
|
||||
echo "❌ 错误:找不到Python解释器"
|
||||
exit 1
|
||||
else
|
||||
PYTHON_CMD="python"
|
||||
fi
|
||||
else
|
||||
PYTHON_CMD="python3"
|
||||
fi
|
||||
|
||||
echo "✓ 使用Python解释器: $PYTHON_CMD"
|
||||
|
||||
# 检查核心模块是否存在
|
||||
if [ ! -d "core" ]; then
|
||||
echo "❌ 错误:找不到core目录"
|
||||
echo "请确保在EG项目根目录中运行"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "core/vr_manager.py" ]; then
|
||||
echo "❌ 错误:找不到core/vr_manager.py文件"
|
||||
echo "VR管理器模块不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ 核心模块检查通过"
|
||||
|
||||
# 检查VR相关依赖
|
||||
echo "🔍 检查VR依赖..."
|
||||
|
||||
$PYTHON_CMD -c "import openvr" 2>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ OpenVR Python绑定可用"
|
||||
else
|
||||
echo "⚠️ 警告:OpenVR Python绑定不可用"
|
||||
echo " 如果需要VR功能,请安装: pip install openvr"
|
||||
fi
|
||||
|
||||
$PYTHON_CMD -c "from panda3d.core import Vec3" 2>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Panda3D可用"
|
||||
else
|
||||
echo "❌ 错误:Panda3D不可用"
|
||||
echo "请安装Panda3D: pip install panda3d"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查可选的性能监控依赖
|
||||
echo "🔍 检查性能监控依赖..."
|
||||
|
||||
$PYTHON_CMD -c "import psutil" 2>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ psutil可用(CPU/内存监控)"
|
||||
else
|
||||
echo "⚠️ 建议安装psutil以获得完整性能监控: pip install psutil"
|
||||
fi
|
||||
|
||||
$PYTHON_CMD -c "import GPUtil" 2>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ GPUtil可用(GPU监控)"
|
||||
else
|
||||
echo "⚠️ 建议安装GPUtil以获得GPU监控: pip install GPUtil"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎮 启动前检查清单:"
|
||||
echo "□ VR头显已连接并开机"
|
||||
echo "□ SteamVR正在运行"
|
||||
echo "□ VR头显已完成初始设置"
|
||||
echo "□ VR跟踪系统正常工作"
|
||||
echo ""
|
||||
|
||||
# 询问用户是否继续
|
||||
echo "是否继续启动VR性能测试?"
|
||||
echo "按Enter继续,或按Ctrl+C取消..."
|
||||
read -r
|
||||
|
||||
echo ""
|
||||
echo "🚀 正在启动VR性能测试..."
|
||||
echo "控制说明:"
|
||||
echo " ESC - 退出测试"
|
||||
echo " 1-9 - 设置场景复杂度"
|
||||
echo " R - 重置性能计数器"
|
||||
echo " P - 手动输出性能报告"
|
||||
echo " D - 切换调试模式"
|
||||
echo "VR分辨率控制:"
|
||||
echo " Q - 切换质量预设 (性能/平衡/质量)"
|
||||
echo " [ - 降低分辨率缩放"
|
||||
echo " ] - 提高分辨率缩放"
|
||||
echo " I - 显示分辨率信息"
|
||||
echo ""
|
||||
|
||||
# 启动测试
|
||||
$PYTHON_CMD vr_performance_test.py
|
||||
|
||||
# 检查退出状态
|
||||
if [ $? -eq 0 ]; then
|
||||
echo ""
|
||||
echo "✅ VR性能测试正常结束"
|
||||
else
|
||||
echo ""
|
||||
echo "❌ VR性能测试异常退出(错误代码:$?)"
|
||||
fi
|
||||
|
||||
echo "📋 检查当前目录中的CSV文件获取详细性能数据"
|
||||
echo "📖 查看VR_PERFORMANCE_TEST_README.md获取详细使用说明"
|
||||
@ -445,6 +445,94 @@ class MainWindow(QMainWindow):
|
||||
self.vrMenu.addSeparator()
|
||||
self.vrStatusAction = self.vrMenu.addAction('VR状态')
|
||||
self.vrSettingsAction = self.vrMenu.addAction('VR设置')
|
||||
self.vrMenu.addSeparator()
|
||||
|
||||
# VR调试子菜单
|
||||
self.vrDebugMenu = self.vrMenu.addMenu('VR调试')
|
||||
self.vrDebugToggleAction = self.vrDebugMenu.addAction('启用调试输出')
|
||||
self.vrDebugToggleAction.setCheckable(True)
|
||||
self.vrDebugToggleAction.setChecked(False) # 默认关闭(节省资源)
|
||||
|
||||
self.vrShowPerformanceAction = self.vrDebugMenu.addAction('立即显示性能报告')
|
||||
|
||||
self.vrDebugMenu.addSeparator()
|
||||
|
||||
# 调试模式切换
|
||||
self.vrDebugModeMenu = self.vrDebugMenu.addMenu('输出模式')
|
||||
self.vrDebugBriefAction = self.vrDebugModeMenu.addAction('简短模式')
|
||||
self.vrDebugDetailedAction = self.vrDebugModeMenu.addAction('详细模式')
|
||||
self.vrDebugBriefAction.setCheckable(True)
|
||||
self.vrDebugDetailedAction.setCheckable(True)
|
||||
self.vrDebugDetailedAction.setChecked(True) # 默认详细模式
|
||||
|
||||
# 创建调试模式动作组(单选)
|
||||
from PyQt5.QtWidgets import QActionGroup
|
||||
self.vrDebugModeGroup = QActionGroup(self)
|
||||
self.vrDebugModeGroup.addAction(self.vrDebugBriefAction)
|
||||
self.vrDebugModeGroup.addAction(self.vrDebugDetailedAction)
|
||||
|
||||
self.vrDebugMenu.addSeparator()
|
||||
|
||||
# 性能监控选项
|
||||
self.vrPerformanceMonitorAction = self.vrDebugMenu.addAction('启用性能监控')
|
||||
self.vrPerformanceMonitorAction.setCheckable(True)
|
||||
self.vrPerformanceMonitorAction.setChecked(False) # 默认关闭(节省资源)
|
||||
|
||||
self.vrGpuTimingAction = self.vrDebugMenu.addAction('启用GPU时间监控')
|
||||
self.vrGpuTimingAction.setCheckable(True)
|
||||
self.vrGpuTimingAction.setChecked(False) # 默认关闭(节省资源)
|
||||
|
||||
# 管线监控选项
|
||||
self.vrPipelineMonitorAction = self.vrDebugMenu.addAction('启用管线监控')
|
||||
self.vrPipelineMonitorAction.setCheckable(True)
|
||||
self.vrPipelineMonitorAction.setChecked(False) # 默认关闭(节省资源)
|
||||
|
||||
self.vrDebugMenu.addSeparator()
|
||||
|
||||
# 姿态策略选项
|
||||
self.vrPoseStrategyMenu = self.vrDebugMenu.addMenu('姿态策略')
|
||||
self.vrPoseRenderCallbackAction = self.vrPoseStrategyMenu.addAction('渲染回调策略')
|
||||
self.vrPoseUpdateTaskAction = self.vrPoseStrategyMenu.addAction('更新任务策略')
|
||||
self.vrPoseRenderCallbackAction.setCheckable(True)
|
||||
self.vrPoseUpdateTaskAction.setCheckable(True)
|
||||
self.vrPoseRenderCallbackAction.setChecked(True) # 默认策略
|
||||
|
||||
# 创建姿态策略动作组(单选)
|
||||
self.vrPoseStrategyGroup = QActionGroup(self)
|
||||
self.vrPoseStrategyGroup.addAction(self.vrPoseRenderCallbackAction)
|
||||
self.vrPoseStrategyGroup.addAction(self.vrPoseUpdateTaskAction)
|
||||
|
||||
self.vrDebugMenu.addSeparator()
|
||||
|
||||
# 测试功能
|
||||
self.vrTestPipelineAction = self.vrDebugMenu.addAction('测试管线监控')
|
||||
|
||||
# VR测试模式
|
||||
self.vrTestModeAction = self.vrDebugMenu.addAction('VR测试模式')
|
||||
self.vrTestModeAction.setCheckable(True)
|
||||
self.vrTestModeAction.setChecked(False) # 默认关闭
|
||||
|
||||
# VR测试模式调试子菜单
|
||||
self.vrTestDebugMenu = self.vrDebugMenu.addMenu('测试模式调试')
|
||||
|
||||
# 渐进式功能开关
|
||||
self.vrTestSubmitTextureAction = self.vrTestDebugMenu.addAction('启用纹理提交')
|
||||
self.vrTestSubmitTextureAction.setCheckable(True)
|
||||
self.vrTestSubmitTextureAction.setChecked(False) # 默认关闭
|
||||
|
||||
self.vrTestWaitPosesAction = self.vrTestDebugMenu.addAction('启用姿态等待')
|
||||
self.vrTestWaitPosesAction.setCheckable(True)
|
||||
self.vrTestWaitPosesAction.setChecked(False) # 默认关闭
|
||||
|
||||
self.vrTestDebugMenu.addSeparator()
|
||||
|
||||
# 快捷测试预设
|
||||
self.vrTestStep1Action = self.vrTestDebugMenu.addAction('步骤1: 只启用纹理提交')
|
||||
self.vrTestStep2Action = self.vrTestDebugMenu.addAction('步骤2: 只启用姿态等待')
|
||||
self.vrTestStep3Action = self.vrTestDebugMenu.addAction('步骤3: 同时启用两者')
|
||||
self.vrTestResetAction = self.vrTestDebugMenu.addAction('重置: 禁用所有功能')
|
||||
|
||||
self.vrDebugSettingsAction = self.vrDebugMenu.addAction('调试设置...')
|
||||
|
||||
# 初始状态下禁用退出VR选项
|
||||
self.exitVRAction.setEnabled(False)
|
||||
@ -915,6 +1003,29 @@ class MainWindow(QMainWindow):
|
||||
self.vrStatusAction.triggered.connect(self.onShowVRStatus)
|
||||
self.vrSettingsAction.triggered.connect(self.onShowVRSettings)
|
||||
|
||||
# 连接VR调试菜单事件
|
||||
self.vrDebugToggleAction.triggered.connect(self.onToggleVRDebug)
|
||||
self.vrShowPerformanceAction.triggered.connect(self.onShowVRPerformance)
|
||||
self.vrDebugBriefAction.triggered.connect(lambda: self.onSetVRDebugMode('brief'))
|
||||
self.vrDebugDetailedAction.triggered.connect(lambda: self.onSetVRDebugMode('detailed'))
|
||||
self.vrPerformanceMonitorAction.triggered.connect(self.onToggleVRPerformanceMonitor)
|
||||
self.vrGpuTimingAction.triggered.connect(self.onToggleVRGpuTiming)
|
||||
self.vrPipelineMonitorAction.triggered.connect(self.onToggleVRPipelineMonitor)
|
||||
self.vrPoseRenderCallbackAction.triggered.connect(lambda: self.onSetVRPoseStrategy('render_callback'))
|
||||
self.vrPoseUpdateTaskAction.triggered.connect(lambda: self.onSetVRPoseStrategy('update_task'))
|
||||
self.vrTestPipelineAction.triggered.connect(self.onTestVRPipeline)
|
||||
self.vrTestModeAction.triggered.connect(self.onToggleVRTestMode)
|
||||
|
||||
# 连接VR测试模式调试菜单事件
|
||||
self.vrTestSubmitTextureAction.triggered.connect(self.onToggleVRTestSubmitTexture)
|
||||
self.vrTestWaitPosesAction.triggered.connect(self.onToggleVRTestWaitPoses)
|
||||
self.vrTestStep1Action.triggered.connect(lambda: self.onSetVRTestStep(1))
|
||||
self.vrTestStep2Action.triggered.connect(lambda: self.onSetVRTestStep(2))
|
||||
self.vrTestStep3Action.triggered.connect(lambda: self.onSetVRTestStep(3))
|
||||
self.vrTestResetAction.triggered.connect(lambda: self.onSetVRTestStep(0))
|
||||
|
||||
self.vrDebugSettingsAction.triggered.connect(self.onShowVRDebugSettings)
|
||||
|
||||
|
||||
def onCreateCesiumView(self):
|
||||
if hasattr(self.world,'gui_manager') and self.world.gui_manager:
|
||||
@ -2190,6 +2301,380 @@ class MainWindow(QMainWindow):
|
||||
except Exception as e:
|
||||
QMessageBox.critical(dialog, "错误", f"应用VR设置时发生错误:\n{str(e)}")
|
||||
|
||||
# ==================== VR调试事件处理 ====================
|
||||
|
||||
def onToggleVRDebug(self):
|
||||
"""切换VR调试输出"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
enabled = self.world.vr_manager.toggle_debug_output()
|
||||
self.vrDebugToggleAction.setChecked(enabled)
|
||||
|
||||
status = "启用" if enabled else "禁用"
|
||||
QMessageBox.information(self, "VR调试", f"VR调试输出已{status}")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"切换VR调试时发生错误:\n{str(e)}")
|
||||
|
||||
def onShowVRPerformance(self):
|
||||
"""立即显示VR性能报告"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
if self.world.vr_manager.vr_enabled:
|
||||
self.world.vr_manager.force_performance_report()
|
||||
QMessageBox.information(self, "VR性能", "性能报告已输出到控制台")
|
||||
else:
|
||||
QMessageBox.warning(self, "提示", "请先启用VR模式")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"显示VR性能报告时发生错误:\n{str(e)}")
|
||||
|
||||
def onSetVRDebugMode(self, mode):
|
||||
"""设置VR调试模式"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
self.world.vr_manager.set_debug_mode(mode)
|
||||
|
||||
# 更新菜单状态
|
||||
if mode == 'brief':
|
||||
self.vrDebugBriefAction.setChecked(True)
|
||||
self.vrDebugDetailedAction.setChecked(False)
|
||||
else:
|
||||
self.vrDebugBriefAction.setChecked(False)
|
||||
self.vrDebugDetailedAction.setChecked(True)
|
||||
|
||||
mode_name = "简短" if mode == 'brief' else "详细"
|
||||
QMessageBox.information(self, "VR调试", f"调试模式已设置为:{mode_name}")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"设置VR调试模式时发生错误:\n{str(e)}")
|
||||
|
||||
def onToggleVRPerformanceMonitor(self):
|
||||
"""切换VR性能监控"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
enabled = self.vrPerformanceMonitorAction.isChecked()
|
||||
if enabled:
|
||||
self.world.vr_manager.enable_performance_monitoring()
|
||||
else:
|
||||
self.world.vr_manager.disable_performance_monitoring()
|
||||
|
||||
status = "启用" if enabled else "禁用"
|
||||
QMessageBox.information(self, "VR性能监控", f"VR性能监控已{status}")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
self.vrPerformanceMonitorAction.setChecked(False)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"切换VR性能监控时发生错误:\n{str(e)}")
|
||||
|
||||
def onToggleVRGpuTiming(self):
|
||||
"""切换VR GPU时间监控"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
enabled = self.vrGpuTimingAction.isChecked()
|
||||
if enabled:
|
||||
self.world.vr_manager.enable_gpu_timing_monitoring()
|
||||
else:
|
||||
self.world.vr_manager.disable_gpu_timing_monitoring()
|
||||
|
||||
status = "启用" if enabled else "禁用"
|
||||
QMessageBox.information(self, "VR GPU监控", f"VR GPU时间监控已{status}")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
self.vrGpuTimingAction.setChecked(False)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"切换VR GPU时间监控时发生错误:\n{str(e)}")
|
||||
|
||||
def onToggleVRPipelineMonitor(self):
|
||||
"""切换VR管线监控"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
enabled = self.vrPipelineMonitorAction.isChecked()
|
||||
self.world.vr_manager.enable_pipeline_monitoring = enabled
|
||||
status = "启用" if enabled else "禁用"
|
||||
print(f"✓ VR管线监控已{status}")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
self.vrPipelineMonitorAction.setChecked(False)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"切换VR管线监控时发生错误:\n{str(e)}")
|
||||
|
||||
def onSetVRPoseStrategy(self, strategy):
|
||||
"""设置VR姿态策略"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
success = self.world.vr_manager.set_pose_strategy(strategy)
|
||||
if success:
|
||||
strategy_names = {
|
||||
'render_callback': '渲染回调策略',
|
||||
'update_task': '更新任务策略'
|
||||
}
|
||||
QMessageBox.information(self, "VR姿态策略",
|
||||
f"姿态策略已切换为:{strategy_names.get(strategy, strategy)}")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", f"无效的姿态策略:{strategy}")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"设置VR姿态策略时发生错误:\n{str(e)}")
|
||||
|
||||
def onTestVRPipeline(self):
|
||||
"""测试VR管线监控功能"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
self.world.vr_manager.test_pipeline_monitoring()
|
||||
QMessageBox.information(self, "VR管线测试", "管线监控测试已完成,请查看控制台输出。")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"测试VR管线监控时发生错误:\n{str(e)}")
|
||||
|
||||
def onShowVRDebugSettings(self):
|
||||
"""显示VR调试设置对话框"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
dialog = self.createVRDebugSettingsDialog()
|
||||
dialog.exec_()
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"打开VR调试设置时发生错误:\n{str(e)}")
|
||||
|
||||
def createVRDebugSettingsDialog(self):
|
||||
"""创建VR调试设置对话框"""
|
||||
from PyQt5.QtWidgets import QCheckBox, QSlider
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle("VR调试设置")
|
||||
dialog.setModal(True)
|
||||
dialog.resize(450, 400)
|
||||
|
||||
layout = QVBoxLayout(dialog)
|
||||
|
||||
# 获取当前设置
|
||||
vr_manager = self.world.vr_manager
|
||||
debug_status = vr_manager.get_debug_status()
|
||||
perf_config = vr_manager.get_performance_monitoring_config()
|
||||
|
||||
# 调试状态显示
|
||||
status_group = QGroupBox("调试状态")
|
||||
status_layout = QVBoxLayout()
|
||||
|
||||
debug_enabled_label = QLabel(f"调试输出: {'启用' if debug_status['debug_enabled'] else '禁用'}")
|
||||
debug_enabled_label.setStyleSheet(f"color: {'green' if debug_status['debug_enabled'] else 'red'};")
|
||||
status_layout.addWidget(debug_enabled_label)
|
||||
|
||||
debug_mode_label = QLabel(f"调试模式: {debug_status['debug_mode']}")
|
||||
status_layout.addWidget(debug_mode_label)
|
||||
|
||||
performance_label = QLabel(f"性能监控: {'启用' if debug_status['performance_monitoring'] else '禁用'}")
|
||||
performance_label.setStyleSheet(f"color: {'green' if debug_status['performance_monitoring'] else 'red'};")
|
||||
status_layout.addWidget(performance_label)
|
||||
|
||||
status_group.setLayout(status_layout)
|
||||
layout.addWidget(status_group)
|
||||
|
||||
# 报告设置
|
||||
report_group = QGroupBox("报告设置")
|
||||
report_layout = QFormLayout()
|
||||
|
||||
# 报告间隔滑块 (5-120秒)
|
||||
interval_slider = QSlider(Qt.Horizontal)
|
||||
interval_slider.setMinimum(5)
|
||||
interval_slider.setMaximum(120)
|
||||
interval_slider.setValue(int(debug_status['report_interval_seconds']))
|
||||
interval_slider.setTickPosition(QSlider.TicksBelow)
|
||||
interval_slider.setTickInterval(15)
|
||||
|
||||
interval_label = QLabel(f"{int(debug_status['report_interval_seconds'])}秒")
|
||||
interval_slider.valueChanged.connect(lambda v: interval_label.setText(f"{v}秒"))
|
||||
|
||||
interval_layout = QHBoxLayout()
|
||||
interval_layout.addWidget(interval_slider)
|
||||
interval_layout.addWidget(interval_label)
|
||||
report_layout.addRow("报告间隔:", interval_layout)
|
||||
|
||||
# 性能检查间隔
|
||||
check_interval_combo = QComboBox()
|
||||
check_interval_combo.addItems(["0.1秒", "0.5秒", "1.0秒", "2.0秒"])
|
||||
current_check_interval = perf_config['check_interval']
|
||||
if current_check_interval == 0.1:
|
||||
check_interval_combo.setCurrentIndex(0)
|
||||
elif current_check_interval == 0.5:
|
||||
check_interval_combo.setCurrentIndex(1)
|
||||
elif current_check_interval == 1.0:
|
||||
check_interval_combo.setCurrentIndex(2)
|
||||
else:
|
||||
check_interval_combo.setCurrentIndex(3)
|
||||
report_layout.addRow("性能检查间隔:", check_interval_combo)
|
||||
|
||||
# 帧历史大小
|
||||
frame_history_spin = QSpinBox()
|
||||
frame_history_spin.setMinimum(10)
|
||||
frame_history_spin.setMaximum(1000)
|
||||
frame_history_spin.setValue(perf_config['frame_history_size'])
|
||||
frame_history_spin.setSuffix(" 帧")
|
||||
report_layout.addRow("帧时间历史:", frame_history_spin)
|
||||
|
||||
report_group.setLayout(report_layout)
|
||||
layout.addWidget(report_group)
|
||||
|
||||
# 监控项目
|
||||
monitor_group = QGroupBox("监控项目")
|
||||
monitor_layout = QVBoxLayout()
|
||||
|
||||
cpu_check = QCheckBox("CPU使用率")
|
||||
cpu_check.setChecked(perf_config['psutil_available'])
|
||||
cpu_check.setEnabled(perf_config['psutil_available'])
|
||||
monitor_layout.addWidget(cpu_check)
|
||||
|
||||
memory_check = QCheckBox("内存使用率")
|
||||
memory_check.setChecked(perf_config['psutil_available'])
|
||||
memory_check.setEnabled(perf_config['psutil_available'])
|
||||
monitor_layout.addWidget(memory_check)
|
||||
|
||||
gpu_check = QCheckBox("GPU使用率")
|
||||
gpu_check.setChecked(perf_config['gputil_available'] or perf_config['nvidia_ml_available'])
|
||||
gpu_check.setEnabled(perf_config['gputil_available'] or perf_config['nvidia_ml_available'])
|
||||
monitor_layout.addWidget(gpu_check)
|
||||
|
||||
frame_time_check = QCheckBox("帧时间统计")
|
||||
frame_time_check.setChecked(True)
|
||||
monitor_layout.addWidget(frame_time_check)
|
||||
|
||||
monitor_group.setLayout(monitor_layout)
|
||||
layout.addWidget(monitor_group)
|
||||
|
||||
# 按钮
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
apply_button = QPushButton("应用")
|
||||
reset_button = QPushButton("重置计数器")
|
||||
ok_button = QPushButton("确定")
|
||||
cancel_button = QPushButton("取消")
|
||||
|
||||
button_layout.addWidget(apply_button)
|
||||
button_layout.addWidget(reset_button)
|
||||
button_layout.addStretch()
|
||||
button_layout.addWidget(ok_button)
|
||||
button_layout.addWidget(cancel_button)
|
||||
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# 连接信号
|
||||
def apply_settings():
|
||||
try:
|
||||
# 应用报告间隔
|
||||
new_interval_seconds = interval_slider.value()
|
||||
new_interval_frames = int(new_interval_seconds * 60) # 假设60fps
|
||||
vr_manager.set_performance_report_interval(new_interval_frames)
|
||||
|
||||
# 应用性能检查间隔
|
||||
check_intervals = [0.1, 0.5, 1.0, 2.0]
|
||||
new_check_interval = check_intervals[check_interval_combo.currentIndex()]
|
||||
vr_manager.set_performance_check_interval(new_check_interval)
|
||||
|
||||
# 应用帧历史大小
|
||||
vr_manager.set_frame_time_history_size(frame_history_spin.value())
|
||||
|
||||
QMessageBox.information(dialog, "成功", "VR调试设置已应用!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(dialog, "错误", f"应用设置时发生错误:\n{str(e)}")
|
||||
|
||||
def reset_counters():
|
||||
try:
|
||||
vr_manager.reset_performance_counters()
|
||||
QMessageBox.information(dialog, "成功", "性能计数器已重置!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(dialog, "错误", f"重置计数器时发生错误:\n{str(e)}")
|
||||
|
||||
apply_button.clicked.connect(apply_settings)
|
||||
reset_button.clicked.connect(reset_counters)
|
||||
ok_button.clicked.connect(lambda: (apply_settings(), dialog.accept()))
|
||||
cancel_button.clicked.connect(dialog.reject)
|
||||
|
||||
return dialog
|
||||
|
||||
# ==================== VR测试模式事件处理 ====================
|
||||
|
||||
def onToggleVRTestMode(self):
|
||||
"""切换VR测试模式"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
if self.vrTestModeAction.isChecked():
|
||||
# 启用VR测试模式
|
||||
success = self.world.vr_manager.enable_vr_test_mode(display_mode='stereo')
|
||||
if success:
|
||||
QMessageBox.information(self, "VR测试模式",
|
||||
"VR测试模式已启用!\n\n现在VR渲染内容将直接显示在PC屏幕上,无需VR头显。\n\n特点:\n- 显示VR左右眼视图\n- 实时性能监控HUD\n- 复用完整VR渲染管线\n- 可测量纯渲染性能")
|
||||
print("✅ VR测试模式已启用")
|
||||
|
||||
# 可选:自动开启性能测试
|
||||
self.world.vr_manager.run_vr_performance_test(duration_seconds=10)
|
||||
else:
|
||||
self.vrTestModeAction.setChecked(False)
|
||||
QMessageBox.warning(self, "错误", "启用VR测试模式失败!")
|
||||
else:
|
||||
# 禁用VR测试模式
|
||||
self.world.vr_manager.disable_vr_test_mode()
|
||||
QMessageBox.information(self, "VR测试模式", "VR测试模式已禁用")
|
||||
print("✅ VR测试模式已禁用")
|
||||
else:
|
||||
self.vrTestModeAction.setChecked(False)
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
except Exception as e:
|
||||
self.vrTestModeAction.setChecked(False)
|
||||
QMessageBox.critical(self, "错误", f"切换VR测试模式时发生错误:\n{str(e)}")
|
||||
|
||||
def onToggleVRTestSubmitTexture(self):
|
||||
"""切换VR测试模式纹理提交功能"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
enabled = self.vrTestSubmitTextureAction.isChecked()
|
||||
self.world.vr_manager.set_test_mode_features(submit_texture=enabled)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"设置纹理提交功能时发生错误:\n{str(e)}")
|
||||
|
||||
def onToggleVRTestWaitPoses(self):
|
||||
"""切换VR测试模式姿态等待功能"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
enabled = self.vrTestWaitPosesAction.isChecked()
|
||||
self.world.vr_manager.set_test_mode_features(wait_poses=enabled)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"设置姿态等待功能时发生错误:\n{str(e)}")
|
||||
|
||||
def onSetVRTestStep(self, step):
|
||||
"""设置VR测试步骤"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
if step == 0: # 重置
|
||||
self.world.vr_manager.set_test_mode_features(submit_texture=False, wait_poses=False)
|
||||
self.vrTestSubmitTextureAction.setChecked(False)
|
||||
self.vrTestWaitPosesAction.setChecked(False)
|
||||
QMessageBox.information(self, "VR测试", "已重置为基线状态:两个功能都禁用")
|
||||
elif step == 1: # 只启用纹理提交
|
||||
self.world.vr_manager.set_test_mode_features(submit_texture=True, wait_poses=False)
|
||||
self.vrTestSubmitTextureAction.setChecked(True)
|
||||
self.vrTestWaitPosesAction.setChecked(False)
|
||||
QMessageBox.information(self, "VR测试", "步骤1:只启用纹理提交\n观察FPS变化来判断submit_texture是否影响性能")
|
||||
elif step == 2: # 只启用姿态等待
|
||||
self.world.vr_manager.set_test_mode_features(submit_texture=False, wait_poses=True)
|
||||
self.vrTestSubmitTextureAction.setChecked(False)
|
||||
self.vrTestWaitPosesAction.setChecked(True)
|
||||
QMessageBox.information(self, "VR测试", "步骤2:只启用姿态等待\n观察FPS变化来判断waitGetPoses是否影响性能")
|
||||
elif step == 3: # 同时启用两者
|
||||
self.world.vr_manager.set_test_mode_features(submit_texture=True, wait_poses=True)
|
||||
self.vrTestSubmitTextureAction.setChecked(True)
|
||||
self.vrTestWaitPosesAction.setChecked(True)
|
||||
QMessageBox.information(self, "VR测试", "步骤3:同时启用两者\n这应该完全复现普通VR模式的36FPS问题")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"设置VR测试步骤时发生错误:\n{str(e)}")
|
||||
|
||||
def setup_main_window(world,path = None):
|
||||
"""设置主窗口的便利函数"""
|
||||
app = QApplication.instance()
|
||||
|
||||
133
vr_actions/bindings_index.json
Normal file
133
vr_actions/bindings_index.json
Normal file
@ -0,0 +1,133 @@
|
||||
{
|
||||
"controller_type": "knuckles",
|
||||
"description": "Valve Index\u63a7\u5236\u5668\u7ed1\u5b9a",
|
||||
"name": "EG VR Editor - Index",
|
||||
"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": {
|
||||
"value": {
|
||||
"output": "/actions/default/in/Squeeze"
|
||||
}
|
||||
},
|
||||
"mode": "trigger",
|
||||
"path": "/user/hand/left/input/grip"
|
||||
},
|
||||
{
|
||||
"inputs": {
|
||||
"value": {
|
||||
"output": "/actions/default/in/Squeeze"
|
||||
}
|
||||
},
|
||||
"mode": "trigger",
|
||||
"path": "/user/hand/right/input/grip"
|
||||
},
|
||||
{
|
||||
"inputs": {
|
||||
"click": {
|
||||
"output": "/actions/default/in/AButton"
|
||||
}
|
||||
},
|
||||
"mode": "button",
|
||||
"path": "/user/hand/right/input/a"
|
||||
},
|
||||
{
|
||||
"inputs": {
|
||||
"click": {
|
||||
"output": "/actions/default/in/BButton"
|
||||
}
|
||||
},
|
||||
"mode": "button",
|
||||
"path": "/user/hand/right/input/b"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"inputs": {
|
||||
"position": {
|
||||
"output": "/actions/default/in/Joystick"
|
||||
}
|
||||
},
|
||||
"mode": "joystick",
|
||||
"path": "/user/hand/left/input/thumbstick"
|
||||
},
|
||||
{
|
||||
"inputs": {
|
||||
"position": {
|
||||
"output": "/actions/default/in/Joystick"
|
||||
}
|
||||
},
|
||||
"mode": "joystick",
|
||||
"path": "/user/hand/right/input/thumbstick"
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
118
vr_actions/bindings_oculus.json
Normal file
118
vr_actions/bindings_oculus.json
Normal file
@ -0,0 +1,118 @@
|
||||
{
|
||||
"controller_type": "oculus_touch",
|
||||
"description": "Oculus Touch\u63a7\u5236\u5668\u7ed1\u5b9a",
|
||||
"name": "EG VR Editor - Oculus Touch",
|
||||
"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": {
|
||||
"value": {
|
||||
"output": "/actions/default/in/Squeeze"
|
||||
}
|
||||
},
|
||||
"mode": "trigger",
|
||||
"path": "/user/hand/left/input/grip"
|
||||
},
|
||||
{
|
||||
"inputs": {
|
||||
"value": {
|
||||
"output": "/actions/default/in/Squeeze"
|
||||
}
|
||||
},
|
||||
"mode": "trigger",
|
||||
"path": "/user/hand/right/input/grip"
|
||||
},
|
||||
{
|
||||
"inputs": {
|
||||
"click": {
|
||||
"output": "/actions/default/in/AButton"
|
||||
}
|
||||
},
|
||||
"mode": "button",
|
||||
"path": "/user/hand/right/input/a"
|
||||
},
|
||||
{
|
||||
"inputs": {
|
||||
"click": {
|
||||
"output": "/actions/default/in/BButton"
|
||||
}
|
||||
},
|
||||
"mode": "button",
|
||||
"path": "/user/hand/right/input/b"
|
||||
},
|
||||
{
|
||||
"inputs": {
|
||||
"position": {
|
||||
"output": "/actions/default/in/Joystick"
|
||||
},
|
||||
"click": {
|
||||
"output": "/actions/default/in/TrackpadClick"
|
||||
}
|
||||
},
|
||||
"mode": "joystick",
|
||||
"path": "/user/hand/left/input/thumbstick"
|
||||
},
|
||||
{
|
||||
"inputs": {
|
||||
"position": {
|
||||
"output": "/actions/default/in/Joystick"
|
||||
},
|
||||
"click": {
|
||||
"output": "/actions/default/in/TrackpadClick"
|
||||
}
|
||||
},
|
||||
"mode": "joystick",
|
||||
"path": "/user/hand/right/input/thumbstick"
|
||||
},
|
||||
{
|
||||
"inputs": {
|
||||
"click": {
|
||||
"output": "/actions/default/in/Menu"
|
||||
}
|
||||
},
|
||||
"mode": "button",
|
||||
"path": "/user/hand/left/input/menu"
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user