forked from Rowland/EG
Merge remote-tracking branch 'refs/remotes/origin/main' into addRender
# Conflicts: # RenderPipelineFile/samples/06-Car/main.py # main.py # ui/main_window.py
This commit is contained in:
commit
9633a32c3b
112
CLAUDE.md
Normal file
112
CLAUDE.md
Normal file
@ -0,0 +1,112 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## 项目概述
|
||||
|
||||
这是一个基于Panda3D的3D渲染引擎和场景编辑器,集成了PyQt5界面和多种高级功能:
|
||||
|
||||
- 3D场景编辑器(模型导入、材质系统、碰撞检测)
|
||||
- GUI元素管理(2D/3D GUI组件)
|
||||
- 项目管理系统(场景保存/加载、项目打包)
|
||||
- Cesium地图集成
|
||||
- 渲染管线增强(RenderPipelineFile)
|
||||
|
||||
## 运行和构建命令
|
||||
|
||||
### 启动应用程序
|
||||
```bash
|
||||
python Start_Run.py [project_path]
|
||||
```
|
||||
或者直接:
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
### 依赖安装
|
||||
```bash
|
||||
# 主要依赖
|
||||
pip install -r requirements/requirements.txt
|
||||
|
||||
# Conda环境依赖
|
||||
pip install -r requirements/conda-requirements.txt
|
||||
```
|
||||
|
||||
### 工具脚本
|
||||
```bash
|
||||
# 安装FBX到GLTF转换工具
|
||||
./install_fbx2gltf.sh
|
||||
```
|
||||
|
||||
## 核心架构
|
||||
|
||||
### 主要模块结构
|
||||
```
|
||||
EG/
|
||||
├── main.py # 应用程序入口点
|
||||
├── Start_Run.py # 启动脚本(路径配置)
|
||||
├── core/ # 核心功能模块
|
||||
│ ├── world.py # 3D世界核心(继承Panda3DWorld)
|
||||
│ ├── scene_manager.py # 场景和模型管理
|
||||
│ ├── selection.py # 对象选择系统
|
||||
│ ├── event_handler.py # 事件处理
|
||||
│ └── tool_manager.py # 工具系统
|
||||
├── gui/ # GUI元素管理
|
||||
│ └── gui_manager.py # 2D/3D GUI组件
|
||||
├── ui/ # 用户界面
|
||||
│ ├── widgets.py # 自定义Qt组件
|
||||
│ ├── property_panel.py # 属性面板
|
||||
│ └── interface_manager.py # 界面管理
|
||||
├── scene/ # 场景相关
|
||||
│ └── scene_manager.py # 场景管理器
|
||||
├── project/ # 项目管理
|
||||
│ └── project_manager.py # 项目生命周期
|
||||
├── RenderPipelineFile/ # 渲染管线扩展
|
||||
└── QPanda3D/ # Panda3D Qt集成
|
||||
```
|
||||
|
||||
### 核心设计模式
|
||||
|
||||
1. **模块化架构**: 每个功能模块独立,通过管理器类协调
|
||||
2. **事件驱动**: EventHandler统一处理用户交互和系统事件
|
||||
3. **组件系统**: SelectionSystem、ToolManager等可插拔组件
|
||||
4. **MVC分离**: UI组件、核心逻辑和数据管理分离
|
||||
|
||||
### 主要依赖集成
|
||||
|
||||
- **Panda3D 1.10.15**: 3D渲染引擎
|
||||
- **PyQt5**: GUI框架
|
||||
- **QPanda3D**: Panda3D的Qt集成
|
||||
- **RenderPipeline**: 高级渲染功能
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 添加新功能模块
|
||||
1. 在对应目录下创建新的Python文件
|
||||
2. 继承相应的基类(如Panda3DWorld用于3D功能)
|
||||
3. 在main.py中集成新模块
|
||||
4. 更新界面管理器以添加UI控制
|
||||
|
||||
### 材质和渲染
|
||||
- 材质系统集成在scene/scene_manager.py
|
||||
- 支持PBR材质和自定义着色器
|
||||
- RenderPipelineFile提供高级渲染特性
|
||||
|
||||
|
||||
### GUI开发
|
||||
- 使用PyQt5构建主界面
|
||||
- 3D GUI元素通过gui/gui_manager.py管理
|
||||
- 自定义组件在ui/widgets.py中定义
|
||||
|
||||
## 文件约定
|
||||
|
||||
- Python文件使用UTF-8编码
|
||||
- 中文注释和文档字符串
|
||||
- 模块顶部包含功能描述注释
|
||||
- 类和方法使用描述性命名
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 项目依赖多个大型库(Panda3D、PyQt5、RenderPipeline)
|
||||
- Cesium集成需要WebEngine支持
|
||||
- 某些功能可能需要特定的系统配置
|
||||
@ -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")
|
||||
|
||||
|
||||
@ -53,7 +53,7 @@ class Application(ShowBase):
|
||||
# ------ End of render pipeline code, thats it! ------
|
||||
|
||||
# Set time of day
|
||||
self.render_pipeline.daytime_mgr.time = "12:43"
|
||||
self.render_pipeline.daytime_mgr.time = "6:43"
|
||||
|
||||
# Load the scene
|
||||
model = loader.loadModel("scene/Scene.bam")
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
# Autogenerated
|
||||
name = 'Plastic-R0.0'
|
||||
roughness = 0.0
|
||||
ior = 1.51
|
||||
basecolor = (1, 0, 0)
|
||||
mat_type = 'default'
|
||||
@ -1,509 +0,0 @@
|
||||
"""
|
||||
ALVR串流处理器
|
||||
|
||||
负责与ALVR服务器通信和视频流传输
|
||||
支持Quest等VR头显的无线串流
|
||||
"""
|
||||
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
import json
|
||||
import time
|
||||
import subprocess
|
||||
import psutil
|
||||
from direct.showbase.DirectObject import DirectObject
|
||||
from panda3d.core import Texture, PNMImage
|
||||
|
||||
|
||||
class ALVRStreamer(DirectObject):
|
||||
"""ALVR串流处理器"""
|
||||
|
||||
def __init__(self, world, vr_manager):
|
||||
super().__init__()
|
||||
self.world = world
|
||||
self.vr_manager = vr_manager
|
||||
|
||||
# ALVR服务器配置
|
||||
self.alvr_server_ip = "127.0.0.1"
|
||||
self.alvr_server_port = 9943
|
||||
self.alvr_streaming_port = 9944
|
||||
|
||||
# 连接状态
|
||||
self.connected = False
|
||||
self.streaming = False
|
||||
self.server_socket = None
|
||||
self.streaming_socket = None
|
||||
|
||||
# 流媒体配置
|
||||
self.stream_width = 2880 # Quest 2 推荐分辨率
|
||||
self.stream_height = 1700
|
||||
self.stream_fps = 72
|
||||
self.bitrate = 150 # Mbps
|
||||
self.codec = "h264"
|
||||
|
||||
# 线程管理
|
||||
self.connection_thread = None
|
||||
self.streaming_thread = None
|
||||
self.running = False
|
||||
|
||||
# 性能统计
|
||||
self.frame_count = 0
|
||||
self.last_fps_time = time.time()
|
||||
self.current_fps = 0
|
||||
self.latency = 0
|
||||
|
||||
print("✓ ALVR串流处理器初始化完成")
|
||||
|
||||
def initialize(self):
|
||||
"""初始化ALVR串流"""
|
||||
try:
|
||||
# 检查ALVR服务器是否运行
|
||||
if not self._check_alvr_server():
|
||||
print("ALVR服务器未运行,尝试启动...")
|
||||
if not self._start_alvr_server():
|
||||
print("无法启动ALVR服务器")
|
||||
return False
|
||||
|
||||
# 连接到ALVR服务器
|
||||
if not self._connect_to_server():
|
||||
print("无法连接到ALVR服务器")
|
||||
return False
|
||||
|
||||
# 配置流媒体设置
|
||||
self._configure_streaming()
|
||||
|
||||
# 启动串流线程
|
||||
self._start_streaming_threads()
|
||||
|
||||
print("✓ ALVR串流初始化成功")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"ALVR初始化错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _check_alvr_server(self):
|
||||
"""检查ALVR服务器是否运行"""
|
||||
try:
|
||||
# 检查进程
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
|
||||
if 'alvr' in proc.info['name'].lower():
|
||||
return True
|
||||
|
||||
# 尝试连接端口
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(2)
|
||||
result = sock.connect_ex((self.alvr_server_ip, self.alvr_server_port))
|
||||
sock.close()
|
||||
|
||||
return result == 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"检查ALVR服务器错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _start_alvr_server(self):
|
||||
"""启动ALVR服务器"""
|
||||
try:
|
||||
# 尝试启动ALVR服务器
|
||||
# 这里需要根据实际的ALVR安装路径调整
|
||||
alvr_paths = [
|
||||
"/usr/local/bin/alvr_server",
|
||||
"/usr/bin/alvr_server",
|
||||
"C:/Program Files/ALVR/alvr_server.exe",
|
||||
"C:/ALVR/alvr_server.exe"
|
||||
]
|
||||
|
||||
for path in alvr_paths:
|
||||
try:
|
||||
subprocess.Popen([path], shell=True)
|
||||
time.sleep(3) # 等待服务器启动
|
||||
if self._check_alvr_server():
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"启动ALVR服务器错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _connect_to_server(self):
|
||||
"""连接到ALVR服务器"""
|
||||
try:
|
||||
# 创建TCP连接
|
||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.server_socket.settimeout(5)
|
||||
self.server_socket.connect((self.alvr_server_ip, self.alvr_server_port))
|
||||
|
||||
# 发送握手消息
|
||||
handshake_data = {
|
||||
"type": "handshake",
|
||||
"client_name": "Panda3D_VR_Engine",
|
||||
"version": "1.0",
|
||||
"capabilities": {
|
||||
"video": True,
|
||||
"audio": True,
|
||||
"tracking": True,
|
||||
"haptics": True
|
||||
}
|
||||
}
|
||||
|
||||
self._send_message(handshake_data)
|
||||
|
||||
# 接收响应
|
||||
response = self._receive_message()
|
||||
if response and response.get("type") == "handshake_response":
|
||||
self.connected = True
|
||||
print("✓ 已连接到ALVR服务器")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"连接ALVR服务器错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _send_message(self, data):
|
||||
"""发送消息到ALVR服务器"""
|
||||
try:
|
||||
message = json.dumps(data).encode('utf-8')
|
||||
length = struct.pack('<I', len(message))
|
||||
self.server_socket.send(length + message)
|
||||
except Exception as e:
|
||||
print(f"发送消息错误: {str(e)}")
|
||||
|
||||
def _receive_message(self):
|
||||
"""从ALVR服务器接收消息"""
|
||||
try:
|
||||
# 接收消息长度
|
||||
length_data = self.server_socket.recv(4)
|
||||
if not length_data:
|
||||
return None
|
||||
|
||||
length = struct.unpack('<I', length_data)[0]
|
||||
|
||||
# 接收消息内容
|
||||
message_data = b''
|
||||
while len(message_data) < length:
|
||||
chunk = self.server_socket.recv(length - len(message_data))
|
||||
if not chunk:
|
||||
return None
|
||||
message_data += chunk
|
||||
|
||||
return json.loads(message_data.decode('utf-8'))
|
||||
|
||||
except Exception as e:
|
||||
print(f"接收消息错误: {str(e)}")
|
||||
return None
|
||||
|
||||
def _configure_streaming(self):
|
||||
"""配置流媒体设置"""
|
||||
try:
|
||||
# 发送流媒体配置
|
||||
config_data = {
|
||||
"type": "stream_config",
|
||||
"video": {
|
||||
"width": self.stream_width,
|
||||
"height": self.stream_height,
|
||||
"fps": self.stream_fps,
|
||||
"bitrate": self.bitrate,
|
||||
"codec": self.codec
|
||||
},
|
||||
"audio": {
|
||||
"enabled": True,
|
||||
"sample_rate": 48000,
|
||||
"channels": 2
|
||||
}
|
||||
}
|
||||
|
||||
self._send_message(config_data)
|
||||
|
||||
# 接收配置响应
|
||||
response = self._receive_message()
|
||||
if response and response.get("type") == "config_response":
|
||||
if response.get("status") == "success":
|
||||
print("✓ 流媒体配置成功")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"配置流媒体错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _start_streaming_threads(self):
|
||||
"""启动串流线程"""
|
||||
self.running = True
|
||||
|
||||
# 启动连接管理线程
|
||||
self.connection_thread = threading.Thread(target=self._connection_handler)
|
||||
self.connection_thread.daemon = True
|
||||
self.connection_thread.start()
|
||||
|
||||
# 启动流媒体线程
|
||||
self.streaming_thread = threading.Thread(target=self._streaming_handler)
|
||||
self.streaming_thread.daemon = True
|
||||
self.streaming_thread.start()
|
||||
|
||||
def _connection_handler(self):
|
||||
"""连接处理线程"""
|
||||
while self.running:
|
||||
try:
|
||||
if self.connected:
|
||||
# 发送心跳
|
||||
heartbeat = {"type": "heartbeat", "timestamp": time.time()}
|
||||
self._send_message(heartbeat)
|
||||
|
||||
# 接收消息
|
||||
response = self._receive_message()
|
||||
if response:
|
||||
self._handle_server_message(response)
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"连接处理错误: {str(e)}")
|
||||
self.connected = False
|
||||
time.sleep(1)
|
||||
|
||||
def _streaming_handler(self):
|
||||
"""流媒体处理线程"""
|
||||
while self.running:
|
||||
try:
|
||||
if self.connected and self.streaming:
|
||||
# 获取VR渲染帧
|
||||
frame_data = self._get_vr_frame()
|
||||
if frame_data:
|
||||
# 发送帧数据
|
||||
self._send_frame(frame_data)
|
||||
|
||||
# 更新性能统计
|
||||
self._update_performance_stats()
|
||||
|
||||
time.sleep(1.0 / self.stream_fps)
|
||||
|
||||
except Exception as e:
|
||||
print(f"流媒体处理错误: {str(e)}")
|
||||
time.sleep(0.1)
|
||||
|
||||
def _handle_server_message(self, message):
|
||||
"""处理服务器消息"""
|
||||
msg_type = message.get("type")
|
||||
|
||||
if msg_type == "start_streaming":
|
||||
self.streaming = True
|
||||
print("✓ 开始VR串流")
|
||||
|
||||
elif msg_type == "stop_streaming":
|
||||
self.streaming = False
|
||||
print("✓ 停止VR串流")
|
||||
|
||||
elif msg_type == "client_connected":
|
||||
print(f"✓ VR客户端已连接: {message.get('client_info', {})}")
|
||||
|
||||
elif msg_type == "client_disconnected":
|
||||
print("✓ VR客户端已断开")
|
||||
|
||||
elif msg_type == "tracking_data":
|
||||
self._handle_tracking_data(message.get("data"))
|
||||
|
||||
elif msg_type == "haptic_feedback":
|
||||
self._handle_haptic_feedback(message.get("data"))
|
||||
|
||||
def _handle_tracking_data(self, tracking_data):
|
||||
"""处理跟踪数据"""
|
||||
if not tracking_data:
|
||||
return
|
||||
|
||||
# 更新VR管理器的跟踪数据
|
||||
# 这里可以处理从ALVR客户端发送的跟踪数据
|
||||
pass
|
||||
|
||||
def _handle_haptic_feedback(self, haptic_data):
|
||||
"""处理触觉反馈"""
|
||||
if not haptic_data:
|
||||
return
|
||||
|
||||
# 处理触觉反馈请求
|
||||
# 这里可以控制VR控制器的震动等
|
||||
pass
|
||||
|
||||
def _get_vr_frame(self):
|
||||
"""获取VR渲染帧"""
|
||||
try:
|
||||
if not self.vr_manager.is_vr_enabled():
|
||||
return None
|
||||
|
||||
# 获取左右眼纹理
|
||||
left_texture = self.vr_manager.eye_textures.get('left')
|
||||
right_texture = self.vr_manager.eye_textures.get('right')
|
||||
|
||||
if not left_texture or not right_texture:
|
||||
return None
|
||||
|
||||
# 合成立体帧
|
||||
frame_data = self._compose_stereo_frame(left_texture, right_texture)
|
||||
return frame_data
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取VR帧错误: {str(e)}")
|
||||
return None
|
||||
|
||||
def _compose_stereo_frame(self, left_texture, right_texture):
|
||||
"""合成立体帧"""
|
||||
try:
|
||||
# 创建组合图像
|
||||
combined_image = PNMImage(self.stream_width, self.stream_height)
|
||||
|
||||
# 获取左右眼图像
|
||||
left_image = PNMImage()
|
||||
right_image = PNMImage()
|
||||
|
||||
if left_texture.store(left_image) and right_texture.store(right_image):
|
||||
# 将左右眼图像合并(Side-by-Side布局)
|
||||
left_width = self.stream_width // 2
|
||||
|
||||
# 缩放左眼图像到左半部分
|
||||
left_scaled = PNMImage(left_width, self.stream_height)
|
||||
left_scaled.quickFilterFrom(left_image)
|
||||
combined_image.copySubImage(left_scaled, 0, 0)
|
||||
|
||||
# 缩放右眼图像到右半部分
|
||||
right_scaled = PNMImage(left_width, self.stream_height)
|
||||
right_scaled.quickFilterFrom(right_image)
|
||||
combined_image.copySubImage(right_scaled, left_width, 0)
|
||||
|
||||
# 转换为字节数据
|
||||
return combined_image.makeRamImage()
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"合成立体帧错误: {str(e)}")
|
||||
return None
|
||||
|
||||
def _send_frame(self, frame_data):
|
||||
"""发送帧数据"""
|
||||
try:
|
||||
if not self.server_socket:
|
||||
return
|
||||
|
||||
# 创建帧消息
|
||||
frame_message = {
|
||||
"type": "video_frame",
|
||||
"timestamp": time.time(),
|
||||
"width": self.stream_width,
|
||||
"height": self.stream_height,
|
||||
"format": "rgb",
|
||||
"data": frame_data.hex() # 转换为十六进制字符串
|
||||
}
|
||||
|
||||
self._send_message(frame_message)
|
||||
|
||||
except Exception as e:
|
||||
print(f"发送帧错误: {str(e)}")
|
||||
|
||||
def _update_performance_stats(self):
|
||||
"""更新性能统计"""
|
||||
self.frame_count += 1
|
||||
current_time = time.time()
|
||||
|
||||
if current_time - self.last_fps_time >= 1.0:
|
||||
self.current_fps = self.frame_count
|
||||
self.frame_count = 0
|
||||
self.last_fps_time = current_time
|
||||
|
||||
def start_streaming(self):
|
||||
"""开始串流"""
|
||||
if not self.connected:
|
||||
print("未连接到ALVR服务器")
|
||||
return False
|
||||
|
||||
start_message = {"type": "start_streaming"}
|
||||
self._send_message(start_message)
|
||||
return True
|
||||
|
||||
def stop_streaming(self):
|
||||
"""停止串流"""
|
||||
if not self.connected:
|
||||
return
|
||||
|
||||
stop_message = {"type": "stop_streaming"}
|
||||
self._send_message(stop_message)
|
||||
self.streaming = False
|
||||
|
||||
def send_haptic_feedback(self, controller_id, duration, intensity):
|
||||
"""发送触觉反馈"""
|
||||
if not self.connected:
|
||||
return
|
||||
|
||||
haptic_message = {
|
||||
"type": "haptic_feedback",
|
||||
"controller_id": controller_id,
|
||||
"duration": duration,
|
||||
"intensity": intensity
|
||||
}
|
||||
|
||||
self._send_message(haptic_message)
|
||||
|
||||
def get_streaming_status(self):
|
||||
"""获取串流状态"""
|
||||
return {
|
||||
"connected": self.connected,
|
||||
"streaming": self.streaming,
|
||||
"fps": self.current_fps,
|
||||
"latency": self.latency,
|
||||
"resolution": f"{self.stream_width}x{self.stream_height}",
|
||||
"bitrate": self.bitrate
|
||||
}
|
||||
|
||||
def set_stream_quality(self, width, height, fps, bitrate):
|
||||
"""设置串流质量"""
|
||||
self.stream_width = width
|
||||
self.stream_height = height
|
||||
self.stream_fps = fps
|
||||
self.bitrate = bitrate
|
||||
|
||||
# 如果正在串流,重新配置
|
||||
if self.streaming:
|
||||
self._configure_streaming()
|
||||
|
||||
def shutdown(self):
|
||||
"""关闭串流"""
|
||||
print("关闭ALVR串流...")
|
||||
|
||||
self.running = False
|
||||
self.streaming = False
|
||||
|
||||
# 发送断开消息
|
||||
if self.connected:
|
||||
disconnect_message = {"type": "disconnect"}
|
||||
self._send_message(disconnect_message)
|
||||
|
||||
# 关闭连接
|
||||
if self.server_socket:
|
||||
self.server_socket.close()
|
||||
|
||||
if self.streaming_socket:
|
||||
self.streaming_socket.close()
|
||||
|
||||
# 等待线程结束
|
||||
if self.connection_thread:
|
||||
self.connection_thread.join(timeout=2)
|
||||
|
||||
if self.streaming_thread:
|
||||
self.streaming_thread.join(timeout=2)
|
||||
|
||||
self.connected = False
|
||||
print("✓ ALVR串流已关闭")
|
||||
|
||||
def is_connected(self):
|
||||
"""检查是否连接"""
|
||||
return self.connected
|
||||
|
||||
def is_streaming(self):
|
||||
"""检查是否在串流"""
|
||||
return self.streaming
|
||||
583
core/vr_actions.py
Normal file
583
core/vr_actions.py
Normal 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)对于vector2,float对于vector1
|
||||
"""
|
||||
if not self.vr_input or action_name not in self.action_handles:
|
||||
return None, None
|
||||
|
||||
try:
|
||||
action_handle = self.action_handles[action_name]
|
||||
device_handle = openvr.k_ulInvalidInputValueHandle
|
||||
|
||||
if device_path:
|
||||
device_handle = self.vr_input.getInputSourceHandle(device_path)
|
||||
|
||||
analog_data = self.vr_input.getAnalogActionData(action_handle, device_handle)
|
||||
|
||||
if device_path and analog_data.bActive:
|
||||
origin_info = self.vr_input.getOriginTrackedDeviceInfo(analog_data.activeOrigin)
|
||||
device_path = origin_info.devicePath
|
||||
|
||||
if analog_data.bActive:
|
||||
# 根据动作类型返回适当的值
|
||||
from panda3d.core import Vec2
|
||||
if action_name in ['trackpad', 'joystick']:
|
||||
return Vec2(analog_data.x, analog_data.y), device_path
|
||||
else:
|
||||
return analog_data.x, device_path
|
||||
|
||||
return None, device_path
|
||||
|
||||
except Exception as e:
|
||||
return None, None
|
||||
|
||||
def get_pose_action_data(self, action_name, device_path=None):
|
||||
"""获取姿态动作数据"""
|
||||
if not self.vr_input or action_name not in self.action_handles:
|
||||
return None
|
||||
|
||||
try:
|
||||
action_handle = self.action_handles[action_name]
|
||||
device_handle = openvr.k_ulInvalidInputValueHandle
|
||||
|
||||
if device_path:
|
||||
device_handle = self.vr_input.getInputSourceHandle(device_path)
|
||||
|
||||
pose_data = self.vr_input.getPoseActionDataForNextFrame(
|
||||
action_handle,
|
||||
openvr.TrackingUniverseStanding,
|
||||
device_handle
|
||||
)
|
||||
|
||||
return pose_data
|
||||
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
def trigger_haptic_pulse(self, action_name, duration=0.001, frequency=1.0, amplitude=1.0, device_path=None):
|
||||
"""触发震动反馈
|
||||
|
||||
Args:
|
||||
action_name: 震动动作名称
|
||||
duration: 持续时间(秒)
|
||||
frequency: 频率
|
||||
amplitude: 振幅 (0.0-1.0)
|
||||
device_path: 设备路径(可选)
|
||||
"""
|
||||
if not self.vr_input or action_name not in self.action_handles:
|
||||
return False
|
||||
|
||||
try:
|
||||
action_handle = self.action_handles[action_name]
|
||||
device_handle = openvr.k_ulInvalidInputValueHandle
|
||||
|
||||
if device_path:
|
||||
device_handle = self.vr_input.getInputSourceHandle(device_path)
|
||||
|
||||
# 触发震动
|
||||
self.vr_input.triggerHapticVibrationAction(
|
||||
action_handle,
|
||||
0, # 开始时间
|
||||
duration,
|
||||
frequency,
|
||||
amplitude,
|
||||
device_handle
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ 触发震动反馈失败: {e}")
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
"""清理资源"""
|
||||
self.ignoreAll()
|
||||
|
||||
# 清理动作句柄
|
||||
self.action_handles.clear()
|
||||
self.action_set_handles.clear()
|
||||
|
||||
print("🧹 VR动作管理器已清理")
|
||||
432
core/vr_controller.py
Normal file
432
core/vr_controller.py
Normal file
@ -0,0 +1,432 @@
|
||||
"""
|
||||
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
|
||||
|
||||
# 摇杆状态 - 用于传送和转向交互
|
||||
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
|
||||
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()
|
||||
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
|
||||
# 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)
|
||||
|
||||
# 扳机轴通常在axis[1].x
|
||||
if len(axis_data) > 1:
|
||||
self.trigger_value = axis_data[1].x
|
||||
|
||||
# 触摸板/摇杆轴通常在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)
|
||||
|
||||
# 额外检查其他轴(某些控制器可能将摇杆分配到不同轴)
|
||||
if len(axis_data) > 2:
|
||||
# 有些控制器可能在axis[2]有摇杆数据
|
||||
axis2_magnitude = abs(axis_data[2].x) + abs(axis_data[2].y)
|
||||
if axis2_magnitude > 0.1: # 如果有显著输入,使用这个轴作为摇杆
|
||||
self.joystick_pos = Vec3(axis_data[2].x, axis_data[2].y, 0)
|
||||
|
||||
# 检查axis[3]和axis[4](Quest控制器可能使用这些轴)
|
||||
for axis_idx in range(3, min(len(axis_data), 5)):
|
||||
axis_magnitude = abs(axis_data[axis_idx].x) + abs(axis_data[axis_idx].y)
|
||||
if axis_magnitude > 0.1: # 如果有显著输入
|
||||
# 覆盖之前的摇杆数据,使用最有活动的轴
|
||||
self.joystick_pos = Vec3(axis_data[axis_idx].x, axis_data[axis_idx].y, 0)
|
||||
# 调试输出
|
||||
if not hasattr(self, '_last_axis_notify'):
|
||||
self._last_axis_notify = {}
|
||||
if self._last_axis_notify.get(axis_idx, 0) == 0:
|
||||
print(f"🎮 {self.name}手检测到axis[{axis_idx}]活动: ({axis_data[axis_idx].x:.3f}, {axis_data[axis_idx].y:.3f})")
|
||||
self._last_axis_notify[axis_idx] = 60 # 60帧后再次提醒
|
||||
else:
|
||||
self._last_axis_notify[axis_idx] -= 1
|
||||
|
||||
# 触摸板和摇杆触摸状态
|
||||
self.touchpad_touched = (state.ulButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 0
|
||||
|
||||
# 摇杆触摸状态(检查多个可能的按钮)
|
||||
joystick_touch_mask = 0
|
||||
if hasattr(openvr, 'k_EButton_Joystick'):
|
||||
joystick_touch_mask |= (1 << openvr.k_EButton_Joystick)
|
||||
if hasattr(openvr, 'k_EButton_Thumbstick'):
|
||||
joystick_touch_mask |= (1 << openvr.k_EButton_Thumbstick)
|
||||
|
||||
self.joystick_touched = (state.ulButtonTouched & joystick_touch_mask) != 0 or self.touchpad_touched
|
||||
|
||||
# 摇杆按下状态
|
||||
joystick_press_mask = 0
|
||||
if hasattr(openvr, 'k_EButton_Joystick'):
|
||||
joystick_press_mask |= (1 << openvr.k_EButton_Joystick)
|
||||
if hasattr(openvr, 'k_EButton_Thumbstick'):
|
||||
joystick_press_mask |= (1 << openvr.k_EButton_Thumbstick)
|
||||
|
||||
self.joystick_pressed = (state.ulButtonPressed & joystick_press_mask) != 0
|
||||
|
||||
except Exception as e:
|
||||
# 减少错误输出频率
|
||||
if not hasattr(self, '_last_input_error_frame'):
|
||||
self._last_input_error_frame = 0
|
||||
|
||||
# 获取当前帧数(通过VR管理器)
|
||||
current_frame = getattr(self.vr_manager, 'frame_count', 0)
|
||||
|
||||
# 每5秒最多输出一次错误(300帧@60fps)
|
||||
if current_frame - self._last_input_error_frame > 300:
|
||||
print(f"⚠️ 更新{self.name}手柄输入状态失败: {e}")
|
||||
self._last_input_error_frame = current_frame
|
||||
|
||||
def is_button_pressed(self, button_id):
|
||||
"""检查按钮是否被按下"""
|
||||
return self.button_states.get(button_id, False)
|
||||
|
||||
def is_button_just_pressed(self, button_id):
|
||||
"""检查按钮是否刚刚被按下(上升沿)"""
|
||||
current = self.button_states.get(button_id, False)
|
||||
previous = self.previous_button_states.get(button_id, False)
|
||||
return current and not previous
|
||||
|
||||
def is_button_just_released(self, button_id):
|
||||
"""检查按钮是否刚刚被释放(下降沿)"""
|
||||
current = self.button_states.get(button_id, False)
|
||||
previous = self.previous_button_states.get(button_id, False)
|
||||
return not current and previous
|
||||
|
||||
def is_trigger_pressed(self, threshold=0.1):
|
||||
"""检查扳机是否被按下"""
|
||||
return self.trigger_value > threshold
|
||||
|
||||
def is_grip_pressed(self, threshold=0.1):
|
||||
"""检查握把是否被按下"""
|
||||
return self.grip_value > threshold
|
||||
|
||||
def show_ray(self, show=True):
|
||||
"""显示或隐藏交互射线"""
|
||||
if self.visualizer:
|
||||
if show:
|
||||
self.visualizer.show_ray()
|
||||
else:
|
||||
self.visualizer.hide_ray()
|
||||
|
||||
def set_ray_color(self, color):
|
||||
"""设置射线颜色"""
|
||||
if self.visualizer and len(color) >= 3:
|
||||
from panda3d.core import Vec4
|
||||
color_vec = Vec4(color[0], color[1], color[2], color[3] if len(color) > 3 else 1.0)
|
||||
self.visualizer.set_ray_color(color_vec)
|
||||
|
||||
def trigger_haptic_feedback(self, duration=0.001, strength=1.0):
|
||||
"""触发震动反馈
|
||||
|
||||
Args:
|
||||
duration: 震动持续时间(秒)
|
||||
strength: 震动强度 (0.0-1.0)
|
||||
"""
|
||||
if not self.is_connected or not OPENVR_AVAILABLE:
|
||||
return
|
||||
|
||||
try:
|
||||
if hasattr(self.vr_manager, 'vr_system') and self.vr_manager.vr_system:
|
||||
# OpenVR的震动API
|
||||
duration_microseconds = int(duration * 1000000)
|
||||
self.vr_manager.vr_system.triggerHapticPulse(
|
||||
self.device_index,
|
||||
0, # axis ID (通常为0)
|
||||
int(strength * 3999) # 强度 (0-3999)
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"⚠️ {self.name}手柄震动反馈失败: {e}")
|
||||
|
||||
def get_world_position(self):
|
||||
"""获取手柄在世界坐标系中的位置"""
|
||||
if self.anchor_node:
|
||||
return self.anchor_node.getPos(self.vr_manager.world.render)
|
||||
return Vec3(0, 0, 0)
|
||||
|
||||
def get_world_rotation(self):
|
||||
"""获取手柄在世界坐标系中的旋转"""
|
||||
if self.anchor_node:
|
||||
return self.anchor_node.getHpr(self.vr_manager.world.render)
|
||||
return Vec3(0, 0, 0)
|
||||
|
||||
def get_forward_direction(self):
|
||||
"""获取手柄指向的方向向量(包含视角转向)"""
|
||||
if self.anchor_node:
|
||||
# 获取相对于世界坐标系的方向,包含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()
|
||||
|
||||
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')
|
||||
@ -1,430 +0,0 @@
|
||||
"""
|
||||
VR输入处理器
|
||||
|
||||
处理VR控制器输入、手势识别和VR交互
|
||||
支持多种VR控制器和手势输入
|
||||
"""
|
||||
|
||||
from direct.showbase.DirectObject import DirectObject
|
||||
from panda3d.core import Vec3, Point3, CollisionRay, CollisionNode, CollisionHandlerQueue
|
||||
from direct.task import Task
|
||||
import time
|
||||
|
||||
|
||||
class VRInputHandler(DirectObject):
|
||||
"""VR输入处理器"""
|
||||
|
||||
def __init__(self, world, vr_manager):
|
||||
super().__init__()
|
||||
self.world = world
|
||||
self.vr_manager = vr_manager
|
||||
|
||||
# 控制器状态
|
||||
self.controllers = {}
|
||||
self.controller_nodes = {}
|
||||
self.controller_rays = {}
|
||||
|
||||
# 手势识别
|
||||
self.gesture_enabled = True
|
||||
self.gesture_history = []
|
||||
self.gesture_threshold = 0.1
|
||||
|
||||
# 交互系统
|
||||
self.interaction_enabled = True
|
||||
self.selected_object = None
|
||||
self.grab_offset = Vec3(0, 0, 0)
|
||||
|
||||
# 输入映射
|
||||
self.input_mappings = {
|
||||
'trigger': self._handle_trigger,
|
||||
'grip': self._handle_grip,
|
||||
'touchpad': self._handle_touchpad,
|
||||
'menu': self._handle_menu,
|
||||
'system': self._handle_system
|
||||
}
|
||||
|
||||
print("✓ VR输入处理器初始化完成")
|
||||
|
||||
def start_input_handling(self):
|
||||
"""启动输入处理"""
|
||||
if not self.vr_manager.is_vr_enabled():
|
||||
print("VR未启用,无法启动输入处理")
|
||||
return False
|
||||
|
||||
# 启动输入更新任务
|
||||
self.world.taskMgr.add(self._update_input, "vr_input_update")
|
||||
|
||||
# 设置控制器可视化
|
||||
self._setup_controller_visualization()
|
||||
|
||||
print("✓ VR输入处理已启动")
|
||||
return True
|
||||
|
||||
def stop_input_handling(self):
|
||||
"""停止输入处理"""
|
||||
self.world.taskMgr.remove("vr_input_update")
|
||||
self._cleanup_controller_visualization()
|
||||
print("✓ VR输入处理已停止")
|
||||
|
||||
def _update_input(self, task):
|
||||
"""更新输入处理"""
|
||||
if not self.vr_manager.is_vr_enabled():
|
||||
return Task.cont
|
||||
|
||||
try:
|
||||
# 更新所有控制器
|
||||
self._update_controllers()
|
||||
|
||||
# 处理手势识别
|
||||
if self.gesture_enabled:
|
||||
self._process_gestures()
|
||||
|
||||
# 处理交互
|
||||
if self.interaction_enabled:
|
||||
self._process_interactions()
|
||||
|
||||
except Exception as e:
|
||||
print(f"VR输入更新错误: {str(e)}")
|
||||
|
||||
return Task.cont
|
||||
|
||||
def _update_controllers(self):
|
||||
"""更新控制器状态"""
|
||||
# 获取控制器姿态
|
||||
controller_poses = self.vr_manager.controller_poses
|
||||
|
||||
for controller_id, pose in controller_poses.items():
|
||||
# 获取控制器输入
|
||||
input_data = self.vr_manager.get_controller_input(controller_id)
|
||||
if not input_data:
|
||||
continue
|
||||
|
||||
# 更新控制器状态
|
||||
if controller_id not in self.controllers:
|
||||
self.controllers[controller_id] = {}
|
||||
|
||||
prev_state = self.controllers[controller_id].copy()
|
||||
self.controllers[controller_id] = input_data
|
||||
|
||||
# 更新控制器可视化
|
||||
self._update_controller_visualization(controller_id, pose)
|
||||
|
||||
# 处理输入事件
|
||||
self._process_controller_input(controller_id, input_data, prev_state)
|
||||
|
||||
def _process_controller_input(self, controller_id, current_state, prev_state):
|
||||
"""处理控制器输入"""
|
||||
# 检查按钮状态变化
|
||||
for input_type, handler in self.input_mappings.items():
|
||||
if input_type in current_state:
|
||||
current_value = current_state[input_type]
|
||||
prev_value = prev_state.get(input_type, 0)
|
||||
|
||||
# 处理按钮按下/释放
|
||||
if isinstance(current_value, (int, float)):
|
||||
if current_value > 0.5 and prev_value <= 0.5:
|
||||
handler(controller_id, 'press', current_value)
|
||||
elif current_value <= 0.5 and prev_value > 0.5:
|
||||
handler(controller_id, 'release', current_value)
|
||||
elif current_value > 0.5:
|
||||
handler(controller_id, 'hold', current_value)
|
||||
|
||||
# 处理触摸板
|
||||
elif isinstance(current_value, tuple) and len(current_value) == 2:
|
||||
if current_value != prev_value:
|
||||
handler(controller_id, 'move', current_value)
|
||||
|
||||
def _handle_trigger(self, controller_id, action, value):
|
||||
"""处理扳机输入"""
|
||||
if action == 'press':
|
||||
print(f"控制器 {controller_id} 扳机按下 (强度: {value:.2f})")
|
||||
self._try_grab_object(controller_id)
|
||||
elif action == 'release':
|
||||
print(f"控制器 {controller_id} 扳机释放")
|
||||
self._try_release_object(controller_id)
|
||||
|
||||
def _handle_grip(self, controller_id, action, value):
|
||||
"""处理握持输入"""
|
||||
if action == 'press':
|
||||
print(f"控制器 {controller_id} 握持按下 (强度: {value:.2f})")
|
||||
self._toggle_interaction_mode(controller_id)
|
||||
elif action == 'release':
|
||||
print(f"控制器 {controller_id} 握持释放")
|
||||
|
||||
def _handle_touchpad(self, controller_id, action, value):
|
||||
"""处理触摸板输入"""
|
||||
if action == 'move':
|
||||
x, y = value
|
||||
print(f"控制器 {controller_id} 触摸板: ({x:.2f}, {y:.2f})")
|
||||
|
||||
# 根据触摸板位置执行不同操作
|
||||
if abs(x) > 0.7: # 左右滑动
|
||||
self._handle_horizontal_swipe(controller_id, x)
|
||||
elif abs(y) > 0.7: # 上下滑动
|
||||
self._handle_vertical_swipe(controller_id, y)
|
||||
|
||||
def _handle_menu(self, controller_id, action, value):
|
||||
"""处理菜单按钮"""
|
||||
if action == 'press':
|
||||
print(f"控制器 {controller_id} 菜单按钮按下")
|
||||
self._show_vr_menu(controller_id)
|
||||
|
||||
def _handle_system(self, controller_id, action, value):
|
||||
"""处理系统按钮"""
|
||||
if action == 'press':
|
||||
print(f"控制器 {controller_id} 系统按钮按下")
|
||||
# 系统按钮通常由VR系统处理
|
||||
|
||||
def _handle_horizontal_swipe(self, controller_id, direction):
|
||||
"""处理水平滑动"""
|
||||
if direction > 0:
|
||||
print(f"控制器 {controller_id} 右滑")
|
||||
self._switch_tool(controller_id, 'next')
|
||||
else:
|
||||
print(f"控制器 {controller_id} 左滑")
|
||||
self._switch_tool(controller_id, 'prev')
|
||||
|
||||
def _handle_vertical_swipe(self, controller_id, direction):
|
||||
"""处理垂直滑动"""
|
||||
if direction > 0:
|
||||
print(f"控制器 {controller_id} 上滑")
|
||||
self._zoom_in(controller_id)
|
||||
else:
|
||||
print(f"控制器 {controller_id} 下滑")
|
||||
self._zoom_out(controller_id)
|
||||
|
||||
def _try_grab_object(self, controller_id):
|
||||
"""尝试抓取对象"""
|
||||
if controller_id not in self.controllers:
|
||||
return
|
||||
|
||||
# 获取控制器射线
|
||||
ray = self._get_controller_ray(controller_id)
|
||||
if not ray:
|
||||
return
|
||||
|
||||
# 执行射线检测
|
||||
hit_object = self._raycast_from_controller(controller_id)
|
||||
if hit_object:
|
||||
self.selected_object = hit_object
|
||||
controller_pose = self.controllers[controller_id].get('pose')
|
||||
if controller_pose:
|
||||
# 计算抓取偏移
|
||||
object_pos = hit_object.getPos()
|
||||
controller_pos = controller_pose.getTranslate()
|
||||
self.grab_offset = object_pos - controller_pos
|
||||
|
||||
print(f"抓取对象: {hit_object.getName()}")
|
||||
|
||||
# 发送抓取事件
|
||||
self.world.event_handler.messenger.send('vr-object-grabbed', [hit_object, controller_id])
|
||||
|
||||
def _try_release_object(self, controller_id):
|
||||
"""尝试释放对象"""
|
||||
if self.selected_object:
|
||||
print(f"释放对象: {self.selected_object.getName()}")
|
||||
|
||||
# 发送释放事件
|
||||
self.world.event_handler.messenger.send('vr-object-released', [self.selected_object, controller_id])
|
||||
|
||||
self.selected_object = None
|
||||
self.grab_offset = Vec3(0, 0, 0)
|
||||
|
||||
def _raycast_from_controller(self, controller_id):
|
||||
"""从控制器发射射线检测"""
|
||||
if controller_id not in self.controllers:
|
||||
return None
|
||||
|
||||
controller_pose = self.controllers[controller_id].get('pose')
|
||||
if not controller_pose:
|
||||
return None
|
||||
|
||||
# 获取控制器位置和方向
|
||||
controller_pos = controller_pose.getTranslate()
|
||||
controller_forward = controller_pose.getQuat().getForward()
|
||||
|
||||
# 创建射线
|
||||
ray = CollisionRay()
|
||||
ray.setOrigin(controller_pos)
|
||||
ray.setDirection(controller_forward)
|
||||
|
||||
# 执行碰撞检测
|
||||
traverser = self.world.cTrav if hasattr(self.world, 'cTrav') else None
|
||||
if not traverser:
|
||||
return None
|
||||
|
||||
handler = CollisionHandlerQueue()
|
||||
collision_node = CollisionNode('vr_controller_ray')
|
||||
collision_node.addSolid(ray)
|
||||
|
||||
ray_np = self.world.render.attachNewNode(collision_node)
|
||||
traverser.addCollider(ray_np, handler)
|
||||
|
||||
# 遍历碰撞
|
||||
traverser.traverse(self.world.render)
|
||||
|
||||
# 清理
|
||||
ray_np.removeNode()
|
||||
|
||||
# 返回最近的碰撞对象
|
||||
if handler.getNumEntries() > 0:
|
||||
handler.sortEntries()
|
||||
entry = handler.getEntry(0)
|
||||
return entry.getIntoNodePath()
|
||||
|
||||
return None
|
||||
|
||||
def _get_controller_ray(self, controller_id):
|
||||
"""获取控制器射线"""
|
||||
return self.controller_rays.get(controller_id)
|
||||
|
||||
def _setup_controller_visualization(self):
|
||||
"""设置控制器可视化"""
|
||||
print("设置控制器可视化...")
|
||||
|
||||
# 为每个控制器创建可视化节点
|
||||
for controller_id in self.controllers:
|
||||
self._create_controller_model(controller_id)
|
||||
|
||||
def _create_controller_model(self, controller_id):
|
||||
"""创建控制器模型"""
|
||||
# 创建简单的控制器模型(立方体)
|
||||
from panda3d.core import CardMaker
|
||||
|
||||
cm = CardMaker(f"controller_{controller_id}")
|
||||
cm.setFrame(-0.05, 0.05, -0.05, 0.05)
|
||||
|
||||
controller_node = self.world.render.attachNewNode(cm.generate())
|
||||
controller_node.setColor(0.2, 0.8, 1.0, 0.8)
|
||||
controller_node.setScale(0.1, 0.2, 0.05)
|
||||
|
||||
self.controller_nodes[controller_id] = controller_node
|
||||
|
||||
# 创建控制器射线可视化
|
||||
self._create_controller_ray_visual(controller_id)
|
||||
|
||||
def _create_controller_ray_visual(self, controller_id):
|
||||
"""创建控制器射线可视化"""
|
||||
from panda3d.core import LineSegs
|
||||
|
||||
# 创建射线线段
|
||||
lines = LineSegs()
|
||||
lines.setColor(1, 0, 0, 0.5)
|
||||
lines.moveTo(0, 0, 0)
|
||||
lines.drawTo(0, 2, 0) # 2米长的射线
|
||||
|
||||
ray_node = self.world.render.attachNewNode(lines.create())
|
||||
ray_node.setRenderModeWireframe()
|
||||
ray_node.hide() # 默认隐藏
|
||||
|
||||
self.controller_rays[controller_id] = ray_node
|
||||
|
||||
def _update_controller_visualization(self, controller_id, pose):
|
||||
"""更新控制器可视化"""
|
||||
if controller_id in self.controller_nodes:
|
||||
node = self.controller_nodes[controller_id]
|
||||
node.setMat(pose)
|
||||
|
||||
if controller_id in self.controller_rays:
|
||||
ray_node = self.controller_rays[controller_id]
|
||||
ray_node.setMat(pose)
|
||||
|
||||
def _cleanup_controller_visualization(self):
|
||||
"""清理控制器可视化"""
|
||||
for node in self.controller_nodes.values():
|
||||
node.removeNode()
|
||||
|
||||
for ray in self.controller_rays.values():
|
||||
ray.removeNode()
|
||||
|
||||
self.controller_nodes.clear()
|
||||
self.controller_rays.clear()
|
||||
|
||||
def _process_gestures(self):
|
||||
"""处理手势识别"""
|
||||
# 简单的手势识别逻辑
|
||||
# 这里可以实现更复杂的手势识别算法
|
||||
pass
|
||||
|
||||
def _process_interactions(self):
|
||||
"""处理交互逻辑"""
|
||||
# 如果有选中的对象,更新其位置
|
||||
if self.selected_object:
|
||||
self._update_grabbed_object()
|
||||
|
||||
def _update_grabbed_object(self):
|
||||
"""更新被抓取对象的位置"""
|
||||
if not self.selected_object:
|
||||
return
|
||||
|
||||
# 找到抓取该对象的控制器
|
||||
grabbing_controller = None
|
||||
for controller_id, controller_state in self.controllers.items():
|
||||
if controller_state.get('trigger', 0) > 0.5:
|
||||
grabbing_controller = controller_id
|
||||
break
|
||||
|
||||
if not grabbing_controller:
|
||||
return
|
||||
|
||||
# 更新对象位置
|
||||
controller_pose = self.controllers[grabbing_controller].get('pose')
|
||||
if controller_pose:
|
||||
controller_pos = controller_pose.getTranslate()
|
||||
new_pos = controller_pos + self.grab_offset
|
||||
self.selected_object.setPos(new_pos)
|
||||
|
||||
def _toggle_interaction_mode(self, controller_id):
|
||||
"""切换交互模式"""
|
||||
self.interaction_enabled = not self.interaction_enabled
|
||||
print(f"交互模式: {'启用' if self.interaction_enabled else '禁用'}")
|
||||
|
||||
def _show_vr_menu(self, controller_id):
|
||||
"""显示VR菜单"""
|
||||
print(f"显示VR菜单 (控制器 {controller_id})")
|
||||
# 这里可以实现VR菜单显示逻辑
|
||||
pass
|
||||
|
||||
def _switch_tool(self, controller_id, direction):
|
||||
"""切换工具"""
|
||||
print(f"切换工具: {direction} (控制器 {controller_id})")
|
||||
# 这里可以实现工具切换逻辑
|
||||
pass
|
||||
|
||||
def _zoom_in(self, controller_id):
|
||||
"""放大"""
|
||||
print(f"放大 (控制器 {controller_id})")
|
||||
# 实现放大逻辑
|
||||
pass
|
||||
|
||||
def _zoom_out(self, controller_id):
|
||||
"""缩小"""
|
||||
print(f"缩小 (控制器 {controller_id})")
|
||||
# 实现缩小逻辑
|
||||
pass
|
||||
|
||||
def show_controller_rays(self, show=True):
|
||||
"""显示/隐藏控制器射线"""
|
||||
for ray in self.controller_rays.values():
|
||||
if show:
|
||||
ray.show()
|
||||
else:
|
||||
ray.hide()
|
||||
|
||||
def get_controller_state(self, controller_id):
|
||||
"""获取控制器状态"""
|
||||
return self.controllers.get(controller_id, {})
|
||||
|
||||
def get_all_controllers(self):
|
||||
"""获取所有控制器"""
|
||||
return list(self.controllers.keys())
|
||||
|
||||
def set_gesture_enabled(self, enabled):
|
||||
"""设置手势识别启用状态"""
|
||||
self.gesture_enabled = enabled
|
||||
print(f"手势识别: {'启用' if enabled else '禁用'}")
|
||||
|
||||
def set_interaction_enabled(self, enabled):
|
||||
"""设置交互启用状态"""
|
||||
self.interaction_enabled = enabled
|
||||
print(f"VR交互: {'启用' if enabled else '禁用'}")
|
||||
432
core/vr_interaction.py
Normal file
432
core/vr_interaction.py
Normal 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交互管理器已清理")
|
||||
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()
|
||||
4646
core/vr_manager.py
4646
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}")
|
||||
729
core/vr_visualization.py
Normal file
729
core/vr_visualization.py
Normal file
@ -0,0 +1,729 @@
|
||||
"""
|
||||
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):
|
||||
"""修复模型材质,使用RenderPipeline兼容的新Material API"""
|
||||
from panda3d.core import Material, Vec4
|
||||
|
||||
# 检查模型是否有纹理
|
||||
has_texture = model.hasTexture()
|
||||
|
||||
# 创建新的材质,使用RenderPipeline兼容的API
|
||||
material = Material()
|
||||
|
||||
if has_texture:
|
||||
# 有纹理时,设置白色基础颜色让纹理完全显示
|
||||
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.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)")
|
||||
|
||||
# 应用材质到模型,使用优先级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
|
||||
|
||||
# 创建一个小的标识几何体
|
||||
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)
|
||||
|
||||
# 使用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}手柄交互射线已创建(含PBR支持)")
|
||||
|
||||
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)
|
||||
|
||||
# 为所有按钮指示器设置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):
|
||||
"""更新可视化状态"""
|
||||
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)
|
||||
|
||||
# 更新射线材质的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
|
||||
# 重新创建射线(简单的实现)
|
||||
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 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}手柄可视化已彻底清理")
|
||||
@ -1,374 +0,0 @@
|
||||
# VR + ALVR 串流实现指南
|
||||
|
||||
## 🎯 概述
|
||||
|
||||
本指南详细介绍了如何在Panda3D引擎中实现VR支持,并通过ALVR串流到Quest等VR头显设备。
|
||||
|
||||
## 🏗️ 系统架构
|
||||
|
||||
### 核心组件
|
||||
|
||||
1. **VRManager** (`core/vr_manager.py`)
|
||||
- 负责VR系统初始化
|
||||
- 管理立体渲染(左右眼)
|
||||
- 处理VR设备跟踪
|
||||
- 集成OpenVR接口
|
||||
|
||||
2. **VRInputHandler** (`core/vr_input_handler.py`)
|
||||
- 处理VR控制器输入
|
||||
- 手势识别系统
|
||||
- 交互逻辑处理
|
||||
|
||||
3. **ALVRStreamer** (`core/alvr_streamer.py`)
|
||||
- ALVR服务器通信
|
||||
- 视频流编码和传输
|
||||
- 性能监控
|
||||
|
||||
4. **VRControlPanel** (`ui/vr_control_panel.py`)
|
||||
- VR系统GUI控制界面
|
||||
- 实时状态监控
|
||||
- 参数调整面板
|
||||
|
||||
## 🔧 安装和配置
|
||||
|
||||
### 1. 系统依赖
|
||||
|
||||
```bash
|
||||
# 安装Python依赖
|
||||
pip install -r requirements/vr-requirements.txt
|
||||
|
||||
# 安装OpenVR运行时
|
||||
# Windows: 下载并安装SteamVR
|
||||
# Linux:
|
||||
sudo apt-get install steam
|
||||
# 然后在Steam中安装SteamVR
|
||||
```
|
||||
|
||||
### 2. ALVR服务器设置
|
||||
|
||||
```bash
|
||||
# 下载ALVR服务器
|
||||
# 从 https://github.com/alvr-org/ALVR 下载最新版本
|
||||
|
||||
# Windows
|
||||
# 1. 解压到 C:\ALVR\
|
||||
# 2. 运行 alvr_server.exe
|
||||
|
||||
# Linux
|
||||
# 1. 解压到 /usr/local/bin/
|
||||
# 2. 运行 alvr_server
|
||||
```
|
||||
|
||||
### 3. Quest设备配置
|
||||
|
||||
```bash
|
||||
# 1. 在Quest上安装ALVR客户端
|
||||
# 2. 启用开发者模式
|
||||
# 3. 连接到同一WiFi网络
|
||||
# 4. 配置防火墙允许ALVR通信(端口9943-9944)
|
||||
```
|
||||
|
||||
## 🚀 使用方法
|
||||
|
||||
### 快速启动
|
||||
|
||||
```python
|
||||
# 在主程序中
|
||||
world = MyWorld()
|
||||
|
||||
# 一键启用VR模式
|
||||
if world.enableVRMode():
|
||||
print("VR模式已启用")
|
||||
|
||||
# 检查VR状态
|
||||
status = world.getVRStatus()
|
||||
print(f"VR设备: {status['vr_info']}")
|
||||
print(f"ALVR连接: {status['alvr_connected']}")
|
||||
print(f"串流状态: {status['alvr_streaming']}")
|
||||
```
|
||||
|
||||
### 详细控制
|
||||
|
||||
```python
|
||||
# 分步骤启动VR
|
||||
world = MyWorld()
|
||||
|
||||
# 1. 初始化VR系统
|
||||
if world.initializeVR():
|
||||
print("VR系统初始化成功")
|
||||
|
||||
# 2. 启动VR输入处理
|
||||
world.startVRInput()
|
||||
|
||||
# 3. 显示控制器射线
|
||||
world.showControllerRays(True)
|
||||
|
||||
# 4. 初始化ALVR串流
|
||||
if world.initializeALVR():
|
||||
print("ALVR初始化成功")
|
||||
|
||||
# 5. 设置串流质量
|
||||
world.setALVRStreamQuality(
|
||||
width=2880,
|
||||
height=1700,
|
||||
fps=72,
|
||||
bitrate=150
|
||||
)
|
||||
|
||||
# 6. 开始串流
|
||||
world.startALVRStreaming()
|
||||
```
|
||||
|
||||
### GUI控制界面
|
||||
|
||||
```python
|
||||
# 创建VR控制面板
|
||||
from ui.vr_control_panel import VRControlPanel
|
||||
|
||||
vr_panel = VRControlPanel(world)
|
||||
vr_panel.show()
|
||||
|
||||
# 面板功能:
|
||||
# - 一键启用/禁用VR
|
||||
# - ALVR串流控制
|
||||
# - 质量参数调整
|
||||
# - 实时性能监控
|
||||
# - 控制器状态显示
|
||||
```
|
||||
|
||||
## 🎮 VR交互功能
|
||||
|
||||
### 控制器输入处理
|
||||
|
||||
```python
|
||||
# 获取控制器状态
|
||||
controllers = world.getAllControllers()
|
||||
for controller_id in controllers:
|
||||
state = world.getControllerState(controller_id)
|
||||
|
||||
# 检查扳机按下
|
||||
if state['trigger'] > 0.5:
|
||||
print(f"控制器 {controller_id} 扳机按下")
|
||||
|
||||
# 检查触摸板输入
|
||||
touchpad_x, touchpad_y = state['touchpad']
|
||||
if abs(touchpad_x) > 0.5:
|
||||
print(f"触摸板水平滑动: {touchpad_x}")
|
||||
```
|
||||
|
||||
### 对象抓取和操作
|
||||
|
||||
```python
|
||||
# VR输入处理器自动处理对象抓取
|
||||
# 扳机按下时自动检测和抓取对象
|
||||
# 扳机释放时自动释放对象
|
||||
|
||||
# 监听VR事件
|
||||
world.event_handler.accept('vr-object-grabbed', onObjectGrabbed)
|
||||
world.event_handler.accept('vr-object-released', onObjectReleased)
|
||||
|
||||
def onObjectGrabbed(object_node, controller_id):
|
||||
print(f"抓取对象: {object_node.getName()}")
|
||||
|
||||
def onObjectReleased(object_node, controller_id):
|
||||
print(f"释放对象: {object_node.getName()}")
|
||||
```
|
||||
|
||||
### 触觉反馈
|
||||
|
||||
```python
|
||||
# 发送触觉反馈
|
||||
world.sendHapticFeedback(
|
||||
controller_id=0,
|
||||
duration=0.5, # 持续时间(秒)
|
||||
intensity=0.8 # 强度(0-1)
|
||||
)
|
||||
```
|
||||
|
||||
## 📊 性能优化
|
||||
|
||||
### 渲染优化
|
||||
|
||||
```python
|
||||
# 调整VR渲染质量
|
||||
vr_manager = world.vr_manager
|
||||
vr_manager.render_scale = 1.0 # 渲染缩放 (0.5-2.0)
|
||||
|
||||
# 设置多重采样抗锯齿
|
||||
fb_props = FrameBufferProperties()
|
||||
fb_props.setMultisamples(4) # 4x MSAA
|
||||
```
|
||||
|
||||
### 网络优化
|
||||
|
||||
```python
|
||||
# 优化ALVR串流参数
|
||||
world.setALVRStreamQuality(
|
||||
width=2160, # 降低分辨率提高性能
|
||||
height=1200,
|
||||
fps=60, # 降低帧率减少延迟
|
||||
bitrate=100 # 降低比特率适应网络带宽
|
||||
)
|
||||
```
|
||||
|
||||
### 系统资源监控
|
||||
|
||||
```python
|
||||
# 获取性能状态
|
||||
streaming_status = world.getALVRStreamingStatus()
|
||||
print(f"串流FPS: {streaming_status['fps']}")
|
||||
print(f"延迟: {streaming_status['latency']} ms")
|
||||
print(f"分辨率: {streaming_status['resolution']}")
|
||||
```
|
||||
|
||||
## 🔍 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **VR系统初始化失败**
|
||||
```
|
||||
错误: OpenVR初始化失败
|
||||
解决: 确保SteamVR已安装并运行
|
||||
```
|
||||
|
||||
2. **ALVR连接失败**
|
||||
```
|
||||
错误: 无法连接到ALVR服务器
|
||||
解决:
|
||||
- 检查ALVR服务器是否运行
|
||||
- 确认防火墙设置
|
||||
- 检查网络连接
|
||||
```
|
||||
|
||||
3. **串流质量问题**
|
||||
```
|
||||
问题: 画面卡顿或延迟高
|
||||
解决:
|
||||
- 降低分辨率和帧率
|
||||
- 检查WiFi信号强度
|
||||
- 调整比特率设置
|
||||
```
|
||||
|
||||
### 调试命令
|
||||
|
||||
```python
|
||||
# 启用调试模式
|
||||
world.vr_manager.debug_mode = True
|
||||
|
||||
# 获取详细状态信息
|
||||
vr_info = world.getVRInfo()
|
||||
print(f"VR设备信息: {vr_info}")
|
||||
|
||||
# 检查网络连接
|
||||
if world.isALVRConnected():
|
||||
print("ALVR连接正常")
|
||||
else:
|
||||
print("ALVR连接失败")
|
||||
```
|
||||
|
||||
## 🎯 高级功能
|
||||
|
||||
### 自定义VR交互
|
||||
|
||||
```python
|
||||
# 创建自定义VR交互逻辑
|
||||
class CustomVRInteraction:
|
||||
def __init__(self, world):
|
||||
self.world = world
|
||||
|
||||
def handleCustomGesture(self, gesture_data):
|
||||
# 处理自定义手势
|
||||
pass
|
||||
|
||||
def createVRMenu(self, controller_id):
|
||||
# 在VR中创建3D菜单
|
||||
pass
|
||||
```
|
||||
|
||||
### 多用户VR支持
|
||||
|
||||
```python
|
||||
# 支持多个VR用户
|
||||
class MultiUserVRManager:
|
||||
def __init__(self):
|
||||
self.vr_users = {}
|
||||
|
||||
def addVRUser(self, user_id, vr_manager):
|
||||
self.vr_users[user_id] = vr_manager
|
||||
|
||||
def syncVRUsers(self):
|
||||
# 同步多用户VR状态
|
||||
pass
|
||||
```
|
||||
|
||||
## 📋 配置文件
|
||||
|
||||
### VR配置 (`config/vr_config.yaml`)
|
||||
|
||||
```yaml
|
||||
vr:
|
||||
enabled: true
|
||||
render_scale: 1.0
|
||||
tracking_space: "standing"
|
||||
|
||||
alvr:
|
||||
server_ip: "127.0.0.1"
|
||||
server_port: 9943
|
||||
streaming_port: 9944
|
||||
|
||||
video:
|
||||
width: 2880
|
||||
height: 1700
|
||||
fps: 72
|
||||
bitrate: 150
|
||||
codec: "h264"
|
||||
|
||||
audio:
|
||||
enabled: true
|
||||
sample_rate: 48000
|
||||
channels: 2
|
||||
```
|
||||
|
||||
## 🚀 部署建议
|
||||
|
||||
### 开发环境
|
||||
|
||||
```bash
|
||||
# 创建VR开发环境
|
||||
python -m venv vr_env
|
||||
source vr_env/bin/activate
|
||||
pip install -r requirements/vr-requirements.txt
|
||||
|
||||
# 启动开发服务器
|
||||
python main.py --vr-mode
|
||||
```
|
||||
|
||||
### 生产环境
|
||||
|
||||
```bash
|
||||
# 优化生产部署
|
||||
# 1. 使用专用VR计算机
|
||||
# 2. 配置高性能网络
|
||||
# 3. 启用GPU加速
|
||||
# 4. 监控系统性能
|
||||
```
|
||||
|
||||
## 📚 参考资料
|
||||
|
||||
- [OpenVR API文档](https://github.com/ValveSoftware/openvr/wiki/API-Documentation)
|
||||
- [ALVR项目主页](https://github.com/alvr-org/ALVR)
|
||||
- [Panda3D VR指南](https://docs.panda3d.org/1.10/python/programming/render-to-texture/index)
|
||||
- [Quest开发者文档](https://developer.oculus.com/documentation/quest/)
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
欢迎贡献代码和改进建议!请遵循以下步骤:
|
||||
|
||||
1. Fork此仓库
|
||||
2. 创建功能分支
|
||||
3. 提交代码修改
|
||||
4. 创建Pull Request
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本项目采用MIT许可证。详见LICENSE文件。
|
||||
253
demo/VR测试说明.md
253
demo/VR测试说明.md
@ -1,253 +0,0 @@
|
||||
# VR测试说明
|
||||
|
||||
## 📋 测试条件说明
|
||||
|
||||
### 🎮 VR测试的两种模式
|
||||
|
||||
#### 1. 模拟模式(推荐用于开发和调试)
|
||||
- **无需VR硬件**
|
||||
- **无需SteamVR**
|
||||
- **无需ALVR服务器**
|
||||
- 自动启用,当真实VR不可用时
|
||||
- 模拟头盔和控制器追踪数据
|
||||
- 立体渲染显示到窗口
|
||||
- 适用于功能测试和开发
|
||||
|
||||
#### 2. 真实VR模式(需要完整VR设备)
|
||||
- **需要VR头盔**
|
||||
- **需要SteamVR运行**
|
||||
- **需要ALVR服务器(Quest无线)**
|
||||
- 真实的6DOF追踪
|
||||
- 立体渲染到VR头盔
|
||||
- 完整的VR交互体验
|
||||
|
||||
## 🔧 软件要求
|
||||
|
||||
### 必需依赖
|
||||
```bash
|
||||
# 安装VR相关依赖
|
||||
pip install -r requirements/vr-requirements.txt
|
||||
|
||||
# 主要包含:
|
||||
# - openvr>=1.26.7
|
||||
# - psutil>=5.9.0
|
||||
# - numpy>=1.21.0
|
||||
# - Pillow>=9.0.1
|
||||
```
|
||||
|
||||
### 系统要求
|
||||
- Python 3.8+
|
||||
- Panda3D
|
||||
- PyQt5
|
||||
- OpenGL支持的显卡
|
||||
|
||||
## 🎮 硬件要求(完整VR模式)
|
||||
|
||||
### 最低硬件要求
|
||||
- **显卡**: GTX 1060 / RX 580 或更好
|
||||
- **内存**: 8GB RAM
|
||||
- **处理器**: Intel i5-4590 / AMD FX 8350 或更好
|
||||
- **USB**: 至少1个USB 3.0端口
|
||||
|
||||
### 支持的VR头盔
|
||||
- **Quest 2/3**: 有线(USB-C)或无线(ALVR)
|
||||
- **Valve Index**: DisplayPort + USB 3.0
|
||||
- **HTC Vive**: HDMI + USB 3.0
|
||||
- **其他OpenVR兼容设备**
|
||||
|
||||
## 🌐 连接方式
|
||||
|
||||
### 有线连接
|
||||
1. **Quest 2/3**: USB-C数据线连接PC
|
||||
2. **Valve Index**: DisplayPort + USB 3.0
|
||||
3. **HTC Vive**: HDMI + USB 3.0
|
||||
|
||||
### 无线连接(推荐Quest)
|
||||
1. **下载ALVR服务器**: [GitHub](https://github.com/alvr-org/ALVR)
|
||||
2. **在PC上运行ALVR服务器**
|
||||
3. **在Quest上安装ALVR客户端**
|
||||
4. **连接到同一Wi-Fi网络(5GHz推荐)**
|
||||
|
||||
## 🔄 启动顺序
|
||||
|
||||
### 完整VR模式启动顺序
|
||||
1. **连接VR头盔**到PC
|
||||
2. **启动SteamVR**
|
||||
3. **(可选)启动ALVR服务器**(Quest无线)
|
||||
4. **运行VR测试脚本**
|
||||
|
||||
### 模拟模式启动顺序
|
||||
1. **直接运行VR测试脚本**
|
||||
2. **系统会自动切换到模拟模式**
|
||||
|
||||
## 📊 测试项目说明
|
||||
|
||||
### 1. 基本VR功能测试
|
||||
- VR系统初始化
|
||||
- 控制器检测
|
||||
- 追踪数据获取
|
||||
- 立体渲染测试
|
||||
|
||||
### 2. VR模拟模式测试
|
||||
- 强制启用模拟模式
|
||||
- 模拟数据更新
|
||||
- 控制器输入模拟
|
||||
- 立体渲染显示
|
||||
|
||||
### 3. ALVR串流测试
|
||||
- ALVR服务器连接
|
||||
- 串流质量设置
|
||||
- 触觉反馈测试
|
||||
- 串流开关控制
|
||||
|
||||
### 4. VR GUI控制面板
|
||||
- 图形界面控制
|
||||
- 实时状态监控
|
||||
- 参数调整
|
||||
- 系统开关
|
||||
|
||||
### 5. VR交互功能测试
|
||||
- 控制器射线显示
|
||||
- 手势识别
|
||||
- 对象交互
|
||||
- 输入处理
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 开发和调试(推荐)
|
||||
```bash
|
||||
# 直接运行,会自动使用模拟模式
|
||||
python vr_test.py
|
||||
|
||||
# 选择 "2. VR模拟模式测试"
|
||||
```
|
||||
|
||||
### 完整VR测试
|
||||
```bash
|
||||
# 确保VR设备已连接并启动SteamVR
|
||||
# 然后运行
|
||||
python vr_test.py
|
||||
|
||||
# 选择 "1. 基本VR功能测试"
|
||||
```
|
||||
|
||||
## 💡 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### 1. OpenVR初始化失败
|
||||
**现象**: 显示"OpenVR初始化失败"
|
||||
**解决方案**:
|
||||
- 检查SteamVR是否运行
|
||||
- 确认VR头盔被系统识别
|
||||
- 重启SteamVR和头盔
|
||||
- 系统会自动切换到模拟模式
|
||||
|
||||
#### 2. 控制器未检测到
|
||||
**现象**: 控制器显示"未连接"
|
||||
**解决方案**:
|
||||
- 确保控制器已配对
|
||||
- 检查控制器电量
|
||||
- 在SteamVR中重新配对
|
||||
- 模拟模式下会显示模拟控制器
|
||||
|
||||
#### 3. ALVR连接失败
|
||||
**现象**: ALVR串流测试失败
|
||||
**解决方案**:
|
||||
- 确保ALVR服务器正在运行
|
||||
- 检查Quest上的ALVR客户端
|
||||
- 确认PC和Quest在同一网络
|
||||
- 使用5GHz Wi-Fi网络
|
||||
|
||||
#### 4. 性能问题
|
||||
**现象**: 帧率低或卡顿
|
||||
**解决方案**:
|
||||
- 降低渲染分辨率
|
||||
- 关闭不必要的后台程序
|
||||
- 检查显卡驱动更新
|
||||
- 使用模拟模式进行开发
|
||||
|
||||
### 日志分析
|
||||
测试过程中的详细日志可以帮助诊断问题:
|
||||
- `✓` 表示成功
|
||||
- `⚠` 表示警告(非致命)
|
||||
- `✗` 表示失败
|
||||
- `🔧` 表示配置或调试信息
|
||||
|
||||
## 📈 性能优化建议
|
||||
|
||||
### 开发阶段
|
||||
1. **使用模拟模式**进行功能开发
|
||||
2. **降低渲染分辨率**提高帧率
|
||||
3. **关闭不必要的特效**
|
||||
4. **使用性能分析工具**
|
||||
|
||||
### 生产部署
|
||||
1. **使用真实VR模式**
|
||||
2. **根据硬件配置调整质量**
|
||||
3. **启用异步时间扭曲**
|
||||
4. **优化渲染管线**
|
||||
|
||||
## 🔍 调试技巧
|
||||
|
||||
### 1. 检查VR系统状态
|
||||
```python
|
||||
# 在代码中添加调试信息
|
||||
vr_info = world.getVRInfo()
|
||||
print(f"VR模式: {vr_info['mode']}")
|
||||
print(f"模拟模式: {vr_info['simulation_mode']}")
|
||||
```
|
||||
|
||||
### 2. 监控性能
|
||||
```python
|
||||
# 获取VR系统状态
|
||||
status = world.getVRStatus()
|
||||
print(f"VR启用: {status['vr_enabled']}")
|
||||
print(f"控制器数量: {len(status['controllers'])}")
|
||||
```
|
||||
|
||||
### 3. 测试特定功能
|
||||
```python
|
||||
# 强制启用模拟模式
|
||||
world.vr_manager.enable_simulation_mode()
|
||||
|
||||
# 更新模拟数据
|
||||
world.vr_manager.update_simulation_data('head_pose', new_data)
|
||||
```
|
||||
|
||||
## 📚 API参考
|
||||
|
||||
### VR系统控制
|
||||
- `world.initializeVR()` - 初始化VR系统
|
||||
- `world.shutdownVR()` - 关闭VR系统
|
||||
- `world.isVREnabled()` - 检查VR状态
|
||||
- `world.getVRInfo()` - 获取VR信息
|
||||
|
||||
### 模拟模式
|
||||
- `world.vr_manager.enable_simulation_mode()` - 启用模拟模式
|
||||
- `world.vr_manager.get_simulation_data()` - 获取模拟数据
|
||||
- `world.vr_manager.update_simulation_data()` - 更新模拟数据
|
||||
|
||||
### ALVR串流
|
||||
- `world.initializeALVR()` - 初始化ALVR
|
||||
- `world.startALVRStreaming()` - 开始串流
|
||||
- `world.stopALVRStreaming()` - 停止串流
|
||||
- `world.setALVRStreamQuality()` - 设置质量
|
||||
|
||||
### VR输入
|
||||
- `world.startVRInput()` - 启动输入处理
|
||||
- `world.showControllerRays()` - 显示控制器射线
|
||||
- `world.getAllControllers()` - 获取所有控制器
|
||||
- `world.getControllerState()` - 获取控制器状态
|
||||
|
||||
## 🎯 总结
|
||||
|
||||
现在的VR测试系统具有以下优势:
|
||||
|
||||
1. **自动适应**: 自动在真实VR和模拟模式之间切换
|
||||
2. **开发友好**: 无需VR硬件即可开发和测试
|
||||
3. **完整功能**: 支持所有VR功能的测试
|
||||
4. **详细反馈**: 提供清晰的状态信息和错误提示
|
||||
5. **灵活配置**: 可根据需要调整各种参数
|
||||
|
||||
无论您是否拥有VR设备,都可以使用这个测试系统来验证VR功能的正确性。
|
||||
155
main.py
155
main.py
@ -21,11 +21,6 @@ from core.selection import SelectionSystem
|
||||
from core.event_handler import EventHandler
|
||||
from core.tool_manager import ToolManager
|
||||
from core.script_system import ScriptManager
|
||||
from core.vr_manager import VRManager
|
||||
from core.vr_input_handler import VRInputHandler
|
||||
from core.alvr_streamer import ALVRStreamer
|
||||
from core.patrol_system import PatrolSystem
|
||||
from core.Command_System import CommandManager
|
||||
from gui.gui_manager import GUIManager
|
||||
from core.terrain_manager import TerrainManager
|
||||
from scene.scene_manager import SceneManager
|
||||
@ -96,10 +91,6 @@ class MyWorld(CoreWorld):
|
||||
# 初始化界面管理系统
|
||||
self.interface_manager = InterfaceManager(self)
|
||||
|
||||
# 初始化VR系统
|
||||
self.vr_manager = VRManager(self)
|
||||
self.vr_input_handler = VRInputHandler(self, self.vr_manager)
|
||||
self.alvr_streamer = ALVRStreamer(self, self.vr_manager)
|
||||
|
||||
# 启动脚本系统
|
||||
self.script_manager.start_system()
|
||||
@ -119,6 +110,15 @@ class MyWorld(CoreWorld):
|
||||
from core.collision_manager import CollisionManager
|
||||
self.collision_manager = CollisionManager(self)
|
||||
|
||||
# 初始化VR管理器
|
||||
try:
|
||||
from core.vr_manager import VRManager
|
||||
self.vr_manager = VRManager(self)
|
||||
print("✓ VR管理器初始化完成")
|
||||
except Exception as e:
|
||||
print(f"⚠ VR管理器初始化失败: {e}")
|
||||
self.vr_manager = None
|
||||
|
||||
# 调试选项
|
||||
self.debug_collision = True # 是否显示碰撞体
|
||||
|
||||
@ -621,143 +621,6 @@ class MyWorld(CoreWorld):
|
||||
def listAllScripts(self):
|
||||
"""列出所有脚本信息"""
|
||||
return self.script_manager.list_all_scripts()
|
||||
# ==================== VR系统功能代理 ====================
|
||||
|
||||
# VR系统控制方法 - 代理到vr_manager
|
||||
def initializeVR(self):
|
||||
"""初始化VR系统"""
|
||||
return self.vr_manager.initialize_vr()
|
||||
|
||||
def shutdownVR(self):
|
||||
"""关闭VR系统"""
|
||||
return self.vr_manager.shutdown_vr()
|
||||
|
||||
def isVREnabled(self):
|
||||
"""检查VR是否启用"""
|
||||
return self.vr_manager.is_vr_enabled()
|
||||
|
||||
def getVRInfo(self):
|
||||
"""获取VR系统信息"""
|
||||
return self.vr_manager.get_vr_info()
|
||||
|
||||
# VR输入处理方法 - 代理到vr_input_handler
|
||||
def startVRInput(self):
|
||||
"""启动VR输入处理"""
|
||||
return self.vr_input_handler.start_input_handling()
|
||||
|
||||
def stopVRInput(self):
|
||||
"""停止VR输入处理"""
|
||||
return self.vr_input_handler.stop_input_handling()
|
||||
|
||||
def showControllerRays(self, show=True):
|
||||
"""显示/隐藏控制器射线"""
|
||||
return self.vr_input_handler.show_controller_rays(show)
|
||||
|
||||
def getControllerState(self, controller_id):
|
||||
"""获取控制器状态"""
|
||||
return self.vr_input_handler.get_controller_state(controller_id)
|
||||
|
||||
def getAllControllers(self):
|
||||
"""获取所有控制器"""
|
||||
return self.vr_input_handler.get_all_controllers()
|
||||
|
||||
def setVRGestureEnabled(self, enabled):
|
||||
"""设置VR手势识别启用状态"""
|
||||
return self.vr_input_handler.set_gesture_enabled(enabled)
|
||||
|
||||
def setVRInteractionEnabled(self, enabled):
|
||||
"""设置VR交互启用状态"""
|
||||
return self.vr_input_handler.set_interaction_enabled(enabled)
|
||||
|
||||
# ALVR串流方法 - 代理到alvr_streamer
|
||||
def initializeALVR(self):
|
||||
"""初始化ALVR串流"""
|
||||
return self.alvr_streamer.initialize()
|
||||
|
||||
def startALVRStreaming(self):
|
||||
"""开始ALVR串流"""
|
||||
return self.alvr_streamer.start_streaming()
|
||||
|
||||
def stopALVRStreaming(self):
|
||||
"""停止ALVR串流"""
|
||||
return self.alvr_streamer.stop_streaming()
|
||||
|
||||
def getALVRStreamingStatus(self):
|
||||
"""获取ALVR串流状态"""
|
||||
return self.alvr_streamer.get_streaming_status()
|
||||
|
||||
def setALVRStreamQuality(self, width, height, fps, bitrate):
|
||||
"""设置ALVR串流质量"""
|
||||
return self.alvr_streamer.set_stream_quality(width, height, fps, bitrate)
|
||||
|
||||
def sendHapticFeedback(self, controller_id, duration, intensity):
|
||||
"""发送触觉反馈"""
|
||||
return self.alvr_streamer.send_haptic_feedback(controller_id, duration, intensity)
|
||||
|
||||
def shutdownALVR(self):
|
||||
"""关闭ALVR串流"""
|
||||
return self.alvr_streamer.shutdown()
|
||||
|
||||
def isALVRConnected(self):
|
||||
"""检查ALVR是否连接"""
|
||||
return self.alvr_streamer.is_connected()
|
||||
|
||||
def isALVRStreaming(self):
|
||||
"""检查ALVR是否在串流"""
|
||||
return self.alvr_streamer.is_streaming()
|
||||
|
||||
# 便捷方法
|
||||
def enableVRMode(self):
|
||||
"""启用VR模式(一键启动)"""
|
||||
print("启用VR模式...")
|
||||
|
||||
# 1. 初始化VR系统
|
||||
if not self.initializeVR():
|
||||
print("VR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 2. 启动VR输入处理
|
||||
if not self.startVRInput():
|
||||
print("VR输入处理启动失败")
|
||||
return False
|
||||
|
||||
# 3. 初始化ALVR串流
|
||||
if self.initializeALVR():
|
||||
print("✓ ALVR串流已启用")
|
||||
# 自动开始串流
|
||||
self.startALVRStreaming()
|
||||
else:
|
||||
print("⚠ ALVR串流启用失败,但VR系统仍可用")
|
||||
|
||||
print("✓ VR模式已启用")
|
||||
return True
|
||||
|
||||
def disableVRMode(self):
|
||||
"""禁用VR模式(一键关闭)"""
|
||||
print("禁用VR模式...")
|
||||
|
||||
# 1. 停止ALVR串流
|
||||
self.stopALVRStreaming()
|
||||
self.shutdownALVR()
|
||||
|
||||
# 2. 停止VR输入处理
|
||||
self.stopVRInput()
|
||||
|
||||
# 3. 关闭VR系统
|
||||
self.shutdownVR()
|
||||
|
||||
print("✓ VR模式已禁用")
|
||||
|
||||
def getVRStatus(self):
|
||||
"""获取VR系统总体状态"""
|
||||
return {
|
||||
"vr_enabled": self.isVREnabled(),
|
||||
"vr_info": self.getVRInfo(),
|
||||
"controllers": self.getAllControllers(),
|
||||
"alvr_connected": self.isALVRConnected(),
|
||||
"alvr_streaming": self.isALVRStreaming(),
|
||||
"streaming_status": self.getALVRStreamingStatus() if self.isALVRConnected() else None
|
||||
}
|
||||
|
||||
def loadCesiumTileset(self,tileset_url,position=(0,0,0)):
|
||||
return self.scene_manager.load_cesium_tileset(tileset_url,position)
|
||||
|
||||
@ -98,3 +98,4 @@ webencodings==0.5.1
|
||||
xdg==5
|
||||
xkit==0.0.0
|
||||
zipp==1.0.0
|
||||
openvr==2.2.0
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
# VR功能依赖包
|
||||
# 用于支持VR显示和ALVR串流功能
|
||||
|
||||
# OpenVR Python库 - VR系统核心
|
||||
openvr>=1.26.7
|
||||
|
||||
# 网络和通信
|
||||
psutil>=5.9.0
|
||||
|
||||
# 数学和科学计算
|
||||
numpy>=1.21.0
|
||||
|
||||
# 图像处理
|
||||
Pillow>=9.0.1
|
||||
|
||||
# 现有核心依赖
|
||||
Panda3D>=1.10.15
|
||||
PyQt5>=5.15.9
|
||||
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获取详细使用说明"
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
@ -767,6 +767,105 @@ class MainWindow(QMainWindow):
|
||||
self.refreshAssetsAction = self.assetsMenu.addAction('刷新资源')
|
||||
self.refreshAssetsAction.triggered.connect(self.refreshAssetsView)
|
||||
|
||||
# VR菜单
|
||||
self.vrMenu = menubar.addMenu('VR')
|
||||
self.enterVRAction = self.vrMenu.addAction('进入VR模式')
|
||||
self.exitVRAction = self.vrMenu.addAction('退出VR模式')
|
||||
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)
|
||||
|
||||
# 帮助菜单
|
||||
self.helpMenu = menubar.addMenu('帮助')
|
||||
self.aboutAction = self.helpMenu.addAction('关于')
|
||||
@ -1857,6 +1956,35 @@ class MainWindow(QMainWindow):
|
||||
# self.toggleHotReloadAction.triggered.connect(self.onToggleHotReload)
|
||||
# self.openScriptsManagerAction.triggered.connect(self.onOpenScriptsManager)
|
||||
|
||||
# 连接VR菜单事件
|
||||
self.enterVRAction.triggered.connect(self.onEnterVR)
|
||||
self.exitVRAction.triggered.connect(self.onExitVR)
|
||||
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 onCopy(self):
|
||||
"""复制操作"""
|
||||
try:
|
||||
@ -3114,13 +3242,21 @@ class MainWindow(QMainWindow):
|
||||
# 清理工具管理器中的进程
|
||||
if hasattr(self.world, 'tool_manager') and self.world.tool_manager:
|
||||
print("🧹 清理工具管理器进程...")
|
||||
self.world.tool_manager.cleanup_processes()
|
||||
if hasattr(self.world.tool_manager, 'cleanup_processes'):
|
||||
self.world.tool_manager.cleanup_processes()
|
||||
else:
|
||||
print("✓ 工具管理器无需清理进程")
|
||||
|
||||
# 停止更新定时器
|
||||
if hasattr(self, 'updateTimer') and self.updateTimer:
|
||||
self.updateTimer.stop()
|
||||
print("⏹️ 更新定时器已停止")
|
||||
|
||||
# 清理VR资源
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
print("🧹 清理VR资源...")
|
||||
self.world.vr_manager.cleanup()
|
||||
|
||||
# 清理Panda3D资源
|
||||
if hasattr(self, 'pandaWidget') and self.pandaWidget:
|
||||
print("🧹 清理Panda3D资源...")
|
||||
@ -3308,6 +3444,545 @@ class MainWindow(QMainWindow):
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "高度图地形创建失败!")
|
||||
|
||||
# ==================== VR事件处理 ====================
|
||||
|
||||
def onEnterVR(self):
|
||||
"""进入VR模式"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
success = self.world.vr_manager.enable_vr()
|
||||
if success:
|
||||
# 更新菜单状态
|
||||
self.enterVRAction.setEnabled(False)
|
||||
self.exitVRAction.setEnabled(True)
|
||||
QMessageBox.information(self, "成功", "VR模式已启用!\n请确保您的VR头显已正确连接。")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "无法启用VR模式!\n请检查:\n1. SteamVR是否正在运行\n2. VR头显是否已连接\n3. OpenVR库是否已正确安装")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"启用VR模式时发生错误:\n{str(e)}")
|
||||
|
||||
def onExitVR(self):
|
||||
"""退出VR模式"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
self.world.vr_manager.disable_vr()
|
||||
# 更新菜单状态
|
||||
self.enterVRAction.setEnabled(True)
|
||||
self.exitVRAction.setEnabled(False)
|
||||
QMessageBox.information(self, "成功", "已退出VR模式")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"退出VR模式时发生错误:\n{str(e)}")
|
||||
|
||||
def onShowVRStatus(self):
|
||||
"""显示VR状态"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
status = self.world.vr_manager.get_vr_status()
|
||||
|
||||
status_text = f"""VR系统状态:
|
||||
|
||||
可用性: {'✅ 可用' if status['available'] else '❌ 不可用'}
|
||||
初始化: {'✅ 已初始化' if status['initialized'] else '❌ 未初始化'}
|
||||
启用状态: {'✅ 已启用' if status['enabled'] else '❌ 未启用'}
|
||||
渲染分辨率: {status['eye_resolution'][0]}x{status['eye_resolution'][1]}
|
||||
追踪设备数: {status['device_count']}
|
||||
|
||||
提示:
|
||||
- 如果VR不可用,请确保已安装SteamVR并连接VR头显
|
||||
- 如果OpenVR库未安装,请运行:pip install openvr
|
||||
"""
|
||||
|
||||
QMessageBox.information(self, "VR状态", status_text)
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"获取VR状态时发生错误:\n{str(e)}")
|
||||
|
||||
def onShowVRSettings(self):
|
||||
"""显示VR设置对话框"""
|
||||
try:
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
dialog = self.createVRSettingsDialog()
|
||||
dialog.exec_()
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "VR管理器不可用!")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"打开VR设置时发生错误:\n{str(e)}")
|
||||
|
||||
def createVRSettingsDialog(self):
|
||||
"""创建VR设置对话框"""
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle("VR设置")
|
||||
dialog.setModal(True)
|
||||
dialog.resize(400, 300)
|
||||
|
||||
layout = QVBoxLayout(dialog)
|
||||
|
||||
# VR状态显示
|
||||
status_group = QGroupBox("VR状态")
|
||||
status_layout = QVBoxLayout()
|
||||
|
||||
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
|
||||
status = self.world.vr_manager.get_vr_status()
|
||||
|
||||
available_label = QLabel(f"VR可用性: {'是' if status['available'] else '否'}")
|
||||
available_label.setStyleSheet(f"color: {'green' if status['available'] else 'red'};")
|
||||
status_layout.addWidget(available_label)
|
||||
|
||||
enabled_label = QLabel(f"VR状态: {'已启用' if status['enabled'] else '未启用'}")
|
||||
enabled_label.setStyleSheet(f"color: {'green' if status['enabled'] else 'gray'};")
|
||||
status_layout.addWidget(enabled_label)
|
||||
|
||||
resolution_label = QLabel(f"渲染分辨率: {status['eye_resolution'][0]}x{status['eye_resolution'][1]}")
|
||||
status_layout.addWidget(resolution_label)
|
||||
|
||||
status_group.setLayout(status_layout)
|
||||
layout.addWidget(status_group)
|
||||
|
||||
# 渲染设置
|
||||
render_group = QGroupBox("渲染设置")
|
||||
render_layout = QFormLayout()
|
||||
|
||||
# 渲染质量
|
||||
quality_combo = QComboBox()
|
||||
quality_combo.addItems(["低", "中", "高", "超高"])
|
||||
quality_combo.setCurrentText("高")
|
||||
render_layout.addRow("渲染质量:", quality_combo)
|
||||
|
||||
# 抗锯齿
|
||||
aa_combo = QComboBox()
|
||||
aa_combo.addItems(["无", "2x", "4x", "8x"])
|
||||
aa_combo.setCurrentText("4x")
|
||||
render_layout.addRow("抗锯齿:", aa_combo)
|
||||
|
||||
render_group.setLayout(render_layout)
|
||||
layout.addWidget(render_group)
|
||||
|
||||
# 性能设置
|
||||
perf_group = QGroupBox("性能设置")
|
||||
perf_layout = QFormLayout()
|
||||
|
||||
# 刷新率
|
||||
refresh_combo = QComboBox()
|
||||
refresh_combo.addItems(["72Hz", "90Hz", "120Hz", "144Hz"])
|
||||
refresh_combo.setCurrentText("90Hz")
|
||||
perf_layout.addRow("刷新率:", refresh_combo)
|
||||
|
||||
# 异步重投影
|
||||
async_check = QCheckBox("启用异步重投影")
|
||||
async_check.setChecked(True)
|
||||
perf_layout.addRow("", async_check)
|
||||
|
||||
perf_group.setLayout(perf_layout)
|
||||
layout.addWidget(perf_group)
|
||||
|
||||
# 按钮
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
apply_button = QPushButton("应用")
|
||||
ok_button = QPushButton("确定")
|
||||
cancel_button = QPushButton("取消")
|
||||
|
||||
button_layout.addWidget(apply_button)
|
||||
button_layout.addStretch()
|
||||
button_layout.addWidget(ok_button)
|
||||
button_layout.addWidget(cancel_button)
|
||||
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# 连接信号
|
||||
apply_button.clicked.connect(lambda: self.applyVRSettings(dialog))
|
||||
ok_button.clicked.connect(dialog.accept)
|
||||
cancel_button.clicked.connect(dialog.reject)
|
||||
|
||||
return dialog
|
||||
|
||||
def applyVRSettings(self, dialog):
|
||||
"""应用VR设置"""
|
||||
try:
|
||||
# 这里可以实现设置的保存和应用逻辑
|
||||
QMessageBox.information(dialog, "成功", "VR设置已应用!")
|
||||
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()
|
||||
|
||||
@ -1,412 +0,0 @@
|
||||
"""
|
||||
VR控制面板
|
||||
|
||||
提供VR系统的GUI控制界面
|
||||
包括VR启用/禁用、ALVR串流控制、性能监控等功能
|
||||
"""
|
||||
|
||||
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox,
|
||||
QPushButton, QLabel, QProgressBar, QSlider,
|
||||
QSpinBox, QCheckBox, QComboBox, QTextEdit,
|
||||
QGridLayout, QFrame, QSizePolicy)
|
||||
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
|
||||
from PyQt5.QtGui import QFont, QColor, QPalette
|
||||
|
||||
|
||||
class VRControlPanel(QWidget):
|
||||
"""VR控制面板"""
|
||||
|
||||
# 信号定义
|
||||
vr_enabled = pyqtSignal(bool)
|
||||
alvr_streaming_changed = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, world, parent=None):
|
||||
super().__init__(parent)
|
||||
self.world = world
|
||||
self.setupUI()
|
||||
self.connectSignals()
|
||||
|
||||
# 设置更新定时器
|
||||
self.update_timer = QTimer()
|
||||
self.update_timer.timeout.connect(self.updateStatus)
|
||||
self.update_timer.start(1000) # 每秒更新一次
|
||||
|
||||
def setupUI(self):
|
||||
"""设置界面"""
|
||||
self.setWindowTitle("VR控制面板")
|
||||
self.setMinimumSize(400, 600)
|
||||
|
||||
# 主布局
|
||||
main_layout = QVBoxLayout(self)
|
||||
|
||||
# VR系统控制组
|
||||
vr_control_group = self.createVRControlGroup()
|
||||
main_layout.addWidget(vr_control_group)
|
||||
|
||||
# ALVR串流控制组
|
||||
alvr_control_group = self.createALVRControlGroup()
|
||||
main_layout.addWidget(alvr_control_group)
|
||||
|
||||
# 性能监控组
|
||||
performance_group = self.createPerformanceGroup()
|
||||
main_layout.addWidget(performance_group)
|
||||
|
||||
# 控制器状态组
|
||||
controller_group = self.createControllerGroup()
|
||||
main_layout.addWidget(controller_group)
|
||||
|
||||
# 日志输出组
|
||||
log_group = self.createLogGroup()
|
||||
main_layout.addWidget(log_group)
|
||||
|
||||
# 添加弹性空间
|
||||
main_layout.addStretch()
|
||||
|
||||
def createVRControlGroup(self):
|
||||
"""创建VR系统控制组"""
|
||||
group = QGroupBox("VR系统控制")
|
||||
layout = QVBoxLayout(group)
|
||||
|
||||
# 状态指示器
|
||||
self.vr_status_label = QLabel("VR状态: 未启用")
|
||||
self.vr_status_label.setStyleSheet("color: red; font-weight: bold;")
|
||||
layout.addWidget(self.vr_status_label)
|
||||
|
||||
# 启用/禁用VR按钮
|
||||
button_layout = QHBoxLayout()
|
||||
self.enable_vr_button = QPushButton("启用VR")
|
||||
self.enable_vr_button.clicked.connect(self.enableVR)
|
||||
button_layout.addWidget(self.enable_vr_button)
|
||||
|
||||
self.disable_vr_button = QPushButton("禁用VR")
|
||||
self.disable_vr_button.clicked.connect(self.disableVR)
|
||||
self.disable_vr_button.setEnabled(False)
|
||||
button_layout.addWidget(self.disable_vr_button)
|
||||
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# VR设备信息
|
||||
self.vr_info_label = QLabel("VR设备: 未连接")
|
||||
layout.addWidget(self.vr_info_label)
|
||||
|
||||
# VR选项
|
||||
options_layout = QGridLayout()
|
||||
|
||||
# 控制器射线显示
|
||||
self.show_rays_checkbox = QCheckBox("显示控制器射线")
|
||||
self.show_rays_checkbox.stateChanged.connect(self.toggleControllerRays)
|
||||
options_layout.addWidget(self.show_rays_checkbox, 0, 0)
|
||||
|
||||
# 手势识别
|
||||
self.gesture_checkbox = QCheckBox("启用手势识别")
|
||||
self.gesture_checkbox.stateChanged.connect(self.toggleGestureRecognition)
|
||||
options_layout.addWidget(self.gesture_checkbox, 0, 1)
|
||||
|
||||
# VR交互
|
||||
self.interaction_checkbox = QCheckBox("启用VR交互")
|
||||
self.interaction_checkbox.setChecked(True)
|
||||
self.interaction_checkbox.stateChanged.connect(self.toggleVRInteraction)
|
||||
options_layout.addWidget(self.interaction_checkbox, 1, 0)
|
||||
|
||||
layout.addLayout(options_layout)
|
||||
|
||||
return group
|
||||
|
||||
def createALVRControlGroup(self):
|
||||
"""创建ALVR串流控制组"""
|
||||
group = QGroupBox("ALVR串流控制")
|
||||
layout = QVBoxLayout(group)
|
||||
|
||||
# 连接状态
|
||||
self.alvr_status_label = QLabel("ALVR状态: 未连接")
|
||||
self.alvr_status_label.setStyleSheet("color: red; font-weight: bold;")
|
||||
layout.addWidget(self.alvr_status_label)
|
||||
|
||||
# 串流控制按钮
|
||||
button_layout = QHBoxLayout()
|
||||
self.start_streaming_button = QPushButton("开始串流")
|
||||
self.start_streaming_button.clicked.connect(self.startStreaming)
|
||||
self.start_streaming_button.setEnabled(False)
|
||||
button_layout.addWidget(self.start_streaming_button)
|
||||
|
||||
self.stop_streaming_button = QPushButton("停止串流")
|
||||
self.stop_streaming_button.clicked.connect(self.stopStreaming)
|
||||
self.stop_streaming_button.setEnabled(False)
|
||||
button_layout.addWidget(self.stop_streaming_button)
|
||||
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# 串流质量设置
|
||||
quality_layout = QGridLayout()
|
||||
|
||||
# 分辨率
|
||||
quality_layout.addWidget(QLabel("分辨率宽度:"), 0, 0)
|
||||
self.width_spinbox = QSpinBox()
|
||||
self.width_spinbox.setRange(1920, 4096)
|
||||
self.width_spinbox.setValue(2880)
|
||||
self.width_spinbox.setSuffix(" px")
|
||||
quality_layout.addWidget(self.width_spinbox, 0, 1)
|
||||
|
||||
quality_layout.addWidget(QLabel("分辨率高度:"), 1, 0)
|
||||
self.height_spinbox = QSpinBox()
|
||||
self.height_spinbox.setRange(1080, 2160)
|
||||
self.height_spinbox.setValue(1700)
|
||||
self.height_spinbox.setSuffix(" px")
|
||||
quality_layout.addWidget(self.height_spinbox, 1, 1)
|
||||
|
||||
# 帧率
|
||||
quality_layout.addWidget(QLabel("帧率:"), 2, 0)
|
||||
self.fps_spinbox = QSpinBox()
|
||||
self.fps_spinbox.setRange(60, 120)
|
||||
self.fps_spinbox.setValue(72)
|
||||
self.fps_spinbox.setSuffix(" fps")
|
||||
quality_layout.addWidget(self.fps_spinbox, 2, 1)
|
||||
|
||||
# 比特率
|
||||
quality_layout.addWidget(QLabel("比特率:"), 3, 0)
|
||||
self.bitrate_spinbox = QSpinBox()
|
||||
self.bitrate_spinbox.setRange(50, 500)
|
||||
self.bitrate_spinbox.setValue(150)
|
||||
self.bitrate_spinbox.setSuffix(" Mbps")
|
||||
quality_layout.addWidget(self.bitrate_spinbox, 3, 1)
|
||||
|
||||
layout.addLayout(quality_layout)
|
||||
|
||||
# 应用设置按钮
|
||||
self.apply_quality_button = QPushButton("应用质量设置")
|
||||
self.apply_quality_button.clicked.connect(self.applyQualitySettings)
|
||||
layout.addWidget(self.apply_quality_button)
|
||||
|
||||
return group
|
||||
|
||||
def createPerformanceGroup(self):
|
||||
"""创建性能监控组"""
|
||||
group = QGroupBox("性能监控")
|
||||
layout = QGridLayout(group)
|
||||
|
||||
# FPS显示
|
||||
layout.addWidget(QLabel("渲染FPS:"), 0, 0)
|
||||
self.fps_label = QLabel("0")
|
||||
self.fps_label.setStyleSheet("font-weight: bold; color: blue;")
|
||||
layout.addWidget(self.fps_label, 0, 1)
|
||||
|
||||
# 串流FPS
|
||||
layout.addWidget(QLabel("串流FPS:"), 1, 0)
|
||||
self.stream_fps_label = QLabel("0")
|
||||
self.stream_fps_label.setStyleSheet("font-weight: bold; color: green;")
|
||||
layout.addWidget(self.stream_fps_label, 1, 1)
|
||||
|
||||
# 延迟
|
||||
layout.addWidget(QLabel("延迟:"), 2, 0)
|
||||
self.latency_label = QLabel("0 ms")
|
||||
self.latency_label.setStyleSheet("font-weight: bold; color: orange;")
|
||||
layout.addWidget(self.latency_label, 2, 1)
|
||||
|
||||
# 性能进度条
|
||||
layout.addWidget(QLabel("CPU使用率:"), 3, 0)
|
||||
self.cpu_progress = QProgressBar()
|
||||
self.cpu_progress.setRange(0, 100)
|
||||
layout.addWidget(self.cpu_progress, 3, 1)
|
||||
|
||||
layout.addWidget(QLabel("GPU使用率:"), 4, 0)
|
||||
self.gpu_progress = QProgressBar()
|
||||
self.gpu_progress.setRange(0, 100)
|
||||
layout.addWidget(self.gpu_progress, 4, 1)
|
||||
|
||||
return group
|
||||
|
||||
def createControllerGroup(self):
|
||||
"""创建控制器状态组"""
|
||||
group = QGroupBox("控制器状态")
|
||||
layout = QVBoxLayout(group)
|
||||
|
||||
# 控制器列表
|
||||
self.controller_list = QTextEdit()
|
||||
self.controller_list.setMaximumHeight(100)
|
||||
self.controller_list.setReadOnly(True)
|
||||
layout.addWidget(self.controller_list)
|
||||
|
||||
# 触觉反馈测试
|
||||
haptic_layout = QHBoxLayout()
|
||||
haptic_layout.addWidget(QLabel("触觉反馈测试:"))
|
||||
|
||||
self.haptic_button = QPushButton("发送震动")
|
||||
self.haptic_button.clicked.connect(self.sendHapticFeedback)
|
||||
haptic_layout.addWidget(self.haptic_button)
|
||||
|
||||
layout.addLayout(haptic_layout)
|
||||
|
||||
return group
|
||||
|
||||
def createLogGroup(self):
|
||||
"""创建日志输出组"""
|
||||
group = QGroupBox("系统日志")
|
||||
layout = QVBoxLayout(group)
|
||||
|
||||
self.log_text = QTextEdit()
|
||||
self.log_text.setMaximumHeight(150)
|
||||
self.log_text.setReadOnly(True)
|
||||
layout.addWidget(self.log_text)
|
||||
|
||||
# 清空日志按钮
|
||||
clear_button = QPushButton("清空日志")
|
||||
clear_button.clicked.connect(self.clearLog)
|
||||
layout.addWidget(clear_button)
|
||||
|
||||
return group
|
||||
|
||||
def connectSignals(self):
|
||||
"""连接信号"""
|
||||
# 连接值变化信号
|
||||
self.width_spinbox.valueChanged.connect(self.onQualityChanged)
|
||||
self.height_spinbox.valueChanged.connect(self.onQualityChanged)
|
||||
self.fps_spinbox.valueChanged.connect(self.onQualityChanged)
|
||||
self.bitrate_spinbox.valueChanged.connect(self.onQualityChanged)
|
||||
|
||||
def enableVR(self):
|
||||
"""启用VR"""
|
||||
self.addLog("正在启用VR模式...")
|
||||
if self.world.enableVRMode():
|
||||
self.addLog("✓ VR模式已启用")
|
||||
self.vr_enabled.emit(True)
|
||||
else:
|
||||
self.addLog("✗ VR模式启用失败")
|
||||
|
||||
def disableVR(self):
|
||||
"""禁用VR"""
|
||||
self.addLog("正在禁用VR模式...")
|
||||
self.world.disableVRMode()
|
||||
self.addLog("✓ VR模式已禁用")
|
||||
self.vr_enabled.emit(False)
|
||||
|
||||
def startStreaming(self):
|
||||
"""开始串流"""
|
||||
self.addLog("正在开始ALVR串流...")
|
||||
if self.world.startALVRStreaming():
|
||||
self.addLog("✓ ALVR串流已开始")
|
||||
self.alvr_streaming_changed.emit(True)
|
||||
else:
|
||||
self.addLog("✗ ALVR串流开始失败")
|
||||
|
||||
def stopStreaming(self):
|
||||
"""停止串流"""
|
||||
self.addLog("正在停止ALVR串流...")
|
||||
self.world.stopALVRStreaming()
|
||||
self.addLog("✓ ALVR串流已停止")
|
||||
self.alvr_streaming_changed.emit(False)
|
||||
|
||||
def toggleControllerRays(self, checked):
|
||||
"""切换控制器射线显示"""
|
||||
self.world.showControllerRays(checked)
|
||||
self.addLog(f"控制器射线: {'显示' if checked else '隐藏'}")
|
||||
|
||||
def toggleGestureRecognition(self, checked):
|
||||
"""切换手势识别"""
|
||||
self.world.setVRGestureEnabled(checked)
|
||||
self.addLog(f"手势识别: {'启用' if checked else '禁用'}")
|
||||
|
||||
def toggleVRInteraction(self, checked):
|
||||
"""切换VR交互"""
|
||||
self.world.setVRInteractionEnabled(checked)
|
||||
self.addLog(f"VR交互: {'启用' if checked else '禁用'}")
|
||||
|
||||
def applyQualitySettings(self):
|
||||
"""应用质量设置"""
|
||||
width = self.width_spinbox.value()
|
||||
height = self.height_spinbox.value()
|
||||
fps = self.fps_spinbox.value()
|
||||
bitrate = self.bitrate_spinbox.value()
|
||||
|
||||
self.world.setALVRStreamQuality(width, height, fps, bitrate)
|
||||
self.addLog(f"串流质量已设置: {width}x{height} @ {fps}fps, {bitrate}Mbps")
|
||||
|
||||
def sendHapticFeedback(self):
|
||||
"""发送触觉反馈"""
|
||||
controllers = self.world.getAllControllers()
|
||||
if controllers:
|
||||
controller_id = controllers[0] # 使用第一个控制器
|
||||
self.world.sendHapticFeedback(controller_id, 0.5, 0.8)
|
||||
self.addLog(f"已发送触觉反馈到控制器 {controller_id}")
|
||||
else:
|
||||
self.addLog("没有可用的控制器")
|
||||
|
||||
def onQualityChanged(self):
|
||||
"""质量设置变化"""
|
||||
# 可以在这里实现实时质量调整
|
||||
pass
|
||||
|
||||
def updateStatus(self):
|
||||
"""更新状态显示"""
|
||||
# 更新VR状态
|
||||
vr_status = self.world.getVRStatus()
|
||||
|
||||
# VR系统状态
|
||||
if vr_status['vr_enabled']:
|
||||
self.vr_status_label.setText("VR状态: 已启用")
|
||||
self.vr_status_label.setStyleSheet("color: green; font-weight: bold;")
|
||||
self.enable_vr_button.setEnabled(False)
|
||||
self.disable_vr_button.setEnabled(True)
|
||||
|
||||
# 更新VR设备信息
|
||||
vr_info = vr_status['vr_info']
|
||||
if vr_info:
|
||||
device_info = f"VR设备: {vr_info.get('hmd_manufacturer', 'Unknown')} {vr_info.get('hmd_model', 'Unknown')}"
|
||||
self.vr_info_label.setText(device_info)
|
||||
else:
|
||||
self.vr_status_label.setText("VR状态: 未启用")
|
||||
self.vr_status_label.setStyleSheet("color: red; font-weight: bold;")
|
||||
self.enable_vr_button.setEnabled(True)
|
||||
self.disable_vr_button.setEnabled(False)
|
||||
self.vr_info_label.setText("VR设备: 未连接")
|
||||
|
||||
# ALVR状态
|
||||
if vr_status['alvr_connected']:
|
||||
self.alvr_status_label.setText("ALVR状态: 已连接")
|
||||
self.alvr_status_label.setStyleSheet("color: green; font-weight: bold;")
|
||||
self.start_streaming_button.setEnabled(not vr_status['alvr_streaming'])
|
||||
self.stop_streaming_button.setEnabled(vr_status['alvr_streaming'])
|
||||
else:
|
||||
self.alvr_status_label.setText("ALVR状态: 未连接")
|
||||
self.alvr_status_label.setStyleSheet("color: red; font-weight: bold;")
|
||||
self.start_streaming_button.setEnabled(False)
|
||||
self.stop_streaming_button.setEnabled(False)
|
||||
|
||||
# 性能统计
|
||||
streaming_status = vr_status['streaming_status']
|
||||
if streaming_status:
|
||||
self.stream_fps_label.setText(str(streaming_status.get('fps', 0)))
|
||||
self.latency_label.setText(f"{streaming_status.get('latency', 0)} ms")
|
||||
|
||||
# 控制器状态
|
||||
controllers = vr_status['controllers']
|
||||
if controllers:
|
||||
controller_text = f"已连接 {len(controllers)} 个控制器:\n"
|
||||
for i, controller_id in enumerate(controllers):
|
||||
controller_text += f"控制器 {i+1}: ID {controller_id}\n"
|
||||
self.controller_list.setText(controller_text)
|
||||
else:
|
||||
self.controller_list.setText("没有连接的控制器")
|
||||
|
||||
def addLog(self, message):
|
||||
"""添加日志"""
|
||||
import datetime
|
||||
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
|
||||
log_message = f"[{timestamp}] {message}"
|
||||
self.log_text.append(log_message)
|
||||
print(log_message) # 同时输出到控制台
|
||||
|
||||
def clearLog(self):
|
||||
"""清空日志"""
|
||||
self.log_text.clear()
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""关闭事件"""
|
||||
# 停止更新定时器
|
||||
if hasattr(self, 'update_timer'):
|
||||
self.update_timer.stop()
|
||||
|
||||
# 如果VR启用,先禁用
|
||||
if self.world.isVREnabled():
|
||||
self.disableVR()
|
||||
|
||||
event.accept()
|
||||
89
vr_actions/actions.json
Normal file
89
vr_actions/actions.json
Normal 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": "震动反馈"
|
||||
}
|
||||
]
|
||||
}
|
||||
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
106
vr_actions/bindings_vive.json
Normal file
106
vr_actions/bindings_vive.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
468
vr_test.py
468
vr_test.py
@ -1,468 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
VR功能测试脚本
|
||||
测试VR系统的各种功能,包括模拟模式和真实VR模式
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from main import MyWorld
|
||||
|
||||
def test_basic_vr_functionality():
|
||||
"""测试基本VR功能"""
|
||||
print("=== VR基本功能测试 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 检查VR管理器是否正确初始化
|
||||
print("✓ VR管理器已创建")
|
||||
|
||||
# 检查OpenVR库是否可用
|
||||
try:
|
||||
import openvr
|
||||
print("✓ OpenVR库已安装")
|
||||
except ImportError:
|
||||
print("⚠ OpenVR库未安装,将使用模拟模式")
|
||||
|
||||
# 测试VR初始化(会自动回退到模拟模式)
|
||||
print("\n正在测试VR初始化...")
|
||||
vr_success = world.initializeVR()
|
||||
|
||||
if vr_success:
|
||||
print("✓ VR系统初始化成功")
|
||||
|
||||
# 获取VR信息
|
||||
vr_info = world.getVRInfo()
|
||||
print(f"\n📊 VR系统信息:")
|
||||
print(f" - 模式: {vr_info['mode']}")
|
||||
print(f" - 启用状态: {vr_info['enabled']}")
|
||||
print(f" - 模拟模式: {vr_info['simulation_mode']}")
|
||||
print(f" - 渲染尺寸: {vr_info['render_size']}")
|
||||
print(f" - 控制器数量: {vr_info.get('controllers', 0)}")
|
||||
|
||||
# 测试控制器输入
|
||||
print("\n🎮 测试控制器输入:")
|
||||
for i in range(2):
|
||||
controller_input = world.vr_manager.get_controller_input(i)
|
||||
if controller_input and controller_input.get('connected'):
|
||||
print(f" 控制器 {i}: 已连接")
|
||||
print(f" - 扳机: {controller_input['trigger']:.2f}")
|
||||
print(f" - 握把: {controller_input['grip']:.2f}")
|
||||
print(f" - 触摸板: ({controller_input['touchpad']['x']:.2f}, {controller_input['touchpad']['y']:.2f})")
|
||||
else:
|
||||
print(f" 控制器 {i}: 未连接")
|
||||
|
||||
# 测试VR状态
|
||||
print("\n📊 VR系统状态:")
|
||||
status = world.getVRStatus()
|
||||
print(f" - VR启用: {status['vr_enabled']}")
|
||||
print(f" - 控制器总数: {len(status['controllers'])}")
|
||||
|
||||
else:
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 关闭VR系统
|
||||
world.shutdownVR()
|
||||
print("\n✓ VR系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
def test_simulation_mode():
|
||||
"""测试VR模拟模式"""
|
||||
print("=== VR模拟模式测试 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 强制启用模拟模式
|
||||
print("强制启用VR模拟模式...")
|
||||
vr_success = world.vr_manager.enable_simulation_mode()
|
||||
|
||||
if vr_success:
|
||||
print("✓ VR模拟模式启用成功")
|
||||
|
||||
# 获取模拟数据
|
||||
sim_data = world.vr_manager.get_simulation_data()
|
||||
if sim_data:
|
||||
print(f"\n🎮 模拟数据:")
|
||||
print(f" - 头部位置: {sim_data['head_pose']['position']}")
|
||||
print(f" - 控制器 0 位置: {sim_data['controller_poses'][0]['position']}")
|
||||
print(f" - 控制器 1 位置: {sim_data['controller_poses'][1]['position']}")
|
||||
print(f" - 渲染尺寸: {sim_data['render_size']}")
|
||||
|
||||
# 测试更新模拟数据
|
||||
print(f"\n🔧 测试模拟数据更新:")
|
||||
new_head_pos = [0.1, 0.1, 1.7]
|
||||
success = world.vr_manager.update_simulation_data('head_pose', {
|
||||
'position': new_head_pos,
|
||||
'rotation': [0, 0, 0, 1]
|
||||
})
|
||||
if success:
|
||||
print(f" ✓ 头部位置更新为: {new_head_pos}")
|
||||
|
||||
# 测试控制器输入(模拟)
|
||||
print(f"\n🎮 模拟控制器输入测试:")
|
||||
for i in range(2):
|
||||
controller_input = world.vr_manager.get_controller_input(i)
|
||||
if controller_input:
|
||||
print(f" 控制器 {i}:")
|
||||
print(f" - 连接状态: {controller_input['connected']}")
|
||||
print(f" - 扳机: {controller_input['trigger']}")
|
||||
print(f" - 握把: {controller_input['grip']}")
|
||||
print(f" - 菜单键: {controller_input['menu']}")
|
||||
|
||||
else:
|
||||
print("✗ VR模拟模式启用失败")
|
||||
return False
|
||||
|
||||
# 关闭VR系统
|
||||
world.vr_manager.shutdown_vr()
|
||||
print("\n✓ VR模拟模式已关闭")
|
||||
|
||||
return True
|
||||
|
||||
def test_alvr_streaming():
|
||||
"""测试ALVR串流功能"""
|
||||
print("=== ALVR串流测试 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 初始化VR系统
|
||||
print("初始化VR系统...")
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 测试ALVR初始化
|
||||
print("\n正在测试ALVR初始化...")
|
||||
alvr_success = world.initializeALVR()
|
||||
|
||||
if alvr_success:
|
||||
print("✓ ALVR初始化成功")
|
||||
|
||||
# 测试串流状态
|
||||
print("\n📊 ALVR串流状态:")
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f" - 连接状态: {world.isALVRConnected()}")
|
||||
print(f" - 串流状态: {world.isALVRStreaming()}")
|
||||
|
||||
# 测试串流质量设置
|
||||
print("\n🎥 测试串流质量设置:")
|
||||
quality_success = world.setALVRStreamQuality(1920, 1080, 60, 100)
|
||||
if quality_success:
|
||||
print(" ✓ 串流质量设置成功: 1920x1080@60fps, 100Mbps")
|
||||
else:
|
||||
print(" ⚠ 串流质量设置失败")
|
||||
|
||||
# 测试触觉反馈
|
||||
print("\n🔔 测试触觉反馈:")
|
||||
for controller_id in range(2):
|
||||
feedback_success = world.sendHapticFeedback(controller_id, 0.1, 0.5)
|
||||
if feedback_success:
|
||||
print(f" ✓ 控制器 {controller_id} 触觉反馈发送成功")
|
||||
else:
|
||||
print(f" ⚠ 控制器 {controller_id} 触觉反馈发送失败")
|
||||
|
||||
# 测试串流控制
|
||||
print("\n🎮 测试串流控制:")
|
||||
start_success = world.startALVRStreaming()
|
||||
if start_success:
|
||||
print(" ✓ 串流开始成功")
|
||||
else:
|
||||
print(" ⚠ 串流开始失败")
|
||||
|
||||
# 等待一下然后停止
|
||||
import time
|
||||
time.sleep(1)
|
||||
|
||||
stop_success = world.stopALVRStreaming()
|
||||
if stop_success:
|
||||
print(" ✓ 串流停止成功")
|
||||
else:
|
||||
print(" ⚠ 串流停止失败")
|
||||
|
||||
else:
|
||||
print("⚠ ALVR初始化失败(这在没有ALVR服务器时是正常的)")
|
||||
print(" 提示: 要使用ALVR功能,请:")
|
||||
print(" 1. 下载并安装ALVR服务器")
|
||||
print(" 2. 启动ALVR服务器")
|
||||
print(" 3. 在Quest头盔上安装并启动ALVR客户端")
|
||||
|
||||
# 关闭系统
|
||||
world.shutdownALVR()
|
||||
world.shutdownVR()
|
||||
print("\n✓ ALVR和VR系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
def test_vr_gui_control_panel():
|
||||
"""测试VR GUI控制面板"""
|
||||
print("=== VR GUI控制面板测试 ===")
|
||||
|
||||
try:
|
||||
from ui.vr_control_panel import VRControlPanel
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
# 创建Qt应用程序
|
||||
app = QApplication.instance()
|
||||
if app is None:
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 创建VR控制面板
|
||||
print("创建VR控制面板...")
|
||||
control_panel = VRControlPanel(world)
|
||||
|
||||
# 测试面板功能
|
||||
print("✓ VR控制面板创建成功")
|
||||
print(" - 面板标题:", control_panel.windowTitle())
|
||||
print(" - 面板大小:", control_panel.size().width(), "x", control_panel.size().height())
|
||||
|
||||
# 显示面板(非阻塞)
|
||||
control_panel.show()
|
||||
print("✓ VR控制面板已显示")
|
||||
|
||||
print("\n💡 GUI控制面板功能:")
|
||||
print(" - VR系统开关控制")
|
||||
print(" - ALVR串流控制")
|
||||
print(" - 实时状态监控")
|
||||
print(" - 质量设置调整")
|
||||
print(" - 控制器状态显示")
|
||||
print(" - 性能监控")
|
||||
|
||||
# 短暂显示然后关闭
|
||||
import time
|
||||
time.sleep(2)
|
||||
control_panel.close()
|
||||
|
||||
print("\n✓ VR控制面板测试完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ VR控制面板测试失败: {str(e)}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_vr_interaction():
|
||||
"""测试VR交互功能"""
|
||||
print("=== VR交互功能测试 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 初始化VR系统
|
||||
print("初始化VR系统...")
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 测试VR输入处理
|
||||
print("\n🎮 测试VR输入处理:")
|
||||
input_success = world.startVRInput()
|
||||
if input_success:
|
||||
print(" ✓ VR输入处理启动成功")
|
||||
|
||||
# 测试控制器射线显示
|
||||
print("\n🌟 测试控制器射线:")
|
||||
ray_success = world.showControllerRays(True)
|
||||
if ray_success:
|
||||
print(" ✓ 控制器射线显示启用")
|
||||
else:
|
||||
print(" ⚠ 控制器射线显示失败")
|
||||
|
||||
# 测试手势识别
|
||||
print("\n✋ 测试手势识别:")
|
||||
gesture_success = world.setVRGestureEnabled(True)
|
||||
if gesture_success:
|
||||
print(" ✓ VR手势识别启用")
|
||||
else:
|
||||
print(" ⚠ VR手势识别启用失败")
|
||||
|
||||
# 测试VR交互
|
||||
print("\n🤏 测试VR交互:")
|
||||
interaction_success = world.setVRInteractionEnabled(True)
|
||||
if interaction_success:
|
||||
print(" ✓ VR交互功能启用")
|
||||
else:
|
||||
print(" ⚠ VR交互功能启用失败")
|
||||
|
||||
# 获取控制器状态
|
||||
print("\n🎮 控制器状态:")
|
||||
controllers = world.getAllControllers()
|
||||
for i, controller in enumerate(controllers):
|
||||
if controller:
|
||||
print(f" 控制器 {i}: 可用")
|
||||
state = world.getControllerState(i)
|
||||
if state:
|
||||
print(f" - 位置: {state.get('position', 'N/A')}")
|
||||
print(f" - 旋转: {state.get('rotation', 'N/A')}")
|
||||
print(f" - 按钮: {state.get('buttons', 'N/A')}")
|
||||
else:
|
||||
print(f" 控制器 {i}: 不可用")
|
||||
|
||||
# 停止VR输入
|
||||
world.stopVRInput()
|
||||
print("\n✓ VR输入处理已停止")
|
||||
|
||||
else:
|
||||
print(" ⚠ VR输入处理启动失败")
|
||||
|
||||
# 关闭VR系统
|
||||
world.shutdownVR()
|
||||
print("\n✓ VR系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
def run_all_tests():
|
||||
"""运行所有测试"""
|
||||
print("=== 运行所有VR测试 ===")
|
||||
|
||||
tests = [
|
||||
("基本VR功能", test_basic_vr_functionality),
|
||||
("VR模拟模式", test_simulation_mode),
|
||||
("ALVR串流", test_alvr_streaming),
|
||||
("VR GUI控制面板", test_vr_gui_control_panel),
|
||||
("VR交互功能", test_vr_interaction)
|
||||
]
|
||||
|
||||
results = []
|
||||
for test_name, test_func in tests:
|
||||
print(f"\n{'='*50}")
|
||||
print(f"正在运行: {test_name}")
|
||||
print('='*50)
|
||||
|
||||
try:
|
||||
success = test_func()
|
||||
results.append((test_name, success))
|
||||
if success:
|
||||
print(f"✓ {test_name} 测试通过")
|
||||
else:
|
||||
print(f"✗ {test_name} 测试失败")
|
||||
except Exception as e:
|
||||
print(f"✗ {test_name} 测试出错: {str(e)}")
|
||||
results.append((test_name, False))
|
||||
|
||||
# 打印总结
|
||||
print(f"\n{'='*50}")
|
||||
print("测试总结")
|
||||
print('='*50)
|
||||
|
||||
passed = sum(1 for _, success in results if success)
|
||||
total = len(results)
|
||||
|
||||
for test_name, success in results:
|
||||
status = "✓ 通过" if success else "✗ 失败"
|
||||
print(f"{test_name}: {status}")
|
||||
|
||||
print(f"\n总计: {passed}/{total} 个测试通过")
|
||||
|
||||
if passed == total:
|
||||
print("🎉 所有测试都通过了!")
|
||||
else:
|
||||
print("⚠ 部分测试失败,请检查上述输出")
|
||||
|
||||
return passed == total
|
||||
|
||||
def print_vr_requirements():
|
||||
"""打印VR系统要求"""
|
||||
print("📋 VR系统要求说明")
|
||||
print("="*50)
|
||||
print("🔧 软件要求:")
|
||||
print(" - Python 3.8+")
|
||||
print(" - Panda3D")
|
||||
print(" - PyQt5")
|
||||
print(" - OpenVR库 (pip install openvr)")
|
||||
print(" - 其他依赖见 requirements/vr-requirements.txt")
|
||||
|
||||
print("\n🎮 硬件要求(完整VR模式):")
|
||||
print(" - 支持VR的显卡 (GTX 1060/RX 580 或更好)")
|
||||
print(" - VR头盔 (Quest 2/3, Valve Index, HTC Vive等)")
|
||||
print(" - 足够的USB端口或无线连接")
|
||||
|
||||
print("\n🌐 连接方式:")
|
||||
print(" 有线连接:")
|
||||
print(" - Valve Index: DisplayPort + USB 3.0")
|
||||
print(" - HTC Vive: HDMI + USB 3.0")
|
||||
print(" - Quest 2/3: USB-C (Quest Link)")
|
||||
print(" 无线连接:")
|
||||
print(" - Quest 2/3: 通过ALVR串流")
|
||||
print(" - 需要5GHz Wi-Fi网络")
|
||||
print(" - 需要ALVR服务器运行")
|
||||
|
||||
print("\n🔄 启动顺序(完整VR模式):")
|
||||
print(" 1. 确保VR头盔已连接并识别")
|
||||
print(" 2. 启动SteamVR")
|
||||
print(" 3. (可选) 启动ALVR服务器(Quest无线)")
|
||||
print(" 4. 运行VR测试脚本")
|
||||
|
||||
print("\n🎮 模拟模式说明:")
|
||||
print(" - 无需VR硬件")
|
||||
print(" - 模拟头盔和控制器追踪")
|
||||
print(" - 立体渲染到窗口")
|
||||
print(" - 适用于开发和调试")
|
||||
|
||||
print("\n💡 故障排除:")
|
||||
print(" - 如果OpenVR初始化失败,系统会自动切换到模拟模式")
|
||||
print(" - 检查SteamVR是否正在运行")
|
||||
print(" - 确认VR头盔被系统识别")
|
||||
print(" - 检查USB/DisplayPort连接")
|
||||
print(" - 重启SteamVR和头盔")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🎮 VR功能测试脚本")
|
||||
print("="*50)
|
||||
|
||||
# 显示要求说明
|
||||
print_vr_requirements()
|
||||
|
||||
print("\n请选择测试类型:")
|
||||
print("1. 基本VR功能测试")
|
||||
print("2. VR模拟模式测试")
|
||||
print("3. ALVR串流测试")
|
||||
print("4. VR GUI控制面板")
|
||||
print("5. VR交互功能测试")
|
||||
print("6. 运行所有测试")
|
||||
print("7. 查看VR系统要求")
|
||||
|
||||
try:
|
||||
choice = input("请输入选择 (1-7): ")
|
||||
|
||||
if choice == "1":
|
||||
success = test_basic_vr_functionality()
|
||||
elif choice == "2":
|
||||
success = test_simulation_mode()
|
||||
elif choice == "3":
|
||||
success = test_alvr_streaming()
|
||||
elif choice == "4":
|
||||
success = test_vr_gui_control_panel()
|
||||
elif choice == "5":
|
||||
success = test_vr_interaction()
|
||||
elif choice == "6":
|
||||
success = run_all_tests()
|
||||
elif choice == "7":
|
||||
print_vr_requirements()
|
||||
return
|
||||
else:
|
||||
print("无效的选择")
|
||||
return
|
||||
|
||||
print("\n" + "="*50)
|
||||
if success:
|
||||
print("✓ 测试成功")
|
||||
else:
|
||||
print("✗ 测试失败")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n用户中断测试")
|
||||
except Exception as e:
|
||||
print(f"测试过程中发生错误: {str(e)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user