添加手柄管理

This commit is contained in:
Rowland 2025-09-15 16:41:35 +08:00
parent 6c9c4339f2
commit ded28e0097
9 changed files with 2498 additions and 201 deletions

View File

@ -1,194 +0,0 @@
# VR模式使用指南
## 概述
本项目现已支持VR模式通过SteamVR将Panda3D引擎的画面传输到VR头显设备。您可以在虚拟现实环境中体验和编辑3D场景。
## 系统要求
### 硬件要求
- 支持SteamVR的VR头显设备如HTC Vive、Oculus Rift、Valve Index等
- VR头显控制器可选用于交互
- 满足VR运行要求的计算机配置
### 软件要求
- Windows 10/11 或 Linux支持OpenVR
- SteamVR运行时
- Python 3.7+
- 项目依赖库(见安装步骤)
## 安装与设置
### 1. 安装VR依赖
```bash
# 安装OpenVR Python库
pip install openvr==2.2.0
# 或者安装所有项目依赖
pip install -r requirements/requirements.txt
```
### 2. 安装SteamVR
1. 安装Steam客户端
2. 在Steam中搜索并安装"SteamVR"
3. 连接您的VR头显设备
4. 按照SteamVR设置向导完成房间设置
### 3. 验证VR设置
运行VR测试脚本来验证安装
```bash
python vr_test.py
```
## 使用方法
### 启动VR模式
1. **启动SteamVR**
- 在Steam中启动SteamVR
- 确保VR头显已连接并处于就绪状态
2. **启动应用程序**
```bash
python main.py
```
3. **进入VR模式**
- 在菜单栏中选择 `VR``进入VR模式`
- 系统会自动检测VR设备并初始化VR渲染
4. **戴上VR头显**
- 现在您可以在VR环境中查看和操作3D场景
### VR菜单选项
- **进入VR模式**: 启用VR渲染将画面输出到VR头显
- **退出VR模式**: 退出VR模式回到桌面显示
- **VR状态**: 查看VR系统状态和设备信息
- **VR设置**: 配置VR渲染参数和性能选项
### VR交互
目前支持的VR交互功能
- **头部追踪**: 自动追踪头显位置和朝向
- **房间规模移动**: 在设定的VR空间内移动
- **立体显示**: 为左右眼提供不同视角的立体图像
## 配置选项
### VR设置对话框
`VR``VR设置` 中可以配置:
- **渲染质量**: 调整VR渲染的画质等级
- **抗锯齿**: 设置抗锯齿级别
- **刷新率**: 配置VR头显刷新率
- **异步重投影**: 启用/禁用ATW技术
### 性能优化建议
1. **降低渲染分辨率**: 如果性能不足可以降低VR渲染质量
2. **减少抗锯齿**: 关闭或降低抗锯齿级别
3. **优化场景复杂度**: 减少场景中的多边形数量
4. **关闭不必要的特效**: 暂时关闭高级渲染特效
## 故障排除
### 常见问题
**Q: 无法进入VR模式提示"VR系统不可用"**
A: 请检查:
- SteamVR是否正在运行
- VR头显是否正确连接
- OpenVR库是否已安装 (`pip install openvr`)
**Q: VR画面卡顿或延迟**
A: 尝试:
- 降低VR渲染质量
- 关闭其他占用GPU的程序
- 检查VR头显连接线是否松动
**Q: VR菜单显示为灰色不可用**
A: 检查:
- VR管理器是否正确初始化
- 查看控制台错误信息
- 运行 `python vr_test.py` 进行诊断
**Q: 只能看到一只眼的画面**
A: 这可能是:
- VR渲染缓冲区创建失败
- OpenVR投影矩阵设置错误
- 检查图形驱动程序是否最新
### 诊断工具
使用内置的VR测试工具进行问题诊断
```bash
python vr_test.py
```
该工具会检测:
- 基础环境和依赖
- VR管理器状态
- VR设备可用性
- VR初始化过程
- UI菜单集成
### 日志信息
VR相关的日志信息会输出到控制台包括
- VR系统初始化状态
- 渲染缓冲区创建信息
- 追踪数据更新
- 错误和警告信息
## 开发者信息
### VR架构
```
main.py (MyWorld)
├── core/vr_manager.py (VRManager)
│ ├── OpenVR集成
│ ├── 渲染缓冲区管理
│ ├── 相机控制
│ └── 追踪数据处理
└── ui/main_window.py
├── VR菜单
├── VR设置对话框
└── VR事件处理
```
### 核心类
- **VRManager**: VR功能的核心管理类
- **MainWindow**: UI集成和事件处理
- **MyWorld**: VR管理器的宿主类
### 扩展开发
如需添加新的VR功能可以
1. 在 `VRManager` 中添加新方法
2. 在 `MainWindow` 中添加对应的UI控件
3. 更新VR设置对话框
4. 添加相应的测试代码
## 技术支持
如果遇到问题,请:
1. 运行 `python vr_test.py` 获取诊断信息
2. 检查控制台日志
3. 确认SteamVR和硬件设置正确
4. 提交问题时请包含详细的错误信息和系统配置
## 版本信息
- VR功能版本: 1.0.0
- 支持的OpenVR版本: 2.2.0+
- 兼容的SteamVR版本: 最新版本
---
**注意**: VR功能目前处于初始版本可能还有一些限制和待完善的功能。我们会持续改进和优化VR体验。

View File

@ -1,6 +0,0 @@
# Autogenerated
name = 'Plastic-R0.0'
roughness = 0.0
ior = 1.51
basecolor = (1, 0, 0)
mat_type = 'default'

583
core/vr_actions.py Normal file
View File

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

282
core/vr_controller.py Normal file
View File

@ -0,0 +1,282 @@
"""
VR手柄管理模块
基于panda3d-openvr参考实现提供完整的VR手柄追踪和交互功能
- 手柄位置和姿态追踪
- 按钮和触摸板输入处理
- 手柄可视化和射线显示
- 震动反馈支持
"""
from panda3d.core import (
NodePath, PandaNode, Vec3, Mat4, LVector3, LMatrix4,
GeomNode, LineSegs, CardMaker, Texture, RenderState,
TransparencyAttrib, ColorAttrib, Vec4
)
from direct.actor.Actor import Actor
from direct.showbase.DirectObject import DirectObject
try:
import openvr
OPENVR_AVAILABLE = True
except ImportError:
OPENVR_AVAILABLE = False
# 导入可视化器
from .vr_visualization import VRControllerVisualizer
class VRController(DirectObject):
"""VR手柄基类 - 管理单个手柄的追踪和交互"""
def __init__(self, vr_manager, name, hand_path, device_index=None):
"""初始化VR手柄
Args:
vr_manager: VR管理器实例
name: 手柄名称 ('left' 'right')
hand_path: OpenVR手部路径 ('/user/hand/left' '/user/hand/right')
device_index: OpenVR设备索引可选
"""
super().__init__()
self.vr_manager = vr_manager
self.name = name
self.hand_path = hand_path
self.device_index = device_index
# 手柄状态
self.is_connected = False
self.is_pose_valid = False
self.pose = Mat4.identMat()
self.velocity = Vec3(0, 0, 0)
self.angular_velocity = Vec3(0, 0, 0)
# 按钮状态
self.button_states = {}
self.previous_button_states = {}
self.trigger_value = 0.0
self.grip_value = 0.0
self.touchpad_pos = Vec3(0, 0, 0)
self.touchpad_touched = False
# 3D节点和可视化
self.anchor_node = None
self.visualizer = None
self.ray_length = 10.0
# 初始化
self._create_anchor()
self._create_visualizer()
print(f"{name}手柄控制器初始化完成")
def _create_anchor(self):
"""创建手柄锚点节点"""
if self.vr_manager.tracking_space:
self.anchor_node = self.vr_manager.tracking_space.attachNewNode(f'{self.name}-controller')
self.anchor_node.hide() # 初始隐藏,直到获得有效姿态
def _create_visualizer(self):
"""创建手柄可视化器"""
if self.anchor_node and hasattr(self.vr_manager, 'world'):
self.visualizer = VRControllerVisualizer(self, self.vr_manager.world.render)
elif self.anchor_node:
# 如果没有世界对象,使用基础渲染节点
from panda3d.core import NodePath
render = NodePath('render')
self.visualizer = VRControllerVisualizer(self, render)
def set_device_index(self, device_index):
"""设置OpenVR设备索引"""
self.device_index = device_index
self.is_connected = True
print(f"📱 {self.name}手柄连接 (设备索引: {device_index})")
def update_pose(self, pose_data):
"""更新手柄姿态
Args:
pose_data: OpenVR TrackedDevicePose_t数据
"""
if not pose_data.bPoseIsValid:
self.is_pose_valid = False
if self.anchor_node:
self.anchor_node.hide()
return
self.is_pose_valid = True
# 转换OpenVR矩阵到Panda3D
if hasattr(self.vr_manager, 'convert_mat') and hasattr(self.vr_manager, 'coord_mat_inv') and hasattr(self.vr_manager, 'coord_mat'):
modelview = self.vr_manager.convert_mat(pose_data.mDeviceToAbsoluteTracking)
self.pose = self.vr_manager.coord_mat_inv * modelview * self.vr_manager.coord_mat
else:
# 直接使用矩阵数据
m = pose_data.mDeviceToAbsoluteTracking.m
self.pose = LMatrix4(
m[0][0], m[1][0], m[2][0], m[3][0],
m[0][1], m[1][1], m[2][1], m[3][1],
m[0][2], m[1][2], m[2][2], m[3][2],
m[0][3], m[1][3], m[2][3], m[3][3]
)
# 更新锚点变换
if self.anchor_node:
self.anchor_node.setMat(self.pose)
self.anchor_node.show()
# 更新可视化
if self.visualizer:
self.visualizer.update()
# 更新速度信息
vel = pose_data.vVelocity
self.velocity = Vec3(vel[0], vel[1], vel[2])
ang_vel = pose_data.vAngularVelocity
self.angular_velocity = Vec3(ang_vel[0], ang_vel[1], ang_vel[2])
def update_input_state(self, vr_system):
"""更新输入状态
Args:
vr_system: OpenVR系统实例
"""
if not self.is_connected or not OPENVR_AVAILABLE or not vr_system:
return
# 保存上一帧的按钮状态
self.previous_button_states = self.button_states.copy()
# 获取控制器状态
try:
result, state = vr_system.getControllerState(self.device_index)
if result:
# 更新按钮状态
for i in range(openvr.k_EButton_Max):
button_mask = 1 << i
self.button_states[i] = (state.rButtonPressed & button_mask) != 0
# 更新轴状态(扳机、握把、触摸板)
if len(state.rAxis) > 0:
# 扳机轴通常在axis[1].x
if len(state.rAxis) > 1:
self.trigger_value = state.rAxis[1].x
# 触摸板轴通常在axis[0]
if len(state.rAxis) > 0:
self.touchpad_pos = Vec3(state.rAxis[0].x, state.rAxis[0].y, 0)
# 触摸板触摸状态
self.touchpad_touched = (state.rButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 0
except Exception as e:
print(f"⚠️ 更新{self.name}手柄输入状态失败: {e}")
def is_button_pressed(self, button_id):
"""检查按钮是否被按下"""
return self.button_states.get(button_id, False)
def is_button_just_pressed(self, button_id):
"""检查按钮是否刚刚被按下(上升沿)"""
current = self.button_states.get(button_id, False)
previous = self.previous_button_states.get(button_id, False)
return current and not previous
def is_button_just_released(self, button_id):
"""检查按钮是否刚刚被释放(下降沿)"""
current = self.button_states.get(button_id, False)
previous = self.previous_button_states.get(button_id, False)
return not current and previous
def is_trigger_pressed(self, threshold=0.1):
"""检查扳机是否被按下"""
return self.trigger_value > threshold
def is_grip_pressed(self, threshold=0.1):
"""检查握把是否被按下"""
return self.grip_value > threshold
def show_ray(self, show=True):
"""显示或隐藏交互射线"""
if self.visualizer:
if show:
self.visualizer.show_ray()
else:
self.visualizer.hide_ray()
def set_ray_color(self, color):
"""设置射线颜色"""
if self.visualizer and len(color) >= 3:
from panda3d.core import Vec4
color_vec = Vec4(color[0], color[1], color[2], color[3] if len(color) > 3 else 1.0)
self.visualizer.set_ray_color(color_vec)
def trigger_haptic_feedback(self, duration=0.001, strength=1.0):
"""触发震动反馈
Args:
duration: 震动持续时间
strength: 震动强度 (0.0-1.0)
"""
if not self.is_connected or not OPENVR_AVAILABLE:
return
try:
if hasattr(self.vr_manager, 'vr_system') and self.vr_manager.vr_system:
# OpenVR的震动API
duration_microseconds = int(duration * 1000000)
self.vr_manager.vr_system.triggerHapticPulse(
self.device_index,
0, # axis ID (通常为0)
int(strength * 3999) # 强度 (0-3999)
)
except Exception as e:
print(f"⚠️ {self.name}手柄震动反馈失败: {e}")
def get_world_position(self):
"""获取手柄在世界坐标系中的位置"""
if self.anchor_node:
return self.anchor_node.getPos(self.vr_manager.world.render)
return Vec3(0, 0, 0)
def get_world_rotation(self):
"""获取手柄在世界坐标系中的旋转"""
if self.anchor_node:
return self.anchor_node.getHpr(self.vr_manager.world.render)
return Vec3(0, 0, 0)
def get_forward_direction(self):
"""获取手柄指向的方向向量"""
if self.anchor_node:
# Y轴正方向为前方
return self.anchor_node.getMat().getRow3(1).getXyz().normalized()
return Vec3(0, 1, 0)
def cleanup(self):
"""清理资源"""
self.ignoreAll()
if self.visualizer:
self.visualizer.cleanup()
if self.anchor_node:
self.anchor_node.removeNode()
self.is_connected = False
print(f"🧹 {self.name}手柄控制器已清理")
class LeftController(VRController):
"""左手控制器"""
def __init__(self, vr_manager):
super().__init__(vr_manager, 'left', '/user/hand/left')
class RightController(VRController):
"""右手控制器"""
def __init__(self, vr_manager):
super().__init__(vr_manager, 'right', '/user/hand/right')

432
core/vr_interaction.py Normal file
View File

@ -0,0 +1,432 @@
"""
VR交互系统模块
提供VR手柄与3D场景的交互功能
- 射线投射和碰撞检测
- 对象选择和高亮
- 对象抓取和移动
- UI交互
- 距离抓取
"""
from panda3d.core import (
Vec3, Vec4, Mat4, Point3, CollisionRay, CollisionTraverser,
CollisionNode, CollisionHandlerQueue, BitMask32, NodePath,
CollisionSphere, CollisionTube, RenderState, TransparencyAttrib,
ColorAttrib
)
from direct.showbase.DirectObject import DirectObject
class VRInteractionManager(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.collision_traverser = CollisionTraverser()
self.collision_queue = CollisionHandlerQueue()
# 射线投射节点
self.left_ray_node = None
self.right_ray_node = None
self.ray_collision_nodes = {}
# 选择和抓取状态
self.selected_objects = {} # 控制器 -> 选中对象
self.grabbed_objects = {} # 控制器 -> 抓取对象
self.grab_offsets = {} # 控制器 -> 抓取偏移
# 交互参数
self.selection_range = 50.0 # 选择距离
self.grab_threshold = 0.5 # 抓取扳机阈值
self.selection_color = Vec4(0.9, 0.9, 0.2, 1.0) # 选择高亮颜色
self.grab_color = Vec4(0.2, 0.9, 0.2, 1.0) # 抓取高亮颜色
# 高亮状态
self.highlighted_objects = set()
self.original_colors = {} # 存储对象原始颜色
print("✓ VR交互管理器初始化完成")
def initialize(self):
"""初始化交互系统"""
try:
print("🔧 正在初始化VR交互系统...")
# 创建射线投射节点
self._create_ray_casters()
# 设置碰撞检测
self._setup_collision_detection()
print("✅ VR交互系统初始化成功")
return True
except Exception as e:
print(f"❌ VR交互系统初始化失败: {e}")
import traceback
traceback.print_exc()
return False
def _create_ray_casters(self):
"""创建射线投射节点"""
# 为左手控制器创建射线
if self.vr_manager.left_controller and self.vr_manager.left_controller.anchor_node:
self.left_ray_node = self._create_controller_ray('left', self.vr_manager.left_controller.anchor_node)
# 为右手控制器创建射线
if self.vr_manager.right_controller and self.vr_manager.right_controller.anchor_node:
self.right_ray_node = self._create_controller_ray('right', self.vr_manager.right_controller.anchor_node)
def _create_controller_ray(self, controller_name, anchor_node):
"""为控制器创建射线投射节点"""
# 创建射线碰撞体
ray = CollisionRay()
ray.setOrigin(0, 0, 0) # 从控制器原点开始
ray.setDirection(0, 1, 0) # 沿Y轴正方向
# 创建碰撞节点
ray_collision_node = CollisionNode(f'{controller_name}_ray')
ray_collision_node.addSolid(ray)
# 设置碰撞掩码
ray_collision_node.setFromCollideMask(BitMask32.bit(0)) # 射线掩码
ray_collision_node.setIntoCollideMask(BitMask32.allOff()) # 不接受碰撞
# 附加到控制器锚点
ray_node = anchor_node.attachNewNode(ray_collision_node)
self.ray_collision_nodes[controller_name] = ray_collision_node
# 注册到碰撞遍历器
self.collision_traverser.addCollider(ray_node, self.collision_queue)
print(f"{controller_name}手控制器射线投射已创建")
return ray_node
def _setup_collision_detection(self):
"""设置碰撞检测系统"""
if self.world:
# 使用世界的碰撞系统
if hasattr(self.world, 'render'):
# 为所有可交互对象设置碰撞体
self._setup_scene_collision_objects()
else:
print("⚠️ 无法访问世界对象,跳过场景碰撞设置")
def _setup_scene_collision_objects(self):
"""为场景对象设置碰撞体"""
if not self.world or not hasattr(self.world, 'render'):
return
try:
# 遍历场景中的所有节点,为它们添加碰撞体
for node_path in self.world.render.findAllMatches("**/+GeomNode"):
self._add_collision_to_object(node_path)
except Exception as e:
print(f"⚠️ 设置场景碰撞对象失败: {e}")
def _add_collision_to_object(self, node_path):
"""为对象添加碰撞体"""
try:
# 获取对象的边界框
bounds = node_path.getBounds()
if bounds.isEmpty():
return
# 计算边界球
center = bounds.getCenter()
radius = bounds.getRadius()
# 创建球形碰撞体
collision_sphere = CollisionSphere(center, radius)
# 创建碰撞节点
collision_node = CollisionNode(f'{node_path.getName()}_collision')
collision_node.addSolid(collision_sphere)
# 设置碰撞掩码
collision_node.setIntoCollideMask(BitMask32.bit(0)) # 接受射线碰撞
collision_node.setFromCollideMask(BitMask32.allOff()) # 不发射射线
# 附加碰撞节点
collision_node_path = node_path.attachNewNode(collision_node)
# 标记为可交互对象
node_path.setTag('interactable', 'true')
node_path.setTag('original_name', node_path.getName())
except Exception as e:
print(f"⚠️ 为对象 {node_path.getName()} 添加碰撞体失败: {e}")
def update(self):
"""更新交互系统 - 每帧调用"""
if not self.vr_manager.are_controllers_connected():
return
# 执行碰撞检测
self._perform_collision_detection()
# 更新选择状态
self._update_selections()
# 更新抓取状态
self._update_grabbing()
def _perform_collision_detection(self):
"""执行碰撞检测"""
if self.world and hasattr(self.world, 'render'):
self.collision_traverser.traverse(self.world.render)
def _update_selections(self):
"""更新对象选择状态"""
# 清除之前的选择高亮
self._clear_selection_highlights()
# 检查每个控制器的选择
for controller in self.vr_manager.get_connected_controllers():
if not controller:
continue
# 获取最近的碰撞对象
hit_object = self._get_closest_hit_object(controller.name)
if hit_object:
# 高亮选中的对象
self._highlight_object(hit_object, self.selection_color)
self.selected_objects[controller.name] = hit_object
# 显示控制器射线
controller.show_ray(True)
controller.set_ray_color([0.9, 0.9, 0.2, 0.8]) # 黄色
else:
# 没有选中对象
if controller.name in self.selected_objects:
del self.selected_objects[controller.name]
# 隐藏射线(除非正在抓取)
if controller.name not in self.grabbed_objects:
controller.show_ray(False)
def _get_closest_hit_object(self, controller_name):
"""获取指定控制器射线最近的碰撞对象"""
if controller_name not in self.ray_collision_nodes:
return None
closest_object = None
closest_distance = float('inf')
# 检查碰撞队列中的条目
for i in range(self.collision_queue.getNumEntries()):
entry = self.collision_queue.getEntry(i)
# 检查是否是该控制器的射线
from_node = entry.getFromNodePath()
if from_node.node() == self.ray_collision_nodes[controller_name]:
# 获取碰撞的对象
hit_node_path = entry.getIntoNodePath()
# 获取实际的几何对象(父节点)
geom_object = hit_node_path.getParent()
if geom_object and geom_object.hasTag('interactable'):
distance = entry.getSurfacePoint(geom_object).length()
if distance < closest_distance and distance <= self.selection_range:
closest_distance = distance
closest_object = geom_object
return closest_object
def _update_grabbing(self):
"""更新对象抓取状态"""
for controller in self.vr_manager.get_connected_controllers():
if not controller:
continue
controller_name = controller.name
# 检查是否按下抓取按钮
if controller.is_trigger_pressed(threshold=self.grab_threshold):
# 如果还没有抓取对象
if controller_name not in self.grabbed_objects:
# 尝试抓取选中的对象
if controller_name in self.selected_objects:
selected_obj = self.selected_objects[controller_name]
self._start_grab(controller, selected_obj)
# 如果正在抓取,更新对象位置
if controller_name in self.grabbed_objects:
self._update_grabbed_object(controller)
else:
# 释放抓取
if controller_name in self.grabbed_objects:
self._release_grab(controller)
def _start_grab(self, controller, obj):
"""开始抓取对象"""
controller_name = controller.name
try:
# 计算抓取偏移(对象相对于控制器的位置)
controller_pos = controller.get_world_position()
object_pos = obj.getPos(self.world.render if self.world else obj.getParent())
offset = object_pos - controller_pos
self.grab_offsets[controller_name] = offset
# 记录抓取状态
self.grabbed_objects[controller_name] = obj
# 改变对象颜色表示抓取状态
self._highlight_object(obj, self.grab_color)
# 触发震动反馈
controller.trigger_haptic_feedback(0.01, 0.8)
# 显示绿色射线表示抓取
controller.show_ray(True)
controller.set_ray_color([0.2, 0.9, 0.2, 0.8])
print(f"🤏 {controller_name}手开始抓取对象: {obj.getName()}")
except Exception as e:
print(f"⚠️ 开始抓取失败: {e}")
def _update_grabbed_object(self, controller):
"""更新被抓取对象的位置"""
controller_name = controller.name
if controller_name not in self.grabbed_objects:
return
try:
grabbed_obj = self.grabbed_objects[controller_name]
grab_offset = self.grab_offsets.get(controller_name, Vec3(0, 0, 0))
# 计算新位置
controller_pos = controller.get_world_position()
new_pos = controller_pos + grab_offset
# 更新对象位置
grabbed_obj.setPos(self.world.render if self.world else grabbed_obj.getParent(), new_pos)
# 可选:同步旋转
if hasattr(controller, 'get_world_rotation'):
controller_rot = controller.get_world_rotation()
grabbed_obj.setHpr(self.world.render if self.world else grabbed_obj.getParent(), controller_rot)
except Exception as e:
print(f"⚠️ 更新抓取对象失败: {e}")
def _release_grab(self, controller):
"""释放抓取的对象"""
controller_name = controller.name
if controller_name not in self.grabbed_objects:
return
try:
grabbed_obj = self.grabbed_objects[controller_name]
# 恢复对象原始颜色
self._restore_object_color(grabbed_obj)
# 清理抓取状态
del self.grabbed_objects[controller_name]
if controller_name in self.grab_offsets:
del self.grab_offsets[controller_name]
# 触发震动反馈
controller.trigger_haptic_feedback(0.005, 0.4)
print(f"🫳 {controller_name}手释放对象: {grabbed_obj.getName()}")
except Exception as e:
print(f"⚠️ 释放抓取失败: {e}")
def _highlight_object(self, obj, color):
"""高亮显示对象"""
if obj in self.highlighted_objects:
return
try:
# 保存原始颜色
if obj not in self.original_colors:
self.original_colors[obj] = obj.getColor()
# 设置高亮颜色
obj.setColor(color)
self.highlighted_objects.add(obj)
except Exception as e:
print(f"⚠️ 高亮对象失败: {e}")
def _restore_object_color(self, obj):
"""恢复对象原始颜色"""
if obj not in self.highlighted_objects:
return
try:
# 恢复原始颜色
if obj in self.original_colors:
obj.setColor(self.original_colors[obj])
del self.original_colors[obj]
self.highlighted_objects.discard(obj)
except Exception as e:
print(f"⚠️ 恢复对象颜色失败: {e}")
def _clear_selection_highlights(self):
"""清除所有选择高亮"""
for obj in list(self.highlighted_objects):
# 只清除非抓取状态的对象
is_grabbed = any(obj == grabbed_obj for grabbed_obj in self.grabbed_objects.values())
if not is_grabbed:
self._restore_object_color(obj)
def get_selected_object(self, controller_name):
"""获取指定控制器选中的对象"""
return self.selected_objects.get(controller_name)
def get_grabbed_object(self, controller_name):
"""获取指定控制器抓取的对象"""
return self.grabbed_objects.get(controller_name)
def is_grabbing(self, controller_name):
"""检查指定控制器是否正在抓取对象"""
return controller_name in self.grabbed_objects
def force_release_all(self):
"""强制释放所有抓取的对象"""
for controller in self.vr_manager.get_connected_controllers():
if controller and controller.name in self.grabbed_objects:
self._release_grab(controller)
def cleanup(self):
"""清理资源"""
self.ignoreAll()
# 释放所有抓取
self.force_release_all()
# 清理碰撞系统
self.collision_traverser.clearColliders()
self.ray_collision_nodes.clear()
# 清理高亮状态
for obj in list(self.highlighted_objects):
self._restore_object_color(obj)
print("🧹 VR交互管理器已清理")

View File

@ -27,6 +27,11 @@ except ImportError:
OPENVR_AVAILABLE = False
print("警告: OpenVR未安装VR功能将不可用")
# 导入手柄控制器、动作系统和交互系统
from .vr_controller import LeftController, RightController
from .vr_actions import VRActionManager
from .vr_interaction import VRInteractionManager
class VRManager(DirectObject):
"""VR管理器类 - 处理所有VR相关功能"""
@ -87,6 +92,18 @@ class VRManager(DirectObject):
# VR提交策略 - 基于参考实现
self.submit_together = True # 是否在right_cb中同时提交左右眼
# VR手柄控制器
self.left_controller = None
self.right_controller = None
self.controllers = {} # 设备索引到控制器的映射
self.tracked_device_anchors = {} # 跟踪设备锚点
# VR动作系统
self.action_manager = VRActionManager(self)
# VR交互系统
self.interaction_manager = VRInteractionManager(self)
print("✓ VR管理器初始化完成")
def convert_mat(self, mat):
@ -163,6 +180,17 @@ class VRManager(DirectObject):
print("❌ 设置VR相机失败")
return False
# 初始化手柄控制器
self._initialize_controllers()
# 初始化动作系统
if not self.action_manager.initialize():
print("⚠️ VR动作系统初始化失败但VR系统将继续运行")
# 初始化交互系统
if not self.interaction_manager.initialize():
print("⚠️ VR交互系统初始化失败但VR系统将继续运行")
# 启动VR更新任务
self._start_vr_task()
@ -368,6 +396,15 @@ class VRManager(DirectObject):
# 2. 更新相机位置(使用刚获取的最新姿态数据)
self._update_camera_poses()
# 3. 更新手柄和其他跟踪设备
self.update_tracked_devices()
# 4. 更新VR动作状态
self.action_manager.update_actions()
# 5. 更新VR交互系统
self.interaction_manager.update()
# 注意:纹理提交现在通过渲染回调自动处理
# 定期输出性能报告
@ -929,4 +966,314 @@ class VRManager(DirectObject):
print("✓ 主相机已恢复")
except Exception as e:
print(f"⚠️ 恢复主相机失败: {e}")
print(f"⚠️ 恢复主相机失败: {e}")
def _initialize_controllers(self):
"""初始化VR手柄控制器"""
try:
print("🎮 正在初始化VR手柄控制器...")
# 创建左右手柄控制器实例
self.left_controller = LeftController(self)
self.right_controller = RightController(self)
# 检测现有连接的控制器
self._detect_controllers()
print("✓ VR手柄控制器初始化完成")
except Exception as e:
print(f"⚠️ VR手柄初始化失败: {e}")
import traceback
traceback.print_exc()
def _detect_controllers(self):
"""检测并连接VR控制器"""
if not self.vr_system:
return
try:
for device_index in range(openvr.k_unMaxTrackedDeviceCount):
# 检查设备是否已连接
if not self.vr_system.isTrackedDeviceConnected(device_index):
continue
# 获取设备类型
device_class = self.vr_system.getTrackedDeviceClass(device_index)
if device_class == openvr.TrackedDeviceClass_Controller:
# 获取控制器角色
role = self.vr_system.getControllerRoleForTrackedDeviceIndex(device_index)
if role == openvr.TrackedControllerRole_LeftHand and self.left_controller:
self.left_controller.set_device_index(device_index)
self.controllers[device_index] = self.left_controller
# 为设备创建锚点
self._create_tracked_device_anchor(device_index, 'left_controller')
elif role == openvr.TrackedControllerRole_RightHand and self.right_controller:
self.right_controller.set_device_index(device_index)
self.controllers[device_index] = self.right_controller
# 为设备创建锚点
self._create_tracked_device_anchor(device_index, 'right_controller')
print(f"🎮 检测到 {len(self.controllers)} 个控制器")
except Exception as e:
print(f"⚠️ 控制器检测失败: {e}")
def _create_tracked_device_anchor(self, device_index, name):
"""为跟踪设备创建锚点节点"""
if not self.tracking_space:
print(f"⚠️ 无法为设备 {device_index} 创建锚点 - tracking_space未初始化")
return
try:
# 获取设备模型名称
if self.vr_system:
model_name = self.vr_system.getStringTrackedDeviceProperty(
device_index,
openvr.Prop_RenderModelName_String
)
anchor_name = f"{device_index}:{model_name}:{name}"
else:
anchor_name = f"{device_index}:{name}"
# 创建锚点节点
device_anchor = self.tracking_space.attachNewNode(anchor_name)
self.tracked_device_anchors[device_index] = device_anchor
print(f"✓ 为设备 {device_index} 创建锚点: {anchor_name}")
except Exception as e:
print(f"⚠️ 创建设备锚点失败: {e}")
def update_tracked_devices(self):
"""更新所有跟踪设备的姿态 - 基于参考实现"""
if not self.poses or not self.vr_system:
return
try:
# 更新每个已连接的控制器
for device_index, controller in self.controllers.items():
if device_index < len(self.poses):
pose_data = self.poses[device_index]
# 更新控制器姿态
controller.update_pose(pose_data)
# 更新控制器输入状态
controller.update_input_state(self.vr_system)
# 更新其他跟踪设备的锚点
for device_index in range(1, min(len(self.poses), openvr.k_unMaxTrackedDeviceCount)):
if device_index in self.tracked_device_anchors:
pose_data = self.poses[device_index]
if pose_data.bPoseIsValid:
# 转换姿态矩阵
modelview = self.convert_mat(pose_data.mDeviceToAbsoluteTracking)
final_matrix = self.coord_mat_inv * modelview * self.coord_mat
# 更新锚点变换
anchor = self.tracked_device_anchors[device_index]
anchor.setMat(final_matrix)
anchor.show()
else:
# 姿态无效,隐藏锚点
self.tracked_device_anchors[device_index].hide()
except Exception as e:
if self.frame_count % 300 == 0: # 每5秒输出一次错误
print(f"⚠️ 更新跟踪设备失败: {e}")
def get_controller_by_role(self, role):
"""根据角色获取控制器
Args:
role: 'left' 'right'
Returns:
VRController实例或None
"""
if role == 'left':
return self.left_controller
elif role == 'right':
return self.right_controller
return None
def are_controllers_connected(self):
"""检查是否有控制器连接"""
return len(self.controllers) > 0
def get_connected_controllers(self):
"""获取所有连接的控制器列表"""
return list(self.controllers.values())
def trigger_controller_haptic(self, role, duration=0.001, strength=1.0):
"""触发控制器震动反馈
Args:
role: 'left', 'right' 'both'
duration: 震动持续时间
strength: 震动强度 (0.0-1.0)
"""
if role in ['left', 'both'] and self.left_controller:
self.left_controller.trigger_haptic_feedback(duration, strength)
if role in ['right', 'both'] and self.right_controller:
self.right_controller.trigger_haptic_feedback(duration, strength)
# VR动作系统便捷方法
def is_trigger_pressed(self, hand='any'):
"""检查扳机是否被按下
Args:
hand: 'left', 'right', 'any'
"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
pressed, _ = self.action_manager.is_digital_action_pressed('trigger', device_path)
return pressed
def is_trigger_just_pressed(self, hand='any'):
"""检查扳机是否刚刚被按下"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
pressed, _ = self.action_manager.is_digital_action_just_pressed('trigger', device_path)
return pressed
def is_grip_pressed(self, hand='any'):
"""检查握把是否被按下"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
pressed, _ = self.action_manager.is_digital_action_pressed('grip', device_path)
return pressed
def is_grip_just_pressed(self, hand='any'):
"""检查握把是否刚刚被按下"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
pressed, _ = self.action_manager.is_digital_action_just_pressed('grip', device_path)
return pressed
def is_menu_pressed(self, hand='any'):
"""检查菜单按钮是否被按下"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
pressed, _ = self.action_manager.is_digital_action_pressed('menu', device_path)
return pressed
def is_trackpad_touched(self, hand='any'):
"""检查触摸板是否被触摸"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
touched, _ = self.action_manager.is_digital_action_pressed('trackpad_touch', device_path)
return touched
def get_trackpad_position(self, hand='any'):
"""获取触摸板位置
Returns:
Vec2或None: 触摸板位置 (-1到1的范围)
"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
value, _ = self.action_manager.get_analog_action_value('trackpad', device_path)
return value
# VR交互系统便捷方法
def get_selected_object(self, hand='any'):
"""获取指定手选中的对象
Args:
hand: 'left', 'right', 'any'
Returns:
选中的对象节点或None
"""
if hand == 'any':
# 返回任意手选中的对象
for controller in self.get_connected_controllers():
selected = self.interaction_manager.get_selected_object(controller.name)
if selected:
return selected
return None
else:
return self.interaction_manager.get_selected_object(hand)
def get_grabbed_object(self, hand='any'):
"""获取指定手抓取的对象
Args:
hand: 'left', 'right', 'any'
Returns:
抓取的对象节点或None
"""
if hand == 'any':
# 返回任意手抓取的对象
for controller in self.get_connected_controllers():
grabbed = self.interaction_manager.get_grabbed_object(controller.name)
if grabbed:
return grabbed
return None
else:
return self.interaction_manager.get_grabbed_object(hand)
def is_grabbing_object(self, hand='any'):
"""检查是否正在抓取对象
Args:
hand: 'left', 'right', 'any'
Returns:
bool: 是否正在抓取
"""
if hand == 'any':
# 检查任意手是否正在抓取
for controller in self.get_connected_controllers():
if self.interaction_manager.is_grabbing(controller.name):
return True
return False
else:
return self.interaction_manager.is_grabbing(hand)
def force_release_all_grabs(self):
"""强制释放所有抓取的对象"""
self.interaction_manager.force_release_all()
def add_interactable_object(self, object_node):
"""将对象标记为可交互
Args:
object_node: 要标记的对象节点
"""
self.interaction_manager._add_collision_to_object(object_node)

658
core/vr_visualization.py Normal file
View File

@ -0,0 +1,658 @@
"""
VR可视化模块
提供VR手柄和交互元素的高级可视化功能
- 手柄3D模型渲染
- 交互射线显示
- 按钮状态可视化
- 触摸板和扳机反馈
"""
from panda3d.core import (
NodePath, GeomNode, LineSegs, CardMaker, Geom, GeomVertexData,
GeomVertexFormat, GeomVertexWriter, GeomTriangles, GeomPoints,
Vec3, Vec4, Mat4, TransparencyAttrib, RenderState, ColorAttrib,
InternalName, loadPrcFileData
)
from panda3d.core import Texture, Material, TextureStage
# 启用Assimp支持OBJ文件加载
loadPrcFileData("", "load-file-type p3assimp")
class VRControllerVisualizer:
"""VR手柄可视化器"""
def __init__(self, controller, render_node):
"""初始化手柄可视化器
Args:
controller: VRController实例
render_node: 渲染节点
"""
self.controller = controller
self.render = render_node
# 可视化节点
self.visual_node = None
self.model_node = None
self.ray_node = None
self.button_indicator_node = None
# 射线参数
self.ray_length = 10.0
self.ray_color = Vec4(0.9, 0.9, 0.2, 0.8)
self.ray_hit_color = Vec4(0.2, 0.9, 0.2, 0.8)
# 按钮指示器参数
self.button_colors = {
'normal': Vec4(0.3, 0.3, 0.8, 1.0),
'left': Vec4(0.2, 0.6, 0.9, 1.0),
'right': Vec4(0.9, 0.3, 0.3, 1.0),
'trigger': Vec4(0.9, 0.6, 0.2, 1.0),
'grip': Vec4(0.6, 0.9, 0.3, 1.0),
'menu': Vec4(0.9, 0.2, 0.9, 1.0),
'trackpad': Vec4(0.2, 0.9, 0.9, 1.0)
}
self._create_visual_components()
def _create_visual_components(self):
"""创建可视化组件"""
if not self.controller.anchor_node:
return
# 创建主可视化节点
self.visual_node = self.controller.anchor_node.attachNewNode(f'{self.controller.name}_visual')
# 创建手柄模型
self._create_controller_model()
# 创建交互射线
self._create_interaction_ray()
# 暂时注释按钮指示器功能,避免额外几何体造成悬空零件
# self._create_button_indicators()
def _create_controller_model(self):
"""创建手柄3D模型"""
if not self.visual_node:
return
# 创建模型节点
self.model_node = self.visual_node.attachNewNode(f'{self.controller.name}_model')
# 尝试加载SteamVR官方模型
steamvr_model = self._load_steamvr_model()
if steamvr_model:
# 使用SteamVR官方模型
steamvr_model.reparentTo(self.model_node)
# 应用SteamVR配置中的正确旋转值绕Y轴俯仰轴旋转90度
# body组件的rotate_xyz: [5.037,0.0,0.0]再加上绕Y轴旋转90度
# 右手正确,左手需要反向
if self.controller.name == 'left':
# 左手控制器绕Y轴俯仰+90度修正反向
steamvr_model.setHpr(0, 5.037 + 90, 0)
else:
# 右手控制器绕Y轴俯仰+90度保持不变
steamvr_model.setHpr(0, 5.037 + 90, 0)
# 设置合适的缩放值
steamvr_model.setScale(1.0)
# 打印实际应用的旋转值
if self.controller.name == 'left':
print(f"🔧 {self.controller.name}手柄:缩放: 1.0,旋转: (0, {5.037 + 90}, 0) [Y轴俯仰+90度]")
else:
print(f"🔧 {self.controller.name}手柄:缩放: 1.0,旋转: (0, {5.037 + 90}, 0) [Y轴俯仰+90度]")
# 修复纯黑色问题:重新设置材质属性
self._fix_model_material(steamvr_model)
# 暂时注释身份标记功能,避免额外几何体造成悬空零件
# self._apply_controller_identity_marker(steamvr_model)
# 设置手柄始终显示在上层
self._set_always_on_top(steamvr_model)
print(f"{self.controller.name}手柄已加载SteamVR官方模型缩放: 1.0,实体渲染模式)")
else:
# 降级到改进的程序化模型
self._create_fallback_model()
print(f"⚠️ {self.controller.name}手柄使用程序化模型未找到SteamVR模型")
def _load_steamvr_model(self):
"""加载SteamVR官方手柄模型"""
import os
from panda3d.core import Filename, Texture, TextureStage
# SteamVR模型基础路径
steamvr_base_paths = [
"/home/hello/.local/share/Steam/steamapps/common/SteamVR/resources/rendermodels/vr_controller_vive_1_5",
"/home/hello/.steam/steam/steamapps/common/SteamVR/resources/rendermodels/vr_controller_vive_1_5",
"~/.local/share/Steam/steamapps/common/SteamVR/resources/rendermodels/vr_controller_vive_1_5"
]
for base_path in steamvr_base_paths:
expanded_base_path = os.path.expanduser(base_path)
if os.path.exists(expanded_base_path):
print(f"🔍 找到SteamVR模型目录: {expanded_base_path}")
# 不再添加目录到搜索路径,避免自动加载多余组件
# from panda3d.core import getModelPath
# getModelPath().appendDirectory(expanded_base_path)
# 尝试加载不同的模型文件,按优先级排序
model_files = [
("body.obj", "手柄主体"), # 最重要的部分
("vr_controller_vive_1_5.obj", "完整手柄模型"), # 组合模型
]
for model_file, description in model_files:
model_path = os.path.join(expanded_base_path, model_file)
if os.path.exists(model_path):
try:
print(f"🎮 尝试加载{description}: {model_file}")
# 加载主模型
model = loader.loadModel(Filename.fromOsSpecific(model_path))
if model:
# 先应用纹理,再修复材质(保持纹理效果)
self._apply_steamvr_textures(model, expanded_base_path)
print(f"✅ 成功加载{description}")
return model
else:
print(f"⚠️ 模型文件存在但加载失败: {model_file}")
except Exception as e:
print(f"❌ 加载{description}失败: {e}")
continue
# 不再尝试组合加载多个部件,避免悬空零件问题
print("⚠️ 单个模型文件加载失败,跳过组合加载以避免悬空零件")
break
print("❌ 未找到任何SteamVR模型目录")
return None
def _fix_model_material(self, model):
"""修复模型材质,解决纯黑色问题同时保持纹理"""
from panda3d.core import Material, MaterialAttrib
# 检查模型是否有纹理
has_texture = model.hasTexture()
# 创建新的材质
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}手柄:已修复材质(保持纹理效果)")
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}手柄:已修复材质(使用颜色)")
# 应用材质到模型
model.setMaterial(material)
# 确保模型能正确渲染
model.setTwoSided(False)
def _apply_controller_identity_marker(self, model):
"""为控制器添加身份标记,区分左右手"""
from panda3d.core import RenderModeAttrib
# 创建一个小的标识几何体
marker_geom = self._create_box_geometry(0.005, 0.005, 0.02)
marker_node = model.attachNewNode(marker_geom)
# 根据左右手设置不同位置和颜色现在都是Y轴+90度俯仰
if self.controller.name == 'left':
# 左手控制器:左侧标记
marker_node.setPos(-0.03, 0.05, 0.02)
marker_node.setColor(0.2, 0.4, 1.0, 1.0) # 蓝色
print(f"🔵 {self.controller.name}手柄已添加蓝色身份标记")
else:
# 右手控制器:右侧标记
marker_node.setPos(0.03, 0.05, 0.02)
marker_node.setColor(1.0, 0.2, 0.2, 1.0) # 红色
print(f"🔴 {self.controller.name}手柄已添加红色身份标记")
# 让标记发光以便更容易看到
marker_node.setLightOff() # 不受光照影响,保持明亮
# 添加轻微的色彩调整(非常微弱,不影响主要纹理)
if self.controller.name == 'left':
model.setColorScale(0.98, 0.98, 1.02, 1.0) # 极轻微的蓝色调
else:
model.setColorScale(1.02, 0.98, 0.98, 1.0) # 极轻微的红色调
def _apply_steamvr_textures(self, model, base_path):
"""为SteamVR模型应用纹理"""
import os
from panda3d.core import Texture, TextureStage
# SteamVR纹理文件
texture_files = {
'diffuse': 'onepointfive_texture.png',
'specular': 'onepointfive_spec.png'
}
textures_applied = 0
for texture_type, texture_file in texture_files.items():
texture_path = os.path.join(base_path, texture_file)
if os.path.exists(texture_path):
try:
texture = loader.loadTexture(texture_path)
if texture:
# 确保纹理能正确加载
texture.setWrapU(Texture.WMClamp)
texture.setWrapV(Texture.WMClamp)
texture.setMinfilter(Texture.FTLinearMipmapLinear)
texture.setMagfilter(Texture.FTLinear)
if texture_type == 'diffuse':
# 应用主要漫反射纹理
model.setTexture(texture)
print(f"✅ 应用了主纹理: {texture_file}")
textures_applied += 1
elif texture_type == 'specular':
# 应用高光纹理
ts = TextureStage('specular')
ts.setMode(TextureStage.MModulate)
model.setTexture(ts, texture)
print(f"✅ 应用了高光纹理: {texture_file}")
textures_applied += 1
except Exception as e:
print(f"⚠️ 应用纹理失败 {texture_file}: {e}")
if textures_applied == 0:
print(f"⚠️ {self.controller.name}手柄未能加载任何纹理,将使用材质颜色")
else:
print(f"🎨 {self.controller.name}手柄成功应用了 {textures_applied} 个纹理")
def _load_combined_steamvr_model(self, base_path):
"""尝试组合加载多个SteamVR模型部件"""
import os
from panda3d.core import NodePath
# 重要的模型部件
important_components = [
"body.obj",
"trigger.obj",
"trackpad.obj",
"l_grip.obj" if self.controller.name == 'left' else "r_grip.obj"
]
combined_model = NodePath("combined_controller")
has_components = False
for component in important_components:
component_path = os.path.join(base_path, component)
if os.path.exists(component_path):
try:
part = loader.loadModel(component_path)
if part:
part.reparentTo(combined_model)
has_components = True
print(f"✅ 加载了部件: {component}")
except Exception as e:
print(f"⚠️ 加载部件失败 {component}: {e}")
if has_components:
self._apply_steamvr_textures(combined_model, base_path)
return combined_model
return None
def _create_fallback_model(self):
"""创建改进的程序化手柄模型作为后备方案"""
# 主体(长条形状)
main_body = self._create_box_geometry(0.025, 0.15, 0.04)
main_node = self.model_node.attachNewNode(main_body)
# 根据左右手设置不同颜色
if self.controller.name == 'left':
color = self.button_colors['left']
else:
color = self.button_colors['right']
main_node.setColor(color)
# 启用光照响应
from panda3d.core import RenderState, MaterialAttrib, Material
material = Material()
material.setShininess(32)
material.setAmbient((0.2, 0.2, 0.2, 1))
material.setDiffuse(color)
material.setSpecular((0.5, 0.5, 0.5, 1))
main_node.setMaterial(material)
# 扳机区域(小突起)
trigger = self._create_box_geometry(0.015, 0.03, 0.02)
trigger_node = self.model_node.attachNewNode(trigger)
trigger_node.setPos(0, -0.08, 0.03)
trigger_node.setColor(self.button_colors['trigger'])
trigger_node.setMaterial(material)
# 握把区域
grip = self._create_box_geometry(0.02, 0.06, 0.03)
grip_node = self.model_node.attachNewNode(grip)
grip_node.setPos(0, 0.05, -0.03)
grip_node.setColor(self.button_colors['grip'])
grip_node.setMaterial(material)
# 触摸板区域(圆盘)
trackpad = self._create_disc_geometry(0.015, 0.005)
trackpad_node = self.model_node.attachNewNode(trackpad)
trackpad_node.setPos(0, -0.02, 0.04)
trackpad_node.setColor(self.button_colors['trackpad'])
trackpad_node.setMaterial(material)
def _create_box_geometry(self, width, length, height):
"""创建立方体几何体"""
# 创建顶点格式
format = GeomVertexFormat.getV3n3()
vdata = GeomVertexData('box', format, Geom.UHStatic)
vertex = GeomVertexWriter(vdata, 'vertex')
normal = GeomVertexWriter(vdata, 'normal')
# 定义立方体的8个顶点
vertices = [
Vec3(-width/2, -length/2, -height/2),
Vec3( width/2, -length/2, -height/2),
Vec3( width/2, length/2, -height/2),
Vec3(-width/2, length/2, -height/2),
Vec3(-width/2, -length/2, height/2),
Vec3( width/2, -length/2, height/2),
Vec3( width/2, length/2, height/2),
Vec3(-width/2, length/2, height/2)
]
# 立方体的6个面每个面4个顶点
faces = [
# 底面 (z = -height/2)
[0, 1, 2, 3, Vec3(0, 0, -1)],
# 顶面 (z = height/2)
[7, 6, 5, 4, Vec3(0, 0, 1)],
# 前面 (y = -length/2)
[4, 5, 1, 0, Vec3(0, -1, 0)],
# 后面 (y = length/2)
[3, 2, 6, 7, Vec3(0, 1, 0)],
# 左面 (x = -width/2)
[0, 3, 7, 4, Vec3(-1, 0, 0)],
# 右面 (x = width/2)
[5, 6, 2, 1, Vec3(1, 0, 0)]
]
# 添加顶点和法线
for face in faces:
for i in range(4):
vertex.addData3(vertices[face[i]])
normal.addData3(face[4]) # 法线向量
# 创建几何体
geom = Geom(vdata)
# 为每个面创建三角形
for face_idx in range(6):
base_idx = face_idx * 4
prim = GeomTriangles(Geom.UHStatic)
# 第一个三角形
prim.addVertices(base_idx, base_idx + 1, base_idx + 2)
# 第二个三角形
prim.addVertices(base_idx, base_idx + 2, base_idx + 3)
geom.addPrimitive(prim)
# 创建几何体节点
geom_node = GeomNode('box')
geom_node.addGeom(geom)
return geom_node
def _create_disc_geometry(self, radius, thickness):
"""创建圆盘几何体(用于触摸板)"""
format = GeomVertexFormat.getV3n3()
vdata = GeomVertexData('disc', format, Geom.UHStatic)
vertex = GeomVertexWriter(vdata, 'vertex')
normal = GeomVertexWriter(vdata, 'normal')
# 创建圆盘顶点
segments = 16
import math
# 中心点
vertex.addData3(0, 0, thickness/2)
normal.addData3(0, 0, 1)
# 圆周点
for i in range(segments):
angle = 2 * math.pi * i / segments
x = radius * math.cos(angle)
y = radius * math.sin(angle)
vertex.addData3(x, y, thickness/2)
normal.addData3(0, 0, 1)
# 创建几何体
geom = Geom(vdata)
prim = GeomTriangles(Geom.UHStatic)
# 创建扇形三角形
for i in range(segments):
next_i = (i + 1) % segments
prim.addVertices(0, i + 1, next_i + 1)
geom.addPrimitive(prim)
# 创建几何体节点
geom_node = GeomNode('disc')
geom_node.addGeom(geom)
return geom_node
def _create_interaction_ray(self):
"""创建交互射线"""
if not self.visual_node:
return
# 创建射线几何
line_segs = LineSegs()
line_segs.setThickness(3)
line_segs.setColor(self.ray_color)
# 射线主体
line_segs.moveTo(0, 0, 0)
line_segs.drawTo(0, self.ray_length, 0)
# 射线端点(小球)
end_point = self._create_sphere_geometry(0.02)
# 创建射线节点
geom_node = line_segs.create()
self.ray_node = self.visual_node.attachNewNode(geom_node)
# 添加端点球
end_node = self.ray_node.attachNewNode(end_point)
end_node.setPos(0, self.ray_length, 0)
end_node.setColor(self.ray_color)
# 设置透明度
self.ray_node.setTransparency(TransparencyAttrib.MAlpha)
# 默认隐藏射线
self.ray_node.hide()
print(f"{self.controller.name}手柄交互射线已创建")
def _create_sphere_geometry(self, radius):
"""创建球体几何体"""
# 简单的立方体作为球体替代
return self._create_box_geometry(radius, radius, radius)
def _create_button_indicators(self):
"""创建按钮状态指示器"""
if not self.visual_node:
return
# 创建按钮指示器容器
self.button_indicator_node = self.visual_node.attachNewNode(f'{self.controller.name}_indicators')
# 扳机指示器
trigger_indicator = self._create_box_geometry(0.005, 0.01, 0.005)
self.trigger_indicator = self.button_indicator_node.attachNewNode(trigger_indicator)
self.trigger_indicator.setPos(0.02, -0.08, 0.03)
self.trigger_indicator.setColor(0.2, 0.2, 0.2, 1.0)
# 握把指示器
grip_indicator = self._create_box_geometry(0.005, 0.02, 0.005)
self.grip_indicator = self.button_indicator_node.attachNewNode(grip_indicator)
self.grip_indicator.setPos(-0.02, 0.05, -0.03)
self.grip_indicator.setColor(0.2, 0.2, 0.2, 1.0)
# 触摸板指示器
trackpad_indicator = self._create_disc_geometry(0.003, 0.002)
self.trackpad_indicator = self.button_indicator_node.attachNewNode(trackpad_indicator)
self.trackpad_indicator.setPos(0, -0.02, 0.045)
self.trackpad_indicator.setColor(0.2, 0.2, 0.2, 1.0)
print(f"{self.controller.name}手柄按钮指示器已创建")
def update(self):
"""更新可视化状态"""
if not self.controller.is_connected:
self.hide()
return
self.show()
# 更新按钮指示器状态
self._update_button_indicators()
# 更新射线显示状态
self._update_ray_display()
def _update_button_indicators(self):
"""更新按钮指示器状态"""
if not hasattr(self, 'trigger_indicator'):
return
# 扳机指示器
if self.controller.is_trigger_pressed():
self.trigger_indicator.setColor(self.button_colors['trigger'])
# 根据扳机值调整位置
trigger_offset = self.controller.trigger_value * 0.01
self.trigger_indicator.setPos(0.02, -0.08 + trigger_offset, 0.03)
else:
self.trigger_indicator.setColor(0.2, 0.2, 0.2, 1.0)
self.trigger_indicator.setPos(0.02, -0.08, 0.03)
# 握把指示器
if self.controller.is_grip_pressed():
self.grip_indicator.setColor(self.button_colors['grip'])
else:
self.grip_indicator.setColor(0.2, 0.2, 0.2, 1.0)
# 触摸板指示器
if self.controller.touchpad_touched:
self.trackpad_indicator.setColor(self.button_colors['trackpad'])
# 根据触摸位置调整指示器位置
if hasattr(self.controller, 'touchpad_pos'):
offset_x = self.controller.touchpad_pos.x * 0.01
offset_y = self.controller.touchpad_pos.y * 0.01
self.trackpad_indicator.setPos(offset_x, -0.02 + offset_y, 0.045)
else:
self.trackpad_indicator.setColor(0.2, 0.2, 0.2, 1.0)
self.trackpad_indicator.setPos(0, -0.02, 0.045)
def _update_ray_display(self):
"""更新射线显示"""
if not self.ray_node:
return
# 根据交互状态显示/隐藏射线
# 这里可以添加更复杂的逻辑,比如只在指向对象时显示
show_ray = (self.controller.is_trigger_pressed(threshold=0.1) or
self.controller.touchpad_touched)
if show_ray:
self.show_ray()
else:
self.hide_ray()
def show(self):
"""显示手柄可视化"""
if self.visual_node:
self.visual_node.show()
def hide(self):
"""隐藏手柄可视化"""
if self.visual_node:
self.visual_node.hide()
def show_ray(self):
"""显示交互射线"""
if self.ray_node:
self.ray_node.show()
def hide_ray(self):
"""隐藏交互射线"""
if self.ray_node:
self.ray_node.hide()
def set_ray_color(self, color):
"""设置射线颜色"""
if self.ray_node:
self.ray_node.setColor(color)
def set_ray_length(self, length):
"""设置射线长度"""
self.ray_length = length
# 重新创建射线(简单的实现)
if self.ray_node:
self.ray_node.removeNode()
self._create_interaction_ray()
def _set_always_on_top(self, model_node):
"""设置手柄模型始终显示在上层,不被其他物体遮挡"""
if not model_node:
return
from panda3d.core import RenderState
# 设置为固定渲染bin优先级设为较高值1000
# fixed bin中的对象按sort值从小到大渲染越大越后渲染越在上层
model_node.setBin("fixed", 1000)
# 禁用深度测试和深度写入,确保始终可见
model_node.setDepthTest(False)
model_node.setDepthWrite(False)
# 递归设置所有子节点的渲染属性
for child in model_node.findAllMatches("**"):
child.setBin("fixed", 1000)
child.setDepthTest(False)
child.setDepthWrite(False)
print(f"🔝 {self.controller.name}手柄已设置为始终显示在上层")
def cleanup(self):
"""清理资源"""
if self.visual_node:
self.visual_node.removeNode()
print(f"🧹 {self.controller.name}手柄可视化已清理")

89
vr_actions/actions.json Normal file
View File

@ -0,0 +1,89 @@
{
"actions": [
{
"name": "/actions/default/in/Pose",
"type": "pose"
},
{
"name": "/actions/default/in/Trigger",
"type": "boolean"
},
{
"name": "/actions/default/in/Grip",
"type": "boolean"
},
{
"name": "/actions/default/in/Menu",
"type": "boolean"
},
{
"name": "/actions/default/in/System",
"type": "boolean"
},
{
"name": "/actions/default/in/TrackpadClick",
"type": "boolean"
},
{
"name": "/actions/default/in/TrackpadTouch",
"type": "boolean"
},
{
"name": "/actions/default/in/AButton",
"type": "boolean"
},
{
"name": "/actions/default/in/BButton",
"type": "boolean"
},
{
"name": "/actions/default/in/Trackpad",
"type": "vector2"
},
{
"name": "/actions/default/in/Joystick",
"type": "vector2"
},
{
"name": "/actions/default/in/Squeeze",
"type": "vector1"
},
{
"name": "/actions/default/out/Haptic",
"type": "vibration"
}
],
"action_sets": [
{
"name": "/actions/default",
"usage": "single"
}
],
"default_bindings": [
{
"controller_type": "vive_controller",
"binding_url": "bindings_vive.json"
},
{
"controller_type": "oculus_touch",
"binding_url": "bindings_oculus.json"
},
{
"controller_type": "knuckles",
"binding_url": "bindings_index.json"
}
],
"localization": [
{
"language_tag": "zh_CN",
"/actions/default/in/Trigger": "扳机",
"/actions/default/in/Grip": "握把",
"/actions/default/in/Menu": "菜单",
"/actions/default/in/System": "系统",
"/actions/default/in/TrackpadClick": "触摸板点击",
"/actions/default/in/TrackpadTouch": "触摸板触摸",
"/actions/default/in/Pose": "手部姿态",
"/actions/default/out/Haptic": "震动反馈"
}
]
}

View File

@ -0,0 +1,106 @@
{
"controller_type": "vive_controller",
"description": "Vive\u63a7\u5236\u5668\u7ed1\u5b9a",
"name": "EG VR Editor - Vive",
"bindings": {
"/actions/default": {
"sources": [
{
"inputs": {
"click": {
"output": "/actions/default/in/Trigger"
}
},
"mode": "button",
"path": "/user/hand/left/input/trigger"
},
{
"inputs": {
"click": {
"output": "/actions/default/in/Trigger"
}
},
"mode": "button",
"path": "/user/hand/right/input/trigger"
},
{
"inputs": {
"click": {
"output": "/actions/default/in/Grip"
}
},
"mode": "button",
"path": "/user/hand/left/input/grip"
},
{
"inputs": {
"click": {
"output": "/actions/default/in/Grip"
}
},
"mode": "button",
"path": "/user/hand/right/input/grip"
},
{
"inputs": {
"click": {
"output": "/actions/default/in/Menu"
}
},
"mode": "button",
"path": "/user/hand/left/input/menu"
},
{
"inputs": {
"position": {
"output": "/actions/default/in/Trackpad"
},
"click": {
"output": "/actions/default/in/TrackpadClick"
},
"touch": {
"output": "/actions/default/in/TrackpadTouch"
}
},
"mode": "trackpad",
"path": "/user/hand/left/input/trackpad"
},
{
"inputs": {
"position": {
"output": "/actions/default/in/Trackpad"
},
"click": {
"output": "/actions/default/in/TrackpadClick"
},
"touch": {
"output": "/actions/default/in/TrackpadTouch"
}
},
"mode": "trackpad",
"path": "/user/hand/right/input/trackpad"
}
],
"poses": [
{
"output": "/actions/default/in/Pose",
"path": "/user/hand/left/pose/raw"
},
{
"output": "/actions/default/in/Pose",
"path": "/user/hand/right/pose/raw"
}
],
"haptics": [
{
"output": "/actions/default/out/Haptic",
"path": "/user/hand/left/output/haptic"
},
{
"output": "/actions/default/out/Haptic",
"path": "/user/hand/right/output/haptic"
}
]
}
}
}