docs(README): 添加项目README文档
- 创建详细的README文档,介绍项目功能、架构、使用方法等 - 新增.pyc文件和目录,用于缓存编译后的Python代码 - 添加.lingma规则文件,用于代码格式检查 - 删除ALVR串流处理器代码,准备替换为OpenXR输入处理器 - 新增OpenXR输入处理器代码,处理VR控制器输入
This commit is contained in:
parent
bb56813e44
commit
72819341ef
4
.lingma/rules/panda3d.md
Normal file
4
.lingma/rules/panda3d.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
在进行panda3d项目的开发时,必须参考api文档,@https://docs.panda3d.org/1.10/python/reference/index
|
||||
BIN
QPanda3D/__pycache__/Panda3DWorld.cpython-313.pyc
Normal file
BIN
QPanda3D/__pycache__/Panda3DWorld.cpython-313.pyc
Normal file
Binary file not shown.
BIN
QPanda3D/__pycache__/QPanda3DWidget.cpython-313.pyc
Normal file
BIN
QPanda3D/__pycache__/QPanda3DWidget.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
QPanda3D/__pycache__/QPanda3D_Keys_Translation.cpython-313.pyc
Normal file
BIN
QPanda3D/__pycache__/QPanda3D_Keys_Translation.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
QPanda3D/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
QPanda3D/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
151
README.md
151
README.md
@ -0,0 +1,151 @@
|
||||
# EG - 3D编辑器与游戏引擎
|
||||
|
||||
EG是一个功能强大的3D编辑器和游戏引擎,基于Panda3D和PyQt5构建,提供直观的可视化编辑功能和VR支持。该引擎适用于游戏开发、3D可视化和交互式应用程序创建。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 核心功能
|
||||
- **3D场景编辑与管理**:完整的场景编辑工具集
|
||||
- **模型导入与处理**:支持多种3D模型格式导入
|
||||
- **材质系统**:高级材质编辑和管理
|
||||
- **光照系统**:环境光、定向光、聚光灯和点光源
|
||||
- **相机控制系统**:灵活的相机操作和视角控制
|
||||
- **场景保存与加载**:BAM格式场景保存和恢复
|
||||
|
||||
### 编辑器功能
|
||||
- **对象选择与变换**:精确的对象选择和变换工具
|
||||
- **坐标轴拖拽控制**:直观的3D变换控制
|
||||
- **属性面板编辑**:实时编辑对象属性
|
||||
- **场景树管理**:层次化场景结构管理
|
||||
- **材质编辑器**:可视化材质编辑
|
||||
- **日夜循环系统**:动态光照和环境变化
|
||||
|
||||
### GUI系统
|
||||
- **2D GUI元素**:按钮、标签、输入框、滑块等
|
||||
- **3D空间GUI**:3D文本和虚拟屏幕
|
||||
- **GUI编辑模式**:可视化GUI布局编辑
|
||||
- **事件处理系统**:GUI交互事件响应
|
||||
|
||||
### VR支持
|
||||
- **VR系统集成**:完整的VR环境支持
|
||||
- **VR控制器输入处理**:精确的控制器输入识别
|
||||
- **ALVR串流支持**:无线VR体验
|
||||
- **触觉反馈**:增强沉浸感的触觉反馈
|
||||
- **VR手势识别**:自然的手势交互
|
||||
|
||||
### 脚本系统
|
||||
- **Python脚本热重载**:实时脚本更新无需重启
|
||||
- **脚本模板创建**:快速创建新脚本
|
||||
- **对象脚本管理**:为游戏对象添加行为脚本
|
||||
- **脚本调试支持**:内置调试功能
|
||||
|
||||
### 项目管理
|
||||
- **项目创建与保存**:完整的项目管理功能
|
||||
- **资源管理**:统一的资源导入和管理
|
||||
- **项目打包**:一键打包发布项目
|
||||
|
||||
## 系统架构
|
||||
|
||||
### 核心模块
|
||||
- `core/world.py` - 核心世界管理,集成所有子系统
|
||||
- `core/event_handler.py` - 输入事件处理系统
|
||||
- `core/selection.py` - 对象选择和变换系统
|
||||
- `core/tool_manager.py` - 编辑工具管理
|
||||
- `core/script_system.py` - Python脚本系统
|
||||
- `core/vr_manager.py` - VR系统管理
|
||||
- `core/vr_input_handler.py` - VR输入处理
|
||||
- `core/alvr_streamer.py` - ALVR串流支持
|
||||
|
||||
### 子系统
|
||||
- `scene/scene_manager.py` - 场景管理系统
|
||||
- `gui/gui_manager.py` - GUI系统管理
|
||||
- `ui/` - 用户界面组件
|
||||
- `ui/main_window.py` - 主窗口设置
|
||||
- `ui/property_panel.py` - 属性面板
|
||||
- `ui/widgets.py` - 自定义UI组件
|
||||
- `ui/interface_manager.py` - 界面管理
|
||||
- `project/project_manager.py` - 项目管理系统
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 系统要求
|
||||
- Python 3.7+
|
||||
- Panda3D
|
||||
- PyQt5
|
||||
- 支持OpenGL的显卡
|
||||
|
||||
### 依赖安装
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 运行编辑器
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
### 基本操作
|
||||
- **鼠标左键**:选择对象
|
||||
- **鼠标右键**:旋转视角
|
||||
- **鼠标中键**:平移视角
|
||||
- **滚轮**:缩放视角
|
||||
- **WASD**:在场景中移动
|
||||
- **数字键1-0**:切换不同时间点
|
||||
- **F键**:聚焦选中对象
|
||||
- **Delete键**:删除选中对象
|
||||
|
||||
## 编辑器使用指南
|
||||
|
||||
### 导入模型
|
||||
1. 点击"文件" > "导入模型"
|
||||
2. 选择支持的3D模型文件
|
||||
3. 调整模型位置和属性
|
||||
|
||||
### 场景编辑
|
||||
1. 使用选择工具选中对象
|
||||
2. 通过属性面板修改对象属性
|
||||
3. 使用变换工具(移动、旋转、缩放)调整对象
|
||||
|
||||
### GUI创建
|
||||
1. 进入GUI编辑模式
|
||||
2. 选择GUI元素类型
|
||||
3. 点击场景放置GUI元素
|
||||
4. 通过属性面板调整GUI属性
|
||||
|
||||
### VR模式
|
||||
1. 连接VR设备
|
||||
2. 点击"VR" > "启用VR模式"
|
||||
3. 使用VR控制器进行交互
|
||||
|
||||
### 脚本编辑
|
||||
1. 点击"脚本" > "创建新脚本"
|
||||
2. 编写Python脚本代码
|
||||
3. 将脚本附加到场景对象
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 架构概述
|
||||
EG采用模块化设计,核心功能通过`MyWorld`类集成各个子系统。所有子系统都通过代理方法暴露功能,使API保持一致性。
|
||||
|
||||
### 添加新功能
|
||||
1. 在相应目录下创建新模块
|
||||
2. 在`MyWorld`类中添加代理方法
|
||||
3. 通过子系统管理器集成功能
|
||||
|
||||
### 扩展VR功能
|
||||
1. 修改`vr_manager.py`添加新功能
|
||||
2. 更新`vr_input_handler.py`处理新输入
|
||||
3. 在`alvr_streamer.py`中添加串流支持
|
||||
|
||||
### 创建自定义工具
|
||||
1. 在`tool_manager.py`中添加新工具类型
|
||||
2. 实现工具的交互逻辑
|
||||
3. 在UI中添加工具选择按钮
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交Pull Request或Issue报告问题。贡献前请先查看贡献指南。
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
Binary file not shown.
BIN
__pycache__/main.cpython-313.pyc
Normal file
BIN
__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
core/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/openxr_input_handler.cpython-310.pyc
Normal file
BIN
core/__pycache__/openxr_input_handler.cpython-310.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/openxr_manager.cpython-310.pyc
Normal file
BIN
core/__pycache__/openxr_manager.cpython-310.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/openxr_streamer.cpython-310.pyc
Normal file
BIN
core/__pycache__/openxr_streamer.cpython-310.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/world.cpython-313.pyc
Normal file
BIN
core/__pycache__/world.cpython-313.pyc
Normal file
Binary file not shown.
@ -1,683 +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.connection_mode = "auto" # auto, wireless, wired
|
||||
self.wired_port = 9945 # Pico 4有线连接可能使用的端口
|
||||
|
||||
# 连接状态
|
||||
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:
|
||||
# 检查进程
|
||||
alvr_processes = []
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
|
||||
try:
|
||||
# 检查ALVR相关进程(包括Pico 4支持)
|
||||
if 'alvr' in proc.info['name'].lower() or any('alvr' in (arg or '').lower() for arg in (proc.info['cmdline'] or [])):
|
||||
alvr_processes.append(proc)
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, TypeError):
|
||||
# 忽略无法访问的进程
|
||||
continue
|
||||
|
||||
if alvr_processes:
|
||||
print(f"✓ 检测到 {len(alvr_processes)} 个ALVR相关进程")
|
||||
for proc in alvr_processes:
|
||||
try:
|
||||
print(f" - 进程: {proc.info['name']} (PID: {proc.info['pid']})")
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
pass
|
||||
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()
|
||||
|
||||
if result == 0:
|
||||
print("✓ 检测到ALVR服务器端口")
|
||||
return True
|
||||
else:
|
||||
# 尝试其他可能的端口(Pico 4可能使用不同的端口)
|
||||
alternative_ports = [self.wired_port, 8080, 8081, 8082, 9944, 9945]
|
||||
for port in alternative_ports:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(1)
|
||||
result = sock.connect_ex((self.alvr_server_ip, port))
|
||||
sock.close()
|
||||
if result == 0:
|
||||
print(f"✓ 检测到ALVR服务器在端口 {port}")
|
||||
self.alvr_server_port = port
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"检查ALVR服务器错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _start_alvr_server(self):
|
||||
"""启动ALVR服务器"""
|
||||
try:
|
||||
# 获取当前操作系统
|
||||
import platform
|
||||
system = platform.system().lower()
|
||||
|
||||
# 尝试启动ALVR服务器
|
||||
# 这里我们只尝试检测ALVR是否正在运行,而不是尝试启动它
|
||||
print("ℹ 检测到系统环境,跳过自动启动ALVR服务器")
|
||||
print(" 请确保ALVR服务器已在运行")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"启动ALVR服务器错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _connect_to_server(self):
|
||||
"""连接到ALVR服务器"""
|
||||
try:
|
||||
# 首先检查是否为Pico 4有线连接模式
|
||||
# 如果VR系统已经通过SteamVR正常工作,我们可以启用直连模式
|
||||
if self._is_steamvr_active():
|
||||
print("💡 检测到SteamVR正在运行,启用直连模式")
|
||||
self.connected = True
|
||||
return True
|
||||
|
||||
# 尝试多种连接方式以支持Pico 4有线连接
|
||||
connection_attempts = [
|
||||
(self.alvr_server_ip, self.alvr_server_port), # 默认无线端口
|
||||
(self.alvr_server_ip, self.wired_port), # Pico 4有线端口
|
||||
(self.alvr_server_ip, 8081), # 替代端口1
|
||||
(self.alvr_server_ip, 8082) # 替代端口2
|
||||
]
|
||||
|
||||
for ip, port in connection_attempts:
|
||||
try:
|
||||
print(f"尝试连接到ALVR服务器 {ip}:{port}...")
|
||||
# 创建TCP连接
|
||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.server_socket.settimeout(5)
|
||||
self.server_socket.connect((ip, 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
|
||||
self.alvr_server_port = port # 记住成功连接的端口
|
||||
print(f"✓ 已连接到ALVR服务器 {ip}:{port}")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 从 {ip}:{port} 接收到无效响应")
|
||||
|
||||
except Exception as e:
|
||||
print(f"连接到 {ip}:{port} 失败: {str(e)}")
|
||||
if self.server_socket:
|
||||
self.server_socket.close()
|
||||
self.server_socket = None
|
||||
continue
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"连接ALVR服务器错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _is_steamvr_active(self):
|
||||
"""检查SteamVR是否正在运行"""
|
||||
try:
|
||||
# 检查是否有SteamVR相关进程
|
||||
steamvr_processes = ["vrserver", "steamvr", "vrcompositor", "vrmonitor"]
|
||||
for proc in psutil.process_iter(['pid', 'name']):
|
||||
try:
|
||||
proc_name = proc.info['name'].lower()
|
||||
if any(sv_proc in proc_name for sv_proc in steamvr_processes):
|
||||
print(f"✓ 检测到SteamVR进程: {proc_name}")
|
||||
return True
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
continue
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"检查SteamVR状态错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _send_message(self, data):
|
||||
"""发送消息到ALVR服务器"""
|
||||
try:
|
||||
# 在直连模式下,不需要发送消息
|
||||
if not self.server_socket and not self.connected:
|
||||
return
|
||||
|
||||
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:
|
||||
# 在直连模式下,不需要接收消息
|
||||
if not self.server_socket and self.connected:
|
||||
return None
|
||||
|
||||
# 接收消息长度
|
||||
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:
|
||||
# 如果启用了直连模式(通过SteamVR),直接返回None
|
||||
# 表示不需要通过ALVR传输帧,SteamVR会直接处理
|
||||
if self._is_steamvr_active():
|
||||
return None
|
||||
|
||||
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:
|
||||
print("⚠ VR眼部纹理不可用,使用主摄像机视图")
|
||||
# 如果VR纹理不可用,使用主摄像机视图作为备选
|
||||
return self._get_fallback_frame()
|
||||
|
||||
# 检查纹理是否有效
|
||||
if not left_texture.has_ram_image() or not right_texture.has_ram_image():
|
||||
# 强制将纹理数据复制到RAM
|
||||
left_texture.set_auto_texture_scale(Texture.AT_scaleNone)
|
||||
right_texture.set_auto_texture_scale(Texture.AT_scaleNone)
|
||||
|
||||
# 尝试重新获取纹理数据
|
||||
try:
|
||||
self.world.graphicsEngine.extract_texture_data(left_texture, self.world.win.get_gsg())
|
||||
self.world.graphicsEngine.extract_texture_data(right_texture, self.world.win.get_gsg())
|
||||
except Exception as e:
|
||||
print(f"提取纹理数据失败: {str(e)}")
|
||||
|
||||
# 合成立体帧
|
||||
frame_data = self._compose_stereo_frame(left_texture, right_texture)
|
||||
return frame_data
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取VR帧错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def _get_fallback_frame(self):
|
||||
"""获取备用帧(主摄像机视图)"""
|
||||
try:
|
||||
# 使用主窗口的纹理作为备选
|
||||
if hasattr(self.world, 'win') and self.world.win:
|
||||
# 创建临时缓冲区来捕获主视图
|
||||
temp_buffer = self.world.win.makeTextureBuffer(
|
||||
"temp_vr_frame", self.stream_width, self.stream_height
|
||||
)
|
||||
temp_texture = temp_buffer.getTexture()
|
||||
temp_camera = self.world.makeCamera(temp_buffer)
|
||||
|
||||
# 渲染一帧
|
||||
self.world.graphicsEngine.render_frame()
|
||||
|
||||
# 获取图像数据
|
||||
image = PNMImage()
|
||||
if temp_texture.store(image):
|
||||
frame_data = image.makeRamImage()
|
||||
# 清理临时资源
|
||||
temp_buffer.remove()
|
||||
return frame_data
|
||||
|
||||
# 清理临时资源
|
||||
temp_buffer.remove()
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取备用帧错误: {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 self._is_steamvr_active():
|
||||
return
|
||||
|
||||
if not self.server_socket:
|
||||
return
|
||||
|
||||
# 创建帧消息
|
||||
frame_message = {
|
||||
"type": "video_frame",
|
||||
"timestamp": time.time(),
|
||||
"width": self.stream_width,
|
||||
"height": self.stream_height,
|
||||
"format": "rgb"
|
||||
}
|
||||
|
||||
# 对于二进制数据,我们将其作为单独的消息发送
|
||||
if frame_data:
|
||||
# 先发送元数据
|
||||
self._send_message(frame_message)
|
||||
|
||||
# 然后发送二进制数据
|
||||
try:
|
||||
# 发送数据长度
|
||||
data_length = len(frame_data)
|
||||
length_header = struct.pack('<I', data_length)
|
||||
self.server_socket.send(length_header)
|
||||
|
||||
# 发送实际数据
|
||||
self.server_socket.send(frame_data)
|
||||
except Exception as e:
|
||||
print(f"发送帧数据错误: {str(e)}")
|
||||
else:
|
||||
# 如果没有帧数据,发送空帧消息
|
||||
frame_message["empty"] = True
|
||||
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 self._is_steamvr_active():
|
||||
self.streaming = True
|
||||
print("✓ 启用直连模式,SteamVR将直接处理渲染输出")
|
||||
return True
|
||||
|
||||
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):
|
||||
"""获取串流状态"""
|
||||
# 如果是直连模式,返回特殊的直连状态
|
||||
if self._is_steamvr_active():
|
||||
return {
|
||||
"connected": True,
|
||||
"streaming": self.streaming,
|
||||
"fps": self.current_fps,
|
||||
"latency": self.latency,
|
||||
"resolution": f"{self.stream_width}x{self.stream_height}",
|
||||
"bitrate": self.bitrate,
|
||||
"mode": "direct" # 直连模式
|
||||
}
|
||||
|
||||
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,
|
||||
"mode": "alvr" # ALVR模式
|
||||
}
|
||||
|
||||
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
|
||||
261
core/openxr_input_handler.py
Normal file
261
core/openxr_input_handler.py
Normal file
@ -0,0 +1,261 @@
|
||||
"""
|
||||
OpenXR输入处理器
|
||||
|
||||
处理OpenXR控制器输入和手势识别
|
||||
支持多种OpenXR控制器和输入方式
|
||||
"""
|
||||
|
||||
from direct.showbase.DirectObject import DirectObject
|
||||
from panda3d.core import Vec3, Point3, CollisionRay, CollisionNode, CollisionHandlerQueue, BitMask32
|
||||
from direct.task import Task
|
||||
import time
|
||||
|
||||
|
||||
class OpenXRInputHandler(DirectObject):
|
||||
"""OpenXR输入处理器"""
|
||||
|
||||
def __init__(self, world, openxr_manager):
|
||||
super().__init__()
|
||||
self.world = world
|
||||
self.openxr_manager = openxr_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,
|
||||
'thumbstick': self._handle_thumbstick,
|
||||
'touchpad': self._handle_touchpad,
|
||||
'menu': self._handle_menu,
|
||||
'system': self._handle_system
|
||||
}
|
||||
|
||||
print("✓ OpenXR输入处理器初始化完成")
|
||||
|
||||
def start_input_handling(self):
|
||||
"""启动输入处理"""
|
||||
if not self.openxr_manager.is_openxr_enabled():
|
||||
print("OpenXR未启用,无法启动输入处理")
|
||||
return False
|
||||
|
||||
# 启动输入更新任务
|
||||
self.world.taskMgr.add(self._update_input, "openxr_input_update")
|
||||
|
||||
# 设置控制器可视化
|
||||
self._setup_controller_visualization()
|
||||
|
||||
print("✓ OpenXR输入处理已启动")
|
||||
return True
|
||||
|
||||
def stop_input_handling(self):
|
||||
"""停止输入处理"""
|
||||
self.world.taskMgr.remove("openxr_input_update")
|
||||
self._cleanup_controller_visualization()
|
||||
print("✓ OpenXR输入处理已停止")
|
||||
|
||||
def _update_input(self, task):
|
||||
"""更新输入处理"""
|
||||
if not self.openxr_manager.is_openxr_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"OpenXR输入更新错误: {str(e)}")
|
||||
|
||||
return Task.cont
|
||||
|
||||
def _update_controllers(self):
|
||||
"""更新控制器状态"""
|
||||
if self.openxr_manager.simulation_mode:
|
||||
# 在模拟模式下使用模拟数据
|
||||
sim_data = self.openxr_manager.get_simulation_data('controller_poses')
|
||||
for controller_id, pose_data in sim_data.items():
|
||||
if pose_data['connected']:
|
||||
self._update_controller_pose(controller_id, pose_data)
|
||||
else:
|
||||
# TODO: 实际OpenXR控制器更新
|
||||
pass
|
||||
|
||||
def _update_controller_pose(self, controller_id, pose_data):
|
||||
"""更新控制器姿态"""
|
||||
# 更新控制器位置和旋转
|
||||
if controller_id not in self.controller_nodes:
|
||||
self._create_controller_node(controller_id)
|
||||
|
||||
controller_node = self.controller_nodes[controller_id]
|
||||
position = pose_data['position']
|
||||
rotation = pose_data['rotation']
|
||||
|
||||
controller_node.setPos(*position)
|
||||
controller_node.setQuat(Quat(*rotation))
|
||||
|
||||
def _create_controller_node(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, 1, 0, 1) # 绿色
|
||||
|
||||
self.controller_nodes[controller_id] = controller_node
|
||||
|
||||
# 创建控制器射线
|
||||
self._create_controller_ray(controller_id)
|
||||
|
||||
def _create_controller_ray(self, controller_id):
|
||||
"""创建控制器射线"""
|
||||
# 创建碰撞射线用于交互检测
|
||||
ray_node = CollisionNode(f'controller_ray_{controller_id}')
|
||||
ray = CollisionRay()
|
||||
ray.setOrigin(Point3(0, 0, 0))
|
||||
ray.setDirection(Vec3(0, 1, 0)) # 默认向前
|
||||
ray_node.addSolid(ray)
|
||||
ray_node.setFromCollideMask(BitMask32.bit(0))
|
||||
|
||||
ray_np = self.controller_nodes[controller_id].attachNewNode(ray_node)
|
||||
queue = CollisionHandlerQueue()
|
||||
|
||||
self.controller_rays[controller_id] = {
|
||||
'node': ray_np,
|
||||
'queue': queue
|
||||
}
|
||||
|
||||
def _setup_controller_visualization(self):
|
||||
"""设置控制器可视化"""
|
||||
if self.openxr_manager.simulation_mode:
|
||||
# 在模拟模式下创建控制器可视化
|
||||
sim_data = self.openxr_manager.get_simulation_data('controller_poses')
|
||||
for controller_id in sim_data.keys():
|
||||
self._create_controller_node(controller_id)
|
||||
|
||||
def _cleanup_controller_visualization(self):
|
||||
"""清理控制器可视化"""
|
||||
# 移除所有控制器节点
|
||||
for controller_node in self.controller_nodes.values():
|
||||
controller_node.removeNode()
|
||||
|
||||
self.controller_nodes.clear()
|
||||
self.controller_rays.clear()
|
||||
|
||||
def _process_gestures(self):
|
||||
"""处理手势识别"""
|
||||
# TODO: 实现手势识别逻辑
|
||||
pass
|
||||
|
||||
def _process_interactions(self):
|
||||
"""处理交互"""
|
||||
# TODO: 实现交互逻辑
|
||||
pass
|
||||
|
||||
def _handle_trigger(self, controller_id, value):
|
||||
"""处理触发器输入"""
|
||||
# TODO: 实现触发器处理逻辑
|
||||
pass
|
||||
|
||||
def _handle_grip(self, controller_id, value):
|
||||
"""处理抓握输入"""
|
||||
# TODO: 实现抓握处理逻辑
|
||||
pass
|
||||
|
||||
def _handle_thumbstick(self, controller_id, x, y):
|
||||
"""处理摇杆输入"""
|
||||
# TODO: 实现摇杆处理逻辑
|
||||
pass
|
||||
|
||||
def _handle_touchpad(self, controller_id, x, y, pressed):
|
||||
"""处理触摸板输入"""
|
||||
# TODO: 实现触摸板处理逻辑
|
||||
pass
|
||||
|
||||
def _handle_menu(self, controller_id, pressed):
|
||||
"""处理菜单按钮"""
|
||||
# TODO: 实现菜单按钮处理逻辑
|
||||
pass
|
||||
|
||||
def _handle_system(self, controller_id, pressed):
|
||||
"""处理系统按钮"""
|
||||
# TODO: 实现系统按钮处理逻辑
|
||||
pass
|
||||
|
||||
def get_all_controllers(self):
|
||||
"""获取所有控制器"""
|
||||
if self.openxr_manager.simulation_mode:
|
||||
sim_data = self.openxr_manager.get_simulation_data('controller_poses')
|
||||
return list(sim_data.keys())
|
||||
else:
|
||||
# TODO: 返回实际连接的控制器
|
||||
return []
|
||||
|
||||
def get_controller_state(self, controller_id):
|
||||
"""获取控制器状态"""
|
||||
if self.openxr_manager.simulation_mode:
|
||||
sim_data = self.openxr_manager.get_simulation_data('controller_poses')
|
||||
if controller_id in sim_data:
|
||||
# 返回模拟控制器状态
|
||||
return {
|
||||
'trigger': 0.0,
|
||||
'grip': 0.0,
|
||||
'thumbstick': {'x': 0.0, 'y': 0.0},
|
||||
'touchpad': {'x': 0.0, 'y': 0.0, 'pressed': False},
|
||||
'menu': False,
|
||||
'system': False,
|
||||
'connected': sim_data[controller_id]['connected']
|
||||
}
|
||||
|
||||
# 默认返回空状态
|
||||
return {
|
||||
'trigger': 0.0,
|
||||
'grip': 0.0,
|
||||
'thumbstick': {'x': 0.0, 'y': 0.0},
|
||||
'touchpad': {'x': 0.0, 'y': 0.0, 'pressed': False},
|
||||
'menu': False,
|
||||
'system': False,
|
||||
'connected': False
|
||||
}
|
||||
|
||||
def set_gesture_enabled(self, enabled):
|
||||
"""设置手势识别启用状态"""
|
||||
self.gesture_enabled = enabled
|
||||
print(f"{'✓' if enabled else '✗'} OpenXR手势识别已{'启用' if enabled else '禁用'}")
|
||||
|
||||
def set_interaction_enabled(self, enabled):
|
||||
"""设置交互启用状态"""
|
||||
self.interaction_enabled = enabled
|
||||
print(f"{'✓' if enabled else '✗'} OpenXR交互已{'启用' if enabled else '禁用'}")
|
||||
|
||||
def show_controller_rays(self, show=True):
|
||||
"""显示/隐藏控制器射线"""
|
||||
for ray_data in self.controller_rays.values():
|
||||
ray_node = ray_data['node']
|
||||
if show:
|
||||
ray_node.show()
|
||||
else:
|
||||
ray_node.hide()
|
||||
|
||||
print(f"{'✓' if show else '✗'} 控制器射线已{'显示' if show else '隐藏'}")
|
||||
342
core/openxr_manager.py
Normal file
342
core/openxr_manager.py
Normal file
@ -0,0 +1,342 @@
|
||||
"""
|
||||
OpenXR管理器
|
||||
|
||||
负责OpenXR系统初始化、追踪和渲染
|
||||
支持有线VR串流功能
|
||||
"""
|
||||
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
|
||||
from panda3d.core import *
|
||||
from panda3d.core import Mat4, PerspectiveLens, CardMaker
|
||||
# 修复taskMgr导入错误,应该从TaskManagerGlobal导入而不是ShowBaseGlobal
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
from direct.showbase.DirectObject import DirectObject
|
||||
from direct.task import Task
|
||||
import sys
|
||||
|
||||
|
||||
class OpenXRManager(DirectObject):
|
||||
"""OpenXR管理器 - 处理OpenXR系统初始化、追踪和渲染"""
|
||||
|
||||
def __init__(self, world):
|
||||
super().__init__()
|
||||
self.world = world
|
||||
self.openxr_enabled = False
|
||||
self.xr_instance = None
|
||||
self.xr_system = None
|
||||
self.xr_session = None
|
||||
|
||||
# 渲染配置
|
||||
self.render_width = 1920
|
||||
self.render_height = 1080
|
||||
|
||||
# 有线连接设置
|
||||
self.wired_connection = True
|
||||
self.connection_ip = "127.0.0.1"
|
||||
self.connection_port = 9943
|
||||
|
||||
# 模拟模式设置(用于开发测试)
|
||||
self.simulation_mode = False
|
||||
self.simulation_data = {
|
||||
'head_pose': {'position': [0, 0, 1.6], 'rotation': [0, 0, 0, 1]},
|
||||
'controller_poses': {
|
||||
0: {'position': [-0.3, 0, 1.2], 'rotation': [0, 0, 0, 1], 'connected': True},
|
||||
1: {'position': [0.3, 0, 1.2], 'rotation': [0, 0, 0, 1], 'connected': True}
|
||||
},
|
||||
'render_size': (1920, 1080)
|
||||
}
|
||||
|
||||
# 立体渲染缓冲区
|
||||
self.left_eye_buffer = None
|
||||
self.right_eye_buffer = None
|
||||
self.left_eye_camera = None
|
||||
self.right_eye_camera = None
|
||||
|
||||
# 眼部纹理(用于串流)
|
||||
self.left_eye_texture = None
|
||||
self.right_eye_texture = None
|
||||
self.eye_textures = {}
|
||||
|
||||
# 控制器相关
|
||||
self.controller_nodes = {}
|
||||
self.controller_poses = {}
|
||||
|
||||
print("✓ OpenXR管理器初始化完成")
|
||||
|
||||
def initialize_openxr(self, force_simulation=False):
|
||||
"""初始化OpenXR系统"""
|
||||
try:
|
||||
# 检查是否强制使用模拟模式
|
||||
if force_simulation:
|
||||
print("🔧 强制启用OpenXR模拟模式")
|
||||
return self._init_simulation_mode()
|
||||
|
||||
# 检查OpenXR支持
|
||||
if not self._check_openxr_support():
|
||||
print("⚠ OpenXR支持不可用,切换到模拟模式")
|
||||
return self._init_simulation_mode()
|
||||
|
||||
# 尝试初始化OpenXR
|
||||
if not self._init_openxr_native():
|
||||
print("⚠ OpenXR初始化失败,切换到模拟模式")
|
||||
print("提示: 请确保OpenXR运行时已安装且VR头盔已连接")
|
||||
return self._init_simulation_mode()
|
||||
|
||||
# 真实OpenXR模式初始化成功
|
||||
print("✓ 真实OpenXR模式初始化成功")
|
||||
return self._init_real_openxr_mode()
|
||||
|
||||
except Exception as e:
|
||||
print(f"OpenXR初始化错误: {str(e)}")
|
||||
print("⚠ 切换到模拟模式")
|
||||
return self._init_simulation_mode()
|
||||
|
||||
def _check_openxr_support(self):
|
||||
"""检查OpenXR支持"""
|
||||
try:
|
||||
# TODO: 实际检查OpenXR运行时支持
|
||||
# 这里暂时返回False以使用模拟模式进行开发
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
def _init_openxr_native(self):
|
||||
"""初始化原生OpenXR"""
|
||||
# TODO: 实现OpenXR初始化逻辑
|
||||
print("ℹ OpenXR原生初始化占位符")
|
||||
return False
|
||||
|
||||
def _init_real_openxr_mode(self):
|
||||
"""初始化真实OpenXR模式"""
|
||||
self.openxr_enabled = True
|
||||
self.simulation_mode = False
|
||||
|
||||
# TODO: 实现实际的OpenXR设置
|
||||
# 1. 创建OpenXR实例
|
||||
# 2. 获取系统信息
|
||||
# 3. 创建会话
|
||||
# 4. 设置渲染目标
|
||||
# 5. 初始化控制器
|
||||
|
||||
return True
|
||||
|
||||
def _init_simulation_mode(self):
|
||||
"""初始化模拟模式"""
|
||||
self.openxr_enabled = True
|
||||
self.simulation_mode = True
|
||||
|
||||
# 设置模拟渲染尺寸
|
||||
self.render_width = self.simulation_data['render_size'][0]
|
||||
self.render_height = self.simulation_data['render_size'][1]
|
||||
|
||||
# 初始化模拟立体渲染
|
||||
self._setup_simulation_rendering()
|
||||
|
||||
print("ℹ OpenXR模拟模式已启用")
|
||||
print(f" 渲染尺寸: {self.render_width}x{self.render_height}")
|
||||
print(" 头部追踪: 模拟摆动")
|
||||
print(" 控制器: 模拟位置")
|
||||
|
||||
return True
|
||||
|
||||
def _setup_simulation_rendering(self):
|
||||
"""设置模拟渲染"""
|
||||
print("🔧 OpenXR模拟模式: 开始设置渲染")
|
||||
|
||||
# 创建左右眼渲染缓冲区
|
||||
self.left_eye_buffer = self._create_eye_buffer("left_eye", self.render_width//2, self.render_height)
|
||||
self.right_eye_buffer = self._create_eye_buffer("right_eye", self.render_width//2, self.render_height)
|
||||
|
||||
print(f" 创建眼部缓冲区: 左眼={self.left_eye_buffer is not None}, 右眼={self.right_eye_buffer is not None}")
|
||||
|
||||
# 创建左右眼相机
|
||||
self.left_eye_camera = self._create_eye_camera("left_eye_camera", (-0.03, 0, 0)) # 瞳距的一半
|
||||
self.right_eye_camera = self._create_eye_camera("right_eye_camera", (0.03, 0, 0)) # 瞳距的一半)
|
||||
|
||||
print(f" 创建眼部相机: 左眼={self.left_eye_camera is not None}, 右眼={self.right_eye_camera is not None}")
|
||||
|
||||
# 创建用于显示的左右眼纹理,格式与主程序保持一致
|
||||
self.left_eye_texture = Texture("left_eye_texture")
|
||||
self.right_eye_texture = Texture("right_eye_texture")
|
||||
self.left_eye_texture.setFormat(Texture.F_rgba8)
|
||||
self.right_eye_texture.setFormat(Texture.F_rgba8)
|
||||
self.left_eye_texture.setComponentType(Texture.T_unsigned_byte)
|
||||
self.right_eye_texture.setComponentType(Texture.T_unsigned_byte)
|
||||
|
||||
print(f" 创建眼部纹理: 左眼={self.left_eye_texture is not None}, 右眼={self.right_eye_texture is not None}")
|
||||
print(" 设置纹理格式为F_rgba8和T_unsigned_byte")
|
||||
|
||||
# 设置自动纹理缩放为None
|
||||
self.left_eye_texture.setAutoTextureScale(ATSNone)
|
||||
self.right_eye_texture.setAutoTextureScale(ATSNone)
|
||||
|
||||
print(" 设置自动纹理缩放为ATSNone")
|
||||
|
||||
# 将纹理附加到渲染缓冲区,使用RTMCopyRam模式确保数据能被复制到RAM
|
||||
left_result = self.left_eye_buffer.addRenderTexture(self.left_eye_texture, GraphicsOutput.RTMCopyRam)
|
||||
right_result = self.right_eye_buffer.addRenderTexture(self.right_eye_texture, GraphicsOutput.RTMCopyRam)
|
||||
|
||||
print(f" 添加渲染纹理结果: 左眼={left_result}, 右眼={right_result}")
|
||||
|
||||
# 设置相机渲染目标
|
||||
if self.left_eye_buffer and self.left_eye_camera:
|
||||
self.left_eye_camera.node().setScene(self.world.render)
|
||||
print(" 设置左眼相机渲染场景")
|
||||
|
||||
if self.right_eye_buffer and self.right_eye_camera:
|
||||
self.right_eye_camera.node().setScene(self.world.render)
|
||||
print(" 设置右眼相机渲染场景")
|
||||
|
||||
self.eye_textures['left'] = self.left_eye_texture
|
||||
self.eye_textures['right'] = self.right_eye_texture
|
||||
|
||||
print(" 初始化完成,开始渲染帧")
|
||||
|
||||
# 强制渲染多帧以便初始化纹理数据
|
||||
# 参考主程序的实现方式,渲染足够多的帧以确保数据填充
|
||||
for i in range(5): # 渲染5帧以确保纹理数据被正确填充
|
||||
self.world.graphicsEngine.renderFrame()
|
||||
print(f" 渲染第{i+1}帧")
|
||||
|
||||
# 提取纹理数据到RAM
|
||||
gsg = self.world.win.getGsg()
|
||||
print(f" 获取GSG: {gsg is not None}")
|
||||
|
||||
if gsg:
|
||||
self.world.graphicsEngine.extractTextureData(self.left_eye_texture, gsg)
|
||||
self.world.graphicsEngine.extractTextureData(self.right_eye_texture, gsg)
|
||||
print(" 提取纹理数据到RAM")
|
||||
|
||||
# 检查纹理数据是否成功提取
|
||||
left_has_ram = self.left_eye_texture.hasRamImage()
|
||||
right_has_ram = self.right_eye_texture.hasRamImage()
|
||||
print(f" 纹理RAM图像检查: 左眼={left_has_ram}, 右眼={right_has_ram}")
|
||||
|
||||
if left_has_ram:
|
||||
left_data_size = len(self.left_eye_texture.getRamImage().getData())
|
||||
print(f" 左眼纹理数据大小: {left_data_size} 字节")
|
||||
|
||||
if right_has_ram:
|
||||
right_data_size = len(self.right_eye_texture.getRamImage().getData())
|
||||
print(f" 右眼纹理数据大小: {right_data_size} 字节")
|
||||
|
||||
def get_left_eye_texture(self):
|
||||
"""获取左眼纹理用于显示"""
|
||||
return self.left_eye_texture
|
||||
|
||||
def get_right_eye_texture(self):
|
||||
"""获取右眼纹理用于显示"""
|
||||
return self.right_eye_texture
|
||||
|
||||
def _create_eye_buffer(self, name, width, height):
|
||||
"""创建眼部渲染缓冲区"""
|
||||
# 创建帧缓冲区属性
|
||||
fb_prop = FrameBufferProperties()
|
||||
fb_prop.setRgbColor(True)
|
||||
fb_prop.setRgbaBits(8, 8, 8, 8)
|
||||
fb_prop.setDepthBits(24)
|
||||
|
||||
# 创建窗口属性
|
||||
win_prop = WindowProperties()
|
||||
win_prop.setSize(width, height)
|
||||
|
||||
# 创建缓冲区
|
||||
buffer = self.world.graphicsEngine.makeOutput(
|
||||
self.world.pipe,
|
||||
name,
|
||||
-1000, # 低优先级
|
||||
fb_prop,
|
||||
win_prop,
|
||||
GraphicsPipe.BFRefuseWindow
|
||||
)
|
||||
|
||||
return buffer
|
||||
|
||||
def _create_eye_camera(self, name, position_offset):
|
||||
"""创建眼部相机"""
|
||||
# 创建相机节点
|
||||
camera_node = Camera(name)
|
||||
lens = PerspectiveLens()
|
||||
lens.setFov(100) # VR镜头视野
|
||||
lens.setNear(0.1)
|
||||
lens.setFar(1000.0)
|
||||
camera_node.setLens(lens)
|
||||
|
||||
# 创建相机节点路径
|
||||
camera_np = self.world.render.attachNewNode(camera_node)
|
||||
camera_np.setPos(*position_offset)
|
||||
|
||||
return camera_np
|
||||
|
||||
def shutdown_openxr(self):
|
||||
"""关闭OpenXR系统"""
|
||||
if not self.openxr_enabled:
|
||||
return True
|
||||
|
||||
try:
|
||||
# 清理渲染缓冲区
|
||||
if self.left_eye_buffer:
|
||||
self.world.graphicsEngine.removeWindow(self.left_eye_buffer)
|
||||
self.left_eye_buffer = None
|
||||
|
||||
if self.right_eye_buffer:
|
||||
self.world.graphicsEngine.removeWindow(self.right_eye_buffer)
|
||||
self.right_eye_buffer = None
|
||||
|
||||
# 清理相机
|
||||
if self.left_eye_camera:
|
||||
self.left_eye_camera.removeNode()
|
||||
self.left_eye_camera = None
|
||||
|
||||
if self.right_eye_camera:
|
||||
self.right_eye_camera.removeNode()
|
||||
self.right_eye_camera = None
|
||||
|
||||
# 重置状态
|
||||
self.openxr_enabled = False
|
||||
self.xr_instance = None
|
||||
self.xr_system = None
|
||||
self.xr_session = None
|
||||
|
||||
print("✓ OpenXR系统已关闭")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"OpenXR关闭错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def is_openxr_enabled(self):
|
||||
"""检查OpenXR是否启用"""
|
||||
return self.openxr_enabled
|
||||
|
||||
def get_openxr_info(self):
|
||||
"""获取OpenXR系统信息"""
|
||||
return {
|
||||
"enabled": self.openxr_enabled,
|
||||
"mode": "simulation" if self.simulation_mode else "real",
|
||||
"render_size": (self.render_width, self.render_height),
|
||||
"simulation_mode": self.simulation_mode,
|
||||
"wired_connection": self.wired_connection
|
||||
}
|
||||
|
||||
def update_simulation_data(self, data_type, data):
|
||||
"""更新模拟数据"""
|
||||
if data_type in self.simulation_data:
|
||||
self.simulation_data[data_type] = data
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_simulation_data(self, data_type=None):
|
||||
"""获取模拟数据"""
|
||||
if data_type:
|
||||
return self.simulation_data.get(data_type, None)
|
||||
return self.simulation_data
|
||||
|
||||
def enable_simulation_mode(self):
|
||||
"""启用模拟模式"""
|
||||
if not self.simulation_mode:
|
||||
self.shutdown_openxr()
|
||||
self.simulation_mode = True
|
||||
self.initialize_openxr(force_simulation=True)
|
||||
257
core/openxr_streamer.py
Normal file
257
core/openxr_streamer.py
Normal file
@ -0,0 +1,257 @@
|
||||
"""
|
||||
OpenXR串流处理器
|
||||
|
||||
负责与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 OpenXRStreamer(DirectObject):
|
||||
"""OpenXR串流处理器"""
|
||||
|
||||
def __init__(self, world, openxr_manager):
|
||||
super().__init__()
|
||||
self.world = world
|
||||
self.openxr_manager = openxr_manager
|
||||
|
||||
# 串流服务器配置
|
||||
self.server_ip = "127.0.0.1"
|
||||
self.server_port = 9943
|
||||
self.streaming_port = 9944
|
||||
|
||||
# 有线连接设置
|
||||
self.connection_mode = "wired" # wired, wireless
|
||||
self.wired_port = 9945 # 有线连接端口
|
||||
|
||||
# 连接状态
|
||||
self.connected = False
|
||||
self.streaming = False
|
||||
self.server_socket = None
|
||||
self.streaming_socket = None
|
||||
|
||||
# 流媒体配置
|
||||
self.stream_width = 2880 # 推荐分辨率
|
||||
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("✓ OpenXR串流处理器初始化完成")
|
||||
|
||||
def initialize(self):
|
||||
"""初始化OpenXR串流"""
|
||||
try:
|
||||
# 检查串流服务器是否运行
|
||||
if not self._check_streaming_server():
|
||||
print("串流服务器未运行,尝试启动...")
|
||||
if not self._start_streaming_server():
|
||||
print("无法启动串流服务器")
|
||||
return False
|
||||
|
||||
# 连接到串流服务器
|
||||
if not self._connect_to_server():
|
||||
print("无法连接到串流服务器")
|
||||
return False
|
||||
|
||||
# 配置流媒体设置
|
||||
self._configure_streaming()
|
||||
|
||||
# 启动串流线程
|
||||
self._start_streaming_threads()
|
||||
|
||||
print("✓ OpenXR串流初始化成功")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"OpenXR串流初始化错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _check_streaming_server(self):
|
||||
"""检查串流服务器是否运行"""
|
||||
try:
|
||||
# TODO: 实际检查串流服务器状态
|
||||
# 这里暂时返回False以便在开发阶段使用模拟模式
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
def _start_streaming_server(self):
|
||||
"""启动串流服务器"""
|
||||
try:
|
||||
# TODO: 实际启动串流服务器
|
||||
print("ℹ 串流服务器启动占位符")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"串流服务器启动失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def _connect_to_server(self):
|
||||
"""连接到串流服务器"""
|
||||
try:
|
||||
# TODO: 实际连接到串流服务器
|
||||
print("ℹ 串流服务器连接占位符")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"串流服务器连接失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def _configure_streaming(self):
|
||||
"""配置流媒体设置"""
|
||||
# 根据OpenXR管理器设置配置串流参数
|
||||
if self.openxr_manager:
|
||||
render_size = self.openxr_manager.get_openxr_info()['render_size']
|
||||
self.stream_width = render_size[0]
|
||||
self.stream_height = render_size[1]
|
||||
|
||||
print(f"ℹ 串流配置: {self.stream_width}x{self.stream_height}@{self.stream_fps}fps")
|
||||
|
||||
def _start_streaming_threads(self):
|
||||
"""启动串流线程"""
|
||||
self.running = True
|
||||
|
||||
# 启动连接线程
|
||||
self.connection_thread = threading.Thread(target=self._connection_handler, daemon=True)
|
||||
self.connection_thread.start()
|
||||
|
||||
# 启动串流线程
|
||||
self.streaming_thread = threading.Thread(target=self._streaming_handler, daemon=True)
|
||||
self.streaming_thread.start()
|
||||
|
||||
def _connection_handler(self):
|
||||
"""连接处理线程"""
|
||||
while self.running:
|
||||
try:
|
||||
# TODO: 实现连接处理逻辑
|
||||
time.sleep(1)
|
||||
except Exception as e:
|
||||
print(f"连接处理错误: {str(e)}")
|
||||
|
||||
def _streaming_handler(self):
|
||||
"""串流处理线程"""
|
||||
while self.running:
|
||||
try:
|
||||
if self.streaming:
|
||||
self._send_video_frames()
|
||||
time.sleep(1.0 / self.stream_fps)
|
||||
except Exception as e:
|
||||
print(f"串流处理错误: {str(e)}")
|
||||
|
||||
def _send_video_frames(self):
|
||||
"""发送视频帧"""
|
||||
try:
|
||||
# 获取左右眼纹理
|
||||
if self.openxr_manager and hasattr(self.openxr_manager, 'eye_textures'):
|
||||
left_texture = self.openxr_manager.eye_textures.get('left')
|
||||
right_texture = self.openxr_manager.eye_textures.get('right')
|
||||
|
||||
if left_texture and right_texture:
|
||||
# TODO: 实现视频编码和发送逻辑
|
||||
self.frame_count += 1
|
||||
|
||||
# 更新FPS统计
|
||||
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
|
||||
# print(f"串流FPS: {self.current_fps}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"视频帧发送错误: {str(e)}")
|
||||
|
||||
def start_streaming(self):
|
||||
"""开始OpenXR串流"""
|
||||
if not self.connected:
|
||||
print("串流服务器未连接")
|
||||
return False
|
||||
|
||||
self.streaming = True
|
||||
print("✓ OpenXR串流已开始")
|
||||
return True
|
||||
|
||||
def stop_streaming(self):
|
||||
"""停止OpenXR串流"""
|
||||
self.streaming = False
|
||||
print("✓ OpenXR串流已停止")
|
||||
|
||||
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,
|
||||
"codec": self.codec
|
||||
}
|
||||
|
||||
def set_stream_quality(self, width, height, fps, bitrate):
|
||||
"""设置串流质量"""
|
||||
self.stream_width = width
|
||||
self.stream_height = height
|
||||
self.stream_fps = fps
|
||||
self.bitrate = bitrate
|
||||
|
||||
print(f"✓ 串流质量已设置: {width}x{height}@{fps}fps, {bitrate}Mbps")
|
||||
return True
|
||||
|
||||
def send_haptic_feedback(self, controller_id, duration, intensity):
|
||||
"""发送触觉反馈"""
|
||||
try:
|
||||
# TODO: 实现触觉反馈发送逻辑
|
||||
print(f"ℹ 触觉反馈发送占位符: 控制器{controller_id}, 持续时间{duration}s, 强度{intensity}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"触觉反馈发送失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def shutdown(self):
|
||||
"""关闭串流"""
|
||||
self.running = False
|
||||
self.stop_streaming()
|
||||
|
||||
# 等待线程结束
|
||||
if self.connection_thread and self.connection_thread.is_alive():
|
||||
self.connection_thread.join(timeout=2)
|
||||
|
||||
if self.streaming_thread and self.streaming_thread.is_alive():
|
||||
self.streaming_thread.join(timeout=2)
|
||||
|
||||
# 关闭套接字
|
||||
if self.server_socket:
|
||||
self.server_socket.close()
|
||||
|
||||
if self.streaming_socket:
|
||||
self.streaming_socket.close()
|
||||
|
||||
print("✓ OpenXR串流已关闭")
|
||||
|
||||
def is_connected(self):
|
||||
"""检查是否连接"""
|
||||
return self.connected
|
||||
|
||||
def is_streaming(self):
|
||||
"""检查是否正在串流"""
|
||||
return self.streaming
|
||||
@ -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 '禁用'}")
|
||||
@ -1,666 +0,0 @@
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
|
||||
from panda3d.core import *
|
||||
from panda3d.core import Mat4, PerspectiveLens, CardMaker
|
||||
# 修复taskMgr导入错误,应该从TaskManagerGlobal导入而不是ShowBaseGlobal
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
from direct.showbase.DirectObject import DirectObject
|
||||
from direct.task import Task
|
||||
import sys
|
||||
|
||||
class VRManager(DirectObject):
|
||||
"""VR管理器 - 处理VR系统初始化、追踪和渲染"""
|
||||
|
||||
def __init__(self, world):
|
||||
super().__init__()
|
||||
self.world = world
|
||||
self.vr_enabled = False
|
||||
self.vr_system = None
|
||||
self.vr_compositor = None
|
||||
self.render_width = 1920
|
||||
self.render_height = 1080
|
||||
self.alvr_enabled = False
|
||||
|
||||
# 模拟模式设置
|
||||
self.simulation_mode = False
|
||||
self.simulation_data = {
|
||||
'head_pose': {'position': [0, 0, 1.6], 'rotation': [0, 0, 0, 1]},
|
||||
'controller_poses': {
|
||||
0: {'position': [-0.3, 0, 1.2], 'rotation': [0, 0, 0, 1], 'connected': True},
|
||||
1: {'position': [0.3, 0, 1.2], 'rotation': [0, 0, 0, 1], 'connected': True}
|
||||
},
|
||||
'render_size': (1920, 1080)
|
||||
}
|
||||
|
||||
# 立体渲染缓冲区
|
||||
self.left_eye_buffer = None
|
||||
self.right_eye_buffer = None
|
||||
self.left_eye_camera = None
|
||||
self.right_eye_camera = None
|
||||
|
||||
# 眼部纹理(用于ALVR串流)
|
||||
self.left_eye_texture = None
|
||||
self.right_eye_texture = None
|
||||
self.eye_textures = {}
|
||||
|
||||
# 控制器相关
|
||||
self.controller_nodes = {}
|
||||
self.controller_poses = {}
|
||||
|
||||
print("✓ VR管理器初始化完成")
|
||||
|
||||
def initialize_vr(self, force_simulation=False):
|
||||
"""初始化VR系统"""
|
||||
try:
|
||||
# 检查是否强制使用模拟模式
|
||||
if force_simulation:
|
||||
print("🔧 强制启用VR模拟模式")
|
||||
return self._init_simulation_mode()
|
||||
|
||||
# 检查OpenVR支持
|
||||
if not self._check_openvr_support():
|
||||
print("⚠ OpenVR支持不可用,切换到模拟模式")
|
||||
return self._init_simulation_mode()
|
||||
|
||||
# 尝试初始化OpenVR
|
||||
if not self._init_openvr():
|
||||
print("⚠ OpenVR初始化失败,切换到模拟模式")
|
||||
print("提示: 请确保SteamVR正在运行且VR头盔已连接")
|
||||
return self._init_simulation_mode()
|
||||
|
||||
# 真实VR模式初始化成功
|
||||
print("✓ 真实VR模式初始化成功")
|
||||
return self._init_real_vr_mode()
|
||||
|
||||
except Exception as e:
|
||||
print(f"VR初始化错误: {str(e)}")
|
||||
print("⚠ 切换到模拟模式")
|
||||
return self._init_simulation_mode()
|
||||
|
||||
def _init_simulation_mode(self):
|
||||
"""初始化模拟模式"""
|
||||
try:
|
||||
self.simulation_mode = True
|
||||
|
||||
# 使用模拟数据设置渲染尺寸
|
||||
self.render_width, self.render_height = self.simulation_data['render_size']
|
||||
print(f"🎮 模拟VR渲染尺寸: {self.render_width}x{self.render_height}")
|
||||
|
||||
# 设置模拟立体渲染
|
||||
self._setup_stereo_rendering()
|
||||
|
||||
# 启动模拟VR任务
|
||||
self._start_simulation_tasks()
|
||||
|
||||
self.vr_enabled = True
|
||||
print("✓ VR模拟模式初始化完成")
|
||||
print("ℹ 模拟模式说明:")
|
||||
print(" - 头盔追踪: 模拟数据")
|
||||
print(" - 控制器: 模拟两个控制器")
|
||||
print(" - 渲染: 立体渲染到窗口")
|
||||
print(" - 交互: 键盘鼠标模拟")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"模拟模式初始化错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _init_real_vr_mode(self):
|
||||
"""初始化真实VR模式"""
|
||||
try:
|
||||
self.simulation_mode = False
|
||||
|
||||
# 设置立体渲染
|
||||
self._setup_stereo_rendering()
|
||||
|
||||
# 初始化ALVR
|
||||
if self._init_alvr():
|
||||
print("✓ ALVR串流已启用")
|
||||
self.alvr_enabled = True
|
||||
|
||||
# 启动VR更新任务
|
||||
self._start_vr_tasks()
|
||||
|
||||
self.vr_enabled = True
|
||||
print("✓ 真实VR系统初始化完成")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"真实VR模式初始化错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _check_openvr_support(self):
|
||||
"""检查OpenVR支持"""
|
||||
try:
|
||||
import openvr
|
||||
return True
|
||||
except ImportError:
|
||||
print("OpenVR库未安装")
|
||||
return False
|
||||
|
||||
def _init_openvr(self):
|
||||
"""初始化OpenVR"""
|
||||
try:
|
||||
import openvr
|
||||
|
||||
# 初始化OpenVR
|
||||
self.vr_system = openvr.init(openvr.VRApplication_Scene)
|
||||
if not self.vr_system:
|
||||
return False
|
||||
|
||||
# 获取合成器
|
||||
self.vr_compositor = openvr.VRCompositor()
|
||||
if not self.vr_compositor:
|
||||
return False
|
||||
|
||||
# 获取推荐的渲染尺寸
|
||||
self.render_width, self.render_height = self.vr_system.getRecommendedRenderTargetSize()
|
||||
print(f"VR推荐渲染尺寸: {self.render_width}x{self.render_height}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"OpenVR初始化错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _setup_stereo_rendering(self):
|
||||
"""设置立体渲染"""
|
||||
try:
|
||||
# 创建眼部缓冲区
|
||||
self._create_eye_buffers()
|
||||
|
||||
# 创建眼部摄像机
|
||||
self._create_eye_cameras()
|
||||
|
||||
# 设置渲染目标
|
||||
self._setup_render_targets()
|
||||
|
||||
print("✓ 立体渲染设置完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"立体渲染设置错误: {str(e)}")
|
||||
|
||||
def _create_eye_buffers(self):
|
||||
"""创建眼部渲染缓冲区"""
|
||||
try:
|
||||
# 创建左眼缓冲区
|
||||
self.left_eye_buffer = self.world.win.makeTextureBuffer(
|
||||
"left_eye", self.render_width, self.render_height
|
||||
)
|
||||
self.left_eye_buffer.setSort(-100)
|
||||
|
||||
# 创建右眼缓冲区
|
||||
self.right_eye_buffer = self.world.win.makeTextureBuffer(
|
||||
"right_eye", self.render_width, self.render_height
|
||||
)
|
||||
self.right_eye_buffer.setSort(-99)
|
||||
|
||||
# 获取纹理并确保它们可以被访问
|
||||
self.left_eye_texture = self.left_eye_buffer.getTexture()
|
||||
self.right_eye_texture = self.right_eye_buffer.getTexture()
|
||||
|
||||
# 设置纹理参数以确保它们可以被ALVR访问
|
||||
if self.left_eye_texture:
|
||||
self.left_eye_texture.set_format(Texture.F_rgba8)
|
||||
self.left_eye_texture.set_component_type(Texture.T_unsigned_byte)
|
||||
self.left_eye_texture.set_wrap_u(Texture.WM_clamp)
|
||||
self.left_eye_texture.set_wrap_v(Texture.WM_clamp)
|
||||
# 确保纹理数据可以被读取到RAM(使用正确的方法)
|
||||
try:
|
||||
self.left_eye_texture.set_ram_image_compression(Texture.CM_off)
|
||||
except AttributeError:
|
||||
# 如果方法不存在,使用替代方法
|
||||
pass
|
||||
|
||||
if self.right_eye_texture:
|
||||
self.right_eye_texture.set_format(Texture.F_rgba8)
|
||||
self.right_eye_texture.set_component_type(Texture.T_unsigned_byte)
|
||||
self.right_eye_texture.set_wrap_u(Texture.WM_clamp)
|
||||
self.right_eye_texture.set_wrap_v(Texture.WM_clamp)
|
||||
# 确保纹理数据可以被读取到RAM(使用正确的方法)
|
||||
try:
|
||||
self.right_eye_texture.set_ram_image_compression(Texture.CM_off)
|
||||
except AttributeError:
|
||||
# 如果方法不存在,使用替代方法
|
||||
pass
|
||||
|
||||
# 存储到eye_textures字典中供ALVR使用
|
||||
self.eye_textures['left'] = self.left_eye_texture
|
||||
self.eye_textures['right'] = self.right_eye_texture
|
||||
|
||||
print("✓ 眼部渲染缓冲区创建完成")
|
||||
print(f" 左眼纹理: {self.left_eye_texture.getXSize()}x{self.left_eye_texture.getYSize()}")
|
||||
print(f" 右眼纹理: {self.right_eye_texture.getXSize()}x{self.right_eye_texture.getYSize()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"眼部缓冲区创建错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _create_eye_cameras(self):
|
||||
"""创建眼部摄像机"""
|
||||
try:
|
||||
# 创建左眼摄像机
|
||||
self.left_eye_camera = self.world.makeCamera(self.left_eye_buffer)
|
||||
self.left_eye_camera.setPos(-0.032, 0, 0) # 瞳距的一半
|
||||
|
||||
# 创建右眼摄像机
|
||||
self.right_eye_camera = self.world.makeCamera(self.right_eye_buffer)
|
||||
self.right_eye_camera.setPos(0.032, 0, 0) # 瞳距的一半
|
||||
|
||||
# 设置投影矩阵
|
||||
if not self.simulation_mode:
|
||||
self._update_eye_projection(0, self.left_eye_camera.node().getLens())
|
||||
self._update_eye_projection(1, self.right_eye_camera.node().getLens())
|
||||
else:
|
||||
# 模拟模式使用标准透视投影
|
||||
lens = PerspectiveLens()
|
||||
lens.setFov(110) # 模拟VR FOV
|
||||
lens.setNearFar(0.1, 1000)
|
||||
self.left_eye_camera.node().setLens(lens)
|
||||
self.right_eye_camera.node().setLens(lens)
|
||||
|
||||
print("✓ 眼部摄像机创建完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"眼部摄像机创建错误: {str(e)}")
|
||||
|
||||
def _update_eye_projection(self, eye, lens):
|
||||
"""更新眼部投影矩阵"""
|
||||
try:
|
||||
if self.simulation_mode:
|
||||
# 模拟模式使用标准投影
|
||||
perspective_lens = PerspectiveLens()
|
||||
perspective_lens.setFov(110)
|
||||
perspective_lens.setNearFar(0.1, 1000)
|
||||
lens.copyFrom(perspective_lens)
|
||||
return
|
||||
import openvr
|
||||
# 获取投影矩阵
|
||||
projection_matrix = self.vr_system.getProjectionMatrix(eye, 0.1, 1000.0)
|
||||
# 转换为 Panda3D 矩阵
|
||||
panda_matrix = self._convert_openvr_matrix(projection_matrix)
|
||||
# 使用正确的方法设置自定义投影矩阵
|
||||
try:
|
||||
lens.set_user_mat(panda_matrix)
|
||||
except AttributeError:
|
||||
# 如果方法不存在,尝试替代方法
|
||||
try:
|
||||
lens.set_projection_mat(panda_matrix)
|
||||
except AttributeError:
|
||||
# 兜底:设置标准 FOV
|
||||
lens.setFov(110)
|
||||
lens.setNearFar(0.1, 1000)
|
||||
except Exception as e:
|
||||
print(f"眼部投影更新错误: {str(e)}")
|
||||
|
||||
def _setup_render_targets(self):
|
||||
"""设置渲染目标"""
|
||||
try:
|
||||
# 在模拟模式下,可以选择将渲染结果显示到主窗口
|
||||
if self.simulation_mode:
|
||||
# 创建并排显示的立体视图
|
||||
self._setup_simulation_display()
|
||||
|
||||
print("✓ 渲染目标设置完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"渲染目标设置错误: {str(e)}")
|
||||
|
||||
def _setup_simulation_display(self):
|
||||
"""设置模拟显示"""
|
||||
try:
|
||||
# 创建卡片来显示眼部纹理
|
||||
cm = CardMaker("stereo_display")
|
||||
|
||||
# 左眼显示区域
|
||||
cm.setFrame(-1, 0, -1, 1)
|
||||
left_card = self.world.render2d.attachNewNode(cm.generate())
|
||||
left_card.setTexture(self.left_eye_texture)
|
||||
|
||||
# 右眼显示区域
|
||||
cm.setFrame(0, 1, -1, 1)
|
||||
right_card = self.world.render2d.attachNewNode(cm.generate())
|
||||
right_card.setTexture(self.right_eye_texture)
|
||||
|
||||
print("✓ 模拟立体显示设置完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"模拟显示设置错误: {str(e)}")
|
||||
|
||||
def _init_alvr(self):
|
||||
"""初始化ALVR(仅在真实VR模式下)"""
|
||||
if self.simulation_mode:
|
||||
print("ℹ 模拟模式: ALVR串流已跳过")
|
||||
return False
|
||||
|
||||
try:
|
||||
# ALVR初始化逻辑
|
||||
# 这里应该连接到ALVR服务器
|
||||
print("✓ ALVR初始化完成")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"ALVR初始化错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _start_vr_tasks(self):
|
||||
"""启动VR更新任务(真实VR模式)"""
|
||||
if not self.simulation_mode:
|
||||
self.world.taskMgr.add(self._update_vr_tracking, "vr_tracking")
|
||||
self.world.taskMgr.add(self._update_vr_rendering, "vr_rendering")
|
||||
|
||||
def _start_simulation_tasks(self):
|
||||
"""启动模拟VR任务"""
|
||||
self.world.taskMgr.add(self._update_simulation_tracking, "simulation_tracking")
|
||||
self.world.taskMgr.add(self._update_simulation_rendering, "simulation_rendering")
|
||||
|
||||
def _update_simulation_tracking(self, task):
|
||||
"""更新模拟追踪数据"""
|
||||
try:
|
||||
# 模拟头部追踪(可以添加一些变化)
|
||||
import math
|
||||
time_factor = task.time * 0.5
|
||||
|
||||
# 模拟轻微的头部摆动
|
||||
head_pos = self.simulation_data['head_pose']['position']
|
||||
head_pos[1] = math.sin(time_factor) * 0.05 # 前后轻微摆动
|
||||
|
||||
# 更新主摄像机位置
|
||||
if hasattr(self.world, 'camera'):
|
||||
self.world.camera.setPos(head_pos[0], head_pos[1], head_pos[2])
|
||||
|
||||
# 更新控制器位置(模拟手部动作)
|
||||
for controller_id, pose in self.simulation_data['controller_poses'].items():
|
||||
if pose['connected']:
|
||||
# 模拟控制器轻微移动
|
||||
pose['position'][1] = math.sin(time_factor + controller_id) * 0.1
|
||||
|
||||
# 更新控制器节点位置
|
||||
if controller_id in self.controller_nodes:
|
||||
node = self.controller_nodes[controller_id]
|
||||
node.setPos(pose['position'][0], pose['position'][1], pose['position'][2])
|
||||
|
||||
return task.cont
|
||||
|
||||
except Exception as e:
|
||||
print(f"模拟追踪更新错误: {str(e)}")
|
||||
return task.cont
|
||||
|
||||
def _update_simulation_rendering(self, task):
|
||||
"""更新模拟渲染"""
|
||||
try:
|
||||
# 在模拟模式下,渲染已经由Panda3D自动处理
|
||||
# 这里可以添加任何特殊的渲染逻辑
|
||||
return task.cont
|
||||
|
||||
except Exception as e:
|
||||
print(f"模拟渲染更新错误: {str(e)}")
|
||||
return task.cont
|
||||
|
||||
def _update_vr_tracking(self, task):
|
||||
"""更新VR追踪数据(真实VR模式)"""
|
||||
try:
|
||||
if not self.vr_system or self.simulation_mode:
|
||||
return task.cont
|
||||
|
||||
import openvr
|
||||
|
||||
# 获取设备姿态
|
||||
poses, game_poses = self.vr_compositor.waitGetPoses(None, None)
|
||||
|
||||
# 检查poses是否有效
|
||||
if poses is None:
|
||||
print("VR追踪更新错误: 无法获取设备姿态数据")
|
||||
return task.cont
|
||||
|
||||
# 更新头显位置
|
||||
if poses[openvr.k_unTrackedDeviceIndex_Hmd].bPoseIsValid:
|
||||
self._update_main_camera_pose()
|
||||
|
||||
# 更新眼部摄像机
|
||||
self._update_eye_cameras()
|
||||
|
||||
# 更新控制器姿态
|
||||
self._update_controller_poses(poses)
|
||||
|
||||
return task.cont
|
||||
|
||||
except Exception as e:
|
||||
print(f"VR追踪更新错误: {str(e)}")
|
||||
return task.cont
|
||||
|
||||
def _update_vr_rendering(self, task):
|
||||
"""更新VR渲染(真实VR模式)"""
|
||||
try:
|
||||
if not self.vr_compositor or self.simulation_mode:
|
||||
return task.cont
|
||||
|
||||
# 确保渲染完成后再提交帧
|
||||
if hasattr(self.world, 'graphicsEngine'):
|
||||
self.world.graphicsEngine.render_frame()
|
||||
|
||||
# 提交帧到合成器
|
||||
self._submit_frames_to_compositor()
|
||||
|
||||
return task.cont
|
||||
|
||||
except Exception as e:
|
||||
print(f"VR渲染更新错误: {str(e)}")
|
||||
return task.cont
|
||||
|
||||
def _convert_openvr_matrix(self, openvr_matrix):
|
||||
"""转换OpenVR矩阵为Panda3D矩阵"""
|
||||
# 实现矩阵转换逻辑
|
||||
mat = Mat4()
|
||||
# 这里需要实现具体的矩阵转换
|
||||
return mat
|
||||
|
||||
def _update_main_camera_pose(self):
|
||||
"""更新主摄像机姿态"""
|
||||
try:
|
||||
if self.simulation_mode:
|
||||
return
|
||||
|
||||
# 从VR系统获取头显姿态并应用到主摄像机
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
print(f"主摄像机姿态更新错误: {str(e)}")
|
||||
|
||||
def _update_eye_cameras(self):
|
||||
"""更新眼部摄像机"""
|
||||
try:
|
||||
if self.simulation_mode:
|
||||
return
|
||||
|
||||
# 更新左右眼摄像机位置
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
print(f"眼部摄像机更新错误: {str(e)}")
|
||||
|
||||
def _update_controller_poses(self, poses):
|
||||
"""更新控制器姿态"""
|
||||
try:
|
||||
if self.simulation_mode:
|
||||
return
|
||||
|
||||
# 更新控制器位置和姿态
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
print(f"控制器姿态更新错误: {str(e)}")
|
||||
|
||||
def _submit_frames_to_compositor(self):
|
||||
"""提交帧到合成器"""
|
||||
try:
|
||||
if not self.vr_compositor or self.simulation_mode:
|
||||
return
|
||||
|
||||
import openvr
|
||||
|
||||
# 确保纹理已正确创建并有效
|
||||
if not self.left_eye_texture or not self.right_eye_texture:
|
||||
print("⚠ 眼部纹理未正确创建,无法提交帧")
|
||||
return
|
||||
|
||||
# 检查纹理是否准备好
|
||||
if not self.left_eye_texture.has_ram_image() or not self.right_eye_texture.has_ram_image():
|
||||
# 强制更新纹理数据
|
||||
try:
|
||||
self.world.graphicsEngine.extract_texture_data(self.left_eye_texture, self.world.win.get_gsg())
|
||||
self.world.graphicsEngine.extract_texture_data(self.right_eye_texture, self.world.win.get_gsg())
|
||||
except Exception as e:
|
||||
print(f"纹理数据提取失败: {str(e)}")
|
||||
|
||||
# 提交左眼纹理
|
||||
left_eye_texture = openvr.Texture_t()
|
||||
# 使用正确的API获取纹理ID
|
||||
try:
|
||||
tex_handle = self.left_eye_texture.get_handle()
|
||||
except:
|
||||
# 如果get_handle方法不可用,尝试其他方式
|
||||
tex_handle = self.left_eye_texture.get_texture_id() if hasattr(self.left_eye_texture, 'get_texture_id') else 0
|
||||
|
||||
left_eye_texture.handle = tex_handle
|
||||
left_eye_texture.eType = openvr.TextureType_OpenGL
|
||||
left_eye_texture.eColorSpace = openvr.ColorSpace_Gamma
|
||||
|
||||
# 检查纹理是否有效后再提交
|
||||
if tex_handle:
|
||||
self.vr_compositor.submit(openvr.Eye_Left, left_eye_texture)
|
||||
|
||||
# 提交右眼纹理
|
||||
right_eye_texture = openvr.Texture_t()
|
||||
# 使用正确的API获取纹理ID
|
||||
try:
|
||||
tex_handle = self.right_eye_texture.get_handle()
|
||||
except:
|
||||
# 如果get_handle方法不可用,尝试其他方式
|
||||
tex_handle = self.right_eye_texture.get_texture_id() if hasattr(self.right_eye_texture, 'get_texture_id') else 0
|
||||
|
||||
right_eye_texture.handle = tex_handle
|
||||
right_eye_texture.eType = openvr.TextureType_OpenGL
|
||||
right_eye_texture.eColorSpace = openvr.ColorSpace_Gamma
|
||||
|
||||
# 检查纹理是否有效后再提交
|
||||
if tex_handle:
|
||||
self.vr_compositor.submit(openvr.Eye_Right, right_eye_texture)
|
||||
|
||||
# 等待下一帧垂直同步
|
||||
self.vr_compositor.waitGetPoses(None, None)
|
||||
|
||||
except Exception as e:
|
||||
print(f"帧提交错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def get_controller_input(self, controller_id):
|
||||
"""获取控制器输入"""
|
||||
try:
|
||||
if self.simulation_mode:
|
||||
# 返回模拟的控制器输入
|
||||
return {
|
||||
'trigger': 0.0,
|
||||
'grip': 0.0,
|
||||
'touchpad': {'x': 0.0, 'y': 0.0, 'pressed': False},
|
||||
'menu': False,
|
||||
'connected': controller_id in self.simulation_data['controller_poses']
|
||||
}
|
||||
|
||||
if not self.vr_system:
|
||||
return None
|
||||
|
||||
import openvr
|
||||
|
||||
# 获取控制器状态
|
||||
result, controller_state = self.vr_system.getControllerState(controller_id)
|
||||
if not result:
|
||||
return None
|
||||
|
||||
# 解析控制器输入
|
||||
return {
|
||||
'trigger': controller_state.rAxis[1].x,
|
||||
'grip': controller_state.rAxis[2].x,
|
||||
'touchpad': {
|
||||
'x': controller_state.rAxis[0].x,
|
||||
'y': controller_state.rAxis[0].y,
|
||||
'pressed': controller_state.rAxis[0].x != 0 or controller_state.rAxis[0].y != 0
|
||||
},
|
||||
'menu': controller_state.ulButtonPressed & (1 << openvr.k_EButton_ApplicationMenu) != 0,
|
||||
'connected': True
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"控制器输入获取错误: {str(e)}")
|
||||
return None
|
||||
|
||||
def shutdown_vr(self):
|
||||
"""关闭VR系统"""
|
||||
try:
|
||||
# 停止任务
|
||||
if self.simulation_mode:
|
||||
self.world.taskMgr.remove("simulation_tracking")
|
||||
self.world.taskMgr.remove("simulation_rendering")
|
||||
else:
|
||||
self.world.taskMgr.remove("vr_tracking")
|
||||
self.world.taskMgr.remove("vr_rendering")
|
||||
|
||||
# 关闭OpenVR
|
||||
if self.vr_system and not self.simulation_mode:
|
||||
import openvr
|
||||
openvr.shutdown()
|
||||
|
||||
# 清理资源
|
||||
self.left_eye_buffer = None
|
||||
self.right_eye_buffer = None
|
||||
self.left_eye_camera = None
|
||||
self.right_eye_camera = None
|
||||
|
||||
self.vr_enabled = False
|
||||
self.simulation_mode = False
|
||||
|
||||
print("✓ VR系统已关闭")
|
||||
|
||||
except Exception as e:
|
||||
print(f"VR关闭错误: {str(e)}")
|
||||
|
||||
def is_vr_enabled(self):
|
||||
"""检查VR是否启用"""
|
||||
return self.vr_enabled
|
||||
|
||||
def get_vr_info(self):
|
||||
"""获取VR系统信息"""
|
||||
info = {
|
||||
'enabled': self.vr_enabled,
|
||||
'simulation_mode': self.simulation_mode,
|
||||
'render_size': (self.render_width, self.render_height),
|
||||
'alvr_enabled': self.alvr_enabled
|
||||
}
|
||||
|
||||
if self.simulation_mode:
|
||||
info['mode'] = 'simulation'
|
||||
info['controllers'] = len(self.simulation_data['controller_poses'])
|
||||
else:
|
||||
info['mode'] = 'real_vr'
|
||||
info['openvr_connected'] = self.vr_system is not None
|
||||
|
||||
return info
|
||||
|
||||
# 便捷方法
|
||||
def enable_simulation_mode(self):
|
||||
"""启用模拟模式(调试用)"""
|
||||
return self.initialize_vr(force_simulation=True)
|
||||
|
||||
def get_simulation_data(self):
|
||||
"""获取模拟数据"""
|
||||
return self.simulation_data if self.simulation_mode else None
|
||||
|
||||
def update_simulation_data(self, key, value):
|
||||
"""更新模拟数据"""
|
||||
if self.simulation_mode and key in self.simulation_data:
|
||||
self.simulation_data[key] = value
|
||||
return True
|
||||
return False
|
||||
346
demo/OPENXR_IMPLEMENTATION_GUIDE.md
Normal file
346
demo/OPENXR_IMPLEMENTATION_GUIDE.md
Normal file
@ -0,0 +1,346 @@
|
||||
# OpenXR 实现指南
|
||||
|
||||
## 🎯 概述
|
||||
|
||||
本指南详细介绍了如何在Panda3D引擎中实现OpenXR支持,并通过有线连接串流到VR设备。
|
||||
|
||||
## 🏗️ 系统架构
|
||||
|
||||
### 核心组件
|
||||
|
||||
1. **OpenXRManager** (`core/openxr_manager.py`)
|
||||
- 负责OpenXR系统初始化
|
||||
- 管理立体渲染(左右眼)
|
||||
- 处理VR设备跟踪
|
||||
- 集成OpenXR接口
|
||||
|
||||
2. **OpenXRInputHandler** (`core/openxr_input_handler.py`)
|
||||
- 处理OpenXR控制器输入
|
||||
- 手势识别系统
|
||||
- 交互逻辑处理
|
||||
|
||||
3. **OpenXRStreamer** (`core/openxr_streamer.py`)
|
||||
- OpenXR服务器通信
|
||||
- 视频流编码和传输
|
||||
- 性能监控
|
||||
|
||||
## 🔧 安装和配置
|
||||
|
||||
### 1. 系统依赖
|
||||
|
||||
```bash
|
||||
# 安装Python依赖
|
||||
pip install -r requirements/openxr-requirements.txt
|
||||
|
||||
# 安装OpenXR运行时
|
||||
# Windows:
|
||||
# 1. 下载并安装SteamVR或Oculus运行时
|
||||
# 2. 安装OpenXR运行时
|
||||
|
||||
# Linux:
|
||||
sudo apt-get install openxr-loader
|
||||
```
|
||||
|
||||
### 2. 有线连接设置
|
||||
|
||||
```bash
|
||||
# 配置网络以支持有线连接
|
||||
# 1. 使用USB网络适配器或以太网连接VR设备
|
||||
# 2. 确保PC和VR设备在同一子网
|
||||
# 3. 配置防火墙允许OpenXR通信(端口9943-9945)
|
||||
```
|
||||
|
||||
### 3. VR设备配置
|
||||
|
||||
```bash
|
||||
# 1. 在VR设备上安装OpenXR客户端应用
|
||||
# 2. 通过有线连接设备到PC
|
||||
# 3. 启用开发者模式(如需要)
|
||||
```
|
||||
|
||||
## 🚀 使用方法
|
||||
|
||||
### 快速启动
|
||||
|
||||
```python
|
||||
# 在主程序中
|
||||
world = MyWorld()
|
||||
|
||||
# 一键启用OpenXR模式
|
||||
if world.enableOpenXRMode():
|
||||
print("OpenXR模式已启用")
|
||||
|
||||
# 检查OpenXR状态
|
||||
status = world.getOpenXRStatus()
|
||||
print(f"OpenXR设备: {status['openxr_info']}")
|
||||
print(f"连接状态: {status['openxr_connected']}")
|
||||
print(f"串流状态: {status['openxr_streaming']}")
|
||||
```
|
||||
|
||||
### 详细控制
|
||||
|
||||
```python
|
||||
# 分步骤启动OpenXR
|
||||
world = MyWorld()
|
||||
|
||||
# 1. 初始化OpenXR系统
|
||||
if world.initializeOpenXR():
|
||||
print("OpenXR系统初始化成功")
|
||||
|
||||
# 2. 启动OpenXR输入处理
|
||||
world.startOpenXRInput()
|
||||
|
||||
# 3. 显示控制器射线
|
||||
world.showControllerRays(True)
|
||||
|
||||
# 4. 初始化OpenXR串流
|
||||
if world.initializeOpenXRStreaming():
|
||||
print("OpenXR串流初始化成功")
|
||||
|
||||
# 5. 设置串流质量
|
||||
world.setOpenXRStreamQuality(
|
||||
width=2880,
|
||||
height=1700,
|
||||
fps=72,
|
||||
bitrate=150
|
||||
)
|
||||
|
||||
# 6. 开始串流
|
||||
world.startOpenXRStreaming()
|
||||
```
|
||||
|
||||
## 🎮 OpenXR交互功能
|
||||
|
||||
### 控制器输入处理
|
||||
|
||||
```python
|
||||
# 获取控制器状态
|
||||
controllers = world.getAllControllers()
|
||||
for controller_id in controllers:
|
||||
state = world.getControllerState(controller_id)
|
||||
|
||||
# 检查触发器按下
|
||||
if state['trigger'] > 0.5:
|
||||
print(f"控制器 {controller_id} 触发器按下")
|
||||
|
||||
# 检查摇杆输入
|
||||
thumbstick_x, thumbstick_y = state['thumbstick']['x'], state['thumbstick']['y']
|
||||
if abs(thumbstick_x) > 0.5:
|
||||
print(f"摇杆水平滑动: {thumbstick_x}")
|
||||
```
|
||||
|
||||
### 对象抓取和操作
|
||||
|
||||
```python
|
||||
# OpenXR输入处理器自动处理对象抓取
|
||||
# 触发器按下时自动检测和抓取对象
|
||||
# 触发器释放时自动释放对象
|
||||
|
||||
# 监听OpenXR事件
|
||||
world.event_handler.accept('openxr-object-grabbed', onObjectGrabbed)
|
||||
world.event_handler.accept('openxr-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
|
||||
# 调整OpenXR渲染质量
|
||||
openxr_manager = world.openxr_manager
|
||||
openxr_manager.render_scale = 1.0 # 渲染缩放 (0.5-2.0)
|
||||
|
||||
# 设置多重采样抗锯齿
|
||||
fb_props = FrameBufferProperties()
|
||||
fb_props.setMultisamples(4) # 4x MSAA
|
||||
```
|
||||
|
||||
### 网络优化
|
||||
|
||||
```python
|
||||
# 优化OpenXR串流参数
|
||||
world.setOpenXRStreamQuality(
|
||||
width=2160, # 降低分辨率提高性能
|
||||
height=1200,
|
||||
fps=60, # 降低帧率减少延迟
|
||||
bitrate=100 # 降低比特率适应网络带宽
|
||||
)
|
||||
```
|
||||
|
||||
### 系统资源监控
|
||||
|
||||
```python
|
||||
# 获取性能状态
|
||||
streaming_status = world.getOpenXRStreamingStatus()
|
||||
print(f"串流FPS: {streaming_status['fps']}")
|
||||
print(f"延迟: {streaming_status['latency']} ms")
|
||||
print(f"分辨率: {streaming_status['resolution']}")
|
||||
```
|
||||
|
||||
## 🔍 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **OpenXR系统初始化失败**
|
||||
```
|
||||
错误: OpenXR初始化失败
|
||||
解决: 确保OpenXR运行时已安装并运行
|
||||
```
|
||||
|
||||
2. **串流连接失败**
|
||||
```
|
||||
错误: 无法连接到串流服务器
|
||||
解决:
|
||||
- 检查串流服务器是否运行
|
||||
- 确认防火墙设置
|
||||
- 检查网络连接
|
||||
```
|
||||
|
||||
3. **串流质量问题**
|
||||
```
|
||||
问题: 画面卡顿或延迟高
|
||||
解决:
|
||||
- 降低分辨率和帧率
|
||||
- 检查网络带宽
|
||||
- 调整比特率设置
|
||||
```
|
||||
|
||||
### 调试命令
|
||||
|
||||
```python
|
||||
# 启用调试模式
|
||||
world.openxr_manager.debug_mode = True
|
||||
|
||||
# 获取详细状态信息
|
||||
openxr_info = world.getOpenXRInfo()
|
||||
print(f"OpenXR设备信息: {openxr_info}")
|
||||
|
||||
# 检查网络连接
|
||||
if world.isOpenXRConnected():
|
||||
print("OpenXR连接正常")
|
||||
else:
|
||||
print("OpenXR连接失败")
|
||||
```
|
||||
|
||||
## 🎯 高级功能
|
||||
|
||||
### 自定义OpenXR交互
|
||||
|
||||
```python
|
||||
# 创建自定义OpenXR交互逻辑
|
||||
class CustomOpenXRInteraction:
|
||||
def __init__(self, world):
|
||||
self.world = world
|
||||
|
||||
def handleCustomGesture(self, gesture_data):
|
||||
# 处理自定义手势
|
||||
pass
|
||||
|
||||
def createOpenXRMenu(self, controller_id):
|
||||
# 在VR中创建3D菜单
|
||||
pass
|
||||
```
|
||||
|
||||
### 多用户OpenXR支持
|
||||
|
||||
```python
|
||||
# 支持多个OpenXR用户
|
||||
class MultiUserOpenXRManager:
|
||||
def __init__(self):
|
||||
self.openxr_users = {}
|
||||
|
||||
def addOpenXRUser(self, user_id, openxr_manager):
|
||||
self.openxr_users[user_id] = openxr_manager
|
||||
|
||||
def syncOpenXRUsers(self):
|
||||
# 同步多用户OpenXR状态
|
||||
pass
|
||||
```
|
||||
|
||||
## 📋 配置文件
|
||||
|
||||
### OpenXR配置 (`config/openxr_config.yaml`)
|
||||
|
||||
```yaml
|
||||
openxr:
|
||||
enabled: true
|
||||
render_scale: 1.0
|
||||
tracking_space: "stage"
|
||||
|
||||
streaming:
|
||||
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
|
||||
# 创建OpenXR开发环境
|
||||
python -m venv openxr_env
|
||||
source openxr_env/bin/activate
|
||||
pip install -r requirements/openxr-requirements.txt
|
||||
|
||||
# 启动开发服务器
|
||||
python main.py --openxr-mode
|
||||
```
|
||||
|
||||
### 生产环境
|
||||
|
||||
```bash
|
||||
# 优化生产部署
|
||||
# 1. 使用专用计算机处理OpenXR渲染
|
||||
# 2. 配置高性能有线网络连接
|
||||
# 3. 启用GPU加速
|
||||
# 4. 监控系统性能
|
||||
```
|
||||
|
||||
## 📚 参考资料
|
||||
|
||||
- [OpenXR规范](https://www.khronos.org/openxr/)
|
||||
- [Panda3D文档](https://docs.panda3d.org/)
|
||||
- [OpenXR Cookbook](https://github.com/KhronosGroup/OpenXR-Docs)
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
欢迎贡献代码和改进建议!请遵循以下步骤:
|
||||
|
||||
1. Fork此仓库
|
||||
2. 创建功能分支
|
||||
3. 提交代码修改
|
||||
4. 创建Pull Request
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本项目采用MIT许可证。详见LICENSE文件。
|
||||
@ -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功能的正确性。
|
||||
@ -1,197 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
直接VR串流测试脚本
|
||||
测试不通过ALVR,直接使用OpenVR API进行串流的功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from main import MyWorld
|
||||
|
||||
|
||||
def test_direct_vr_streaming():
|
||||
"""测试直接VR串流功能"""
|
||||
print("=== 直接VR串流功能测试 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 初始化VR系统
|
||||
print("🥽 初始化VR系统...")
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
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']}")
|
||||
|
||||
# 检查是否为真实VR模式
|
||||
if not vr_info['enabled'] or vr_info['simulation_mode']:
|
||||
print("⚠ 未检测到真实VR设备或处于模拟模式")
|
||||
print(" 直接串流需要真实的VR设备支持")
|
||||
return False
|
||||
|
||||
# 检查VR纹理是否正确创建
|
||||
print("\n🔍 检查VR纹理...")
|
||||
if hasattr(world.vr_manager, 'left_eye_texture') and world.vr_manager.left_eye_texture:
|
||||
print(" ✓ 左眼纹理已创建")
|
||||
print(f" 纹理尺寸: {world.vr_manager.left_eye_texture.getXSize()}x{world.vr_manager.left_eye_texture.getYSize()}")
|
||||
else:
|
||||
print(" ✗ 左眼纹理未创建")
|
||||
return False
|
||||
|
||||
if hasattr(world.vr_manager, 'right_eye_texture') and world.vr_manager.right_eye_texture:
|
||||
print(" ✓ 右眼纹理已创建")
|
||||
print(f" 纹理尺寸: {world.vr_manager.right_eye_texture.getXSize()}x{world.vr_manager.right_eye_texture.getYSize()}")
|
||||
else:
|
||||
print(" ✗ 右眼纹理未创建")
|
||||
return False
|
||||
|
||||
# 检查OpenVR系统是否正常
|
||||
print("\n🔍 检查OpenVR系统...")
|
||||
if hasattr(world.vr_manager, 'vr_system') and world.vr_manager.vr_system:
|
||||
print(" ✓ OpenVR系统已初始化")
|
||||
else:
|
||||
print(" ✗ OpenVR系统未初始化")
|
||||
return False
|
||||
|
||||
if hasattr(world.vr_manager, 'vr_compositor') and world.vr_manager.vr_compositor:
|
||||
print(" ✓ OpenVR合成器已初始化")
|
||||
else:
|
||||
print(" ✗ OpenVR合成器未初始化")
|
||||
return False
|
||||
|
||||
# 创建简单的测试场景
|
||||
print("\n🎨 创建测试场景...")
|
||||
try:
|
||||
from panda3d.core import CardMaker, Vec4, AmbientLight, DirectionalLight
|
||||
|
||||
# 添加光照
|
||||
ambient_light = AmbientLight('ambient')
|
||||
ambient_light.setColor(Vec4(0.3, 0.3, 0.3, 1))
|
||||
ambient_light_np = world.render.attachNewNode(ambient_light)
|
||||
world.render.setLight(ambient_light_np)
|
||||
|
||||
directional_light = DirectionalLight('directional')
|
||||
directional_light.setColor(Vec4(0.8, 0.8, 0.8, 1))
|
||||
directional_light.setDirection(Vec4(-1, -1, -1, 0))
|
||||
directional_light_np = world.render.attachNewNode(directional_light)
|
||||
world.render.setLight(directional_light_np)
|
||||
|
||||
# 创建地面网格
|
||||
for i in range(-5, 6):
|
||||
for j in range(-5, 6):
|
||||
cm = CardMaker(f"grid_{i}_{j}")
|
||||
cm.setFrame(-0.4, 0.4, -0.4, 0.4)
|
||||
grid = world.render.attachNewNode(cm.generate())
|
||||
grid.setPos(i * 1.0, j * 1.0, -1)
|
||||
grid.setHpr(0, -90, 0)
|
||||
|
||||
# 棋盘格颜色
|
||||
if (i + j) % 2 == 0:
|
||||
grid.setColor(0.8, 0.8, 0.8, 1.0)
|
||||
else:
|
||||
grid.setColor(0.6, 0.6, 0.6, 1.0)
|
||||
|
||||
# 创建一些彩色立方体
|
||||
for i in range(8):
|
||||
cm = CardMaker(f"cube_{i}")
|
||||
cm.setFrame(-0.3, 0.3, -0.3, 0.3)
|
||||
cube = world.render.attachNewNode(cm.generate())
|
||||
|
||||
import math
|
||||
angle = i * math.pi * 2 / 8
|
||||
radius = 3
|
||||
cube.setPos(math.cos(angle) * radius, math.sin(angle) * radius, 0.5)
|
||||
cube.setHpr(i * 45, 0, 0)
|
||||
cube.setScale(1, 1, 2)
|
||||
|
||||
# 彩虹颜色
|
||||
hue = i / 8.0
|
||||
r = abs(math.sin(hue * math.pi * 2))
|
||||
g = abs(math.sin((hue + 0.33) * math.pi * 2))
|
||||
b = abs(math.sin((hue + 0.66) * math.pi * 2))
|
||||
cube.setColor(r, g, b, 1.0)
|
||||
|
||||
print("✓ 测试场景创建完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"场景创建错误: {str(e)}")
|
||||
|
||||
# 测试直接串流
|
||||
print("\n🎮 开始直接VR串流测试...")
|
||||
print(" 测试将持续15秒,请观察VR头显中的画面")
|
||||
|
||||
start_time = time.time()
|
||||
frame_count = 0
|
||||
|
||||
try:
|
||||
while time.time() - start_time < 15:
|
||||
# 处理引擎更新
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
|
||||
frame_count += 1
|
||||
|
||||
# 每秒显示一次状态
|
||||
if frame_count % 60 == 0: # 约每秒
|
||||
elapsed = time.time() - start_time
|
||||
fps = frame_count / elapsed
|
||||
print(f" [{elapsed:.1f}s] FPS: {fps:.1f}")
|
||||
|
||||
time.sleep(0.016) # ~60 FPS
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⏹️ 用户中断测试")
|
||||
|
||||
# 关闭系统
|
||||
world.shutdownVR()
|
||||
print("\n✓ VR系统已关闭")
|
||||
|
||||
final_fps = frame_count / (time.time() - start_time)
|
||||
print(f"✅ 直接VR串流测试完成,平均FPS: {final_fps:.1f}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🎮 直接VR串流测试脚本")
|
||||
print("="*50)
|
||||
print("此脚本将测试不通过ALVR,直接使用OpenVR API进行串流的功能")
|
||||
print("请确保:")
|
||||
print(" 1. VR设备已连接(如Pico 4通过USB-C连接)")
|
||||
print(" 2. SteamVR正在运行")
|
||||
print(" 3. VR设备已被SteamVR识别")
|
||||
|
||||
try:
|
||||
input("\n按回车键开始测试,或按Ctrl+C退出...")
|
||||
success = test_direct_vr_streaming()
|
||||
|
||||
print("\n" + "="*50)
|
||||
if success:
|
||||
print("✓ 直接VR串流测试成功完成")
|
||||
print("🎉 您现在已经可以不通过ALVR直接串流到VR设备!")
|
||||
else:
|
||||
print("✗ 直接VR串流测试遇到问题")
|
||||
print("💡 请检查VR设备连接和SteamVR状态")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n用户中断测试")
|
||||
except Exception as e:
|
||||
print(f"测试过程中发生错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
305
main.py
305
main.py
@ -15,9 +15,9 @@ 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.openxr_manager import OpenXRManager
|
||||
from core.openxr_input_handler import OpenXRInputHandler
|
||||
from core.openxr_streamer import OpenXRStreamer
|
||||
from gui.gui_manager import GUIManager
|
||||
from scene.scene_manager import SceneManager
|
||||
from project.project_manager import ProjectManager
|
||||
@ -41,7 +41,7 @@ import json
|
||||
import datetime
|
||||
from direct.actor.Actor import Actor
|
||||
from PyQt5.sip import wrapinstance
|
||||
#from RenderPipelineFile.toolkit.material_editor.main import MaterialEditor
|
||||
# RenderPipelineFile.toolkit.material_editor.main import MaterialEditor
|
||||
|
||||
|
||||
class MyWorld(CoreWorld):
|
||||
@ -75,10 +75,10 @@ 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)
|
||||
# 初始化OpenXR系统
|
||||
self.openxr_manager = OpenXRManager(self)
|
||||
self.openxr_input_handler = OpenXRInputHandler(self, self.openxr_manager)
|
||||
self.openxr_streamer = OpenXRStreamer(self, self.openxr_manager)
|
||||
|
||||
# 启动脚本系统
|
||||
self.script_manager.start_system()
|
||||
@ -444,6 +444,144 @@ class MyWorld(CoreWorld):
|
||||
"""根据名称查找模型"""
|
||||
return self.scene_manager.findModelByName(name)
|
||||
|
||||
# ==================== OpenXR系统功能代理 ====================
|
||||
|
||||
# OpenXR系统控制方法 - 代理到openxr_manager
|
||||
def initializeOpenXR(self):
|
||||
"""初始化OpenXR系统"""
|
||||
return self.openxr_manager.initialize_openxr()
|
||||
|
||||
def shutdownOpenXR(self):
|
||||
"""关闭OpenXR系统"""
|
||||
return self.openxr_manager.shutdown_openxr()
|
||||
|
||||
def isOpenXREnabled(self):
|
||||
"""检查OpenXR是否启用"""
|
||||
return self.openxr_manager.is_openxr_enabled()
|
||||
|
||||
def getOpenXRInfo(self):
|
||||
"""获取OpenXR系统信息"""
|
||||
return self.openxr_manager.get_openxr_info()
|
||||
|
||||
# OpenXR输入处理方法 - 代理到openxr_input_handler
|
||||
def startOpenXRInput(self):
|
||||
"""启动OpenXR输入处理"""
|
||||
return self.openxr_input_handler.start_input_handling()
|
||||
|
||||
def stopOpenXRInput(self):
|
||||
"""停止OpenXR输入处理"""
|
||||
return self.openxr_input_handler.stop_input_handling()
|
||||
|
||||
def showControllerRays(self, show=True):
|
||||
"""显示/隐藏控制器射线"""
|
||||
return self.openxr_input_handler.show_controller_rays(show)
|
||||
|
||||
def getControllerState(self, controller_id):
|
||||
"""获取控制器状态"""
|
||||
return self.openxr_input_handler.get_controller_state(controller_id)
|
||||
|
||||
def getAllControllers(self):
|
||||
"""获取所有控制器"""
|
||||
return self.openxr_input_handler.get_all_controllers()
|
||||
|
||||
def setVRGestureEnabled(self, enabled):
|
||||
"""设置OpenXR手势识别启用状态"""
|
||||
return self.openxr_input_handler.set_gesture_enabled(enabled)
|
||||
|
||||
def setVRInteractionEnabled(self, enabled):
|
||||
"""设置OpenXR交互启用状态"""
|
||||
return self.openxr_input_handler.set_interaction_enabled(enabled)
|
||||
|
||||
# OpenXR串流方法 - 代理到openxr_streamer
|
||||
def initializeOpenXRStreaming(self):
|
||||
"""初始化OpenXR串流"""
|
||||
return self.openxr_streamer.initialize()
|
||||
|
||||
def startOpenXRStreaming(self):
|
||||
"""开始OpenXR串流"""
|
||||
return self.openxr_streamer.start_streaming()
|
||||
|
||||
def stopOpenXRStreaming(self):
|
||||
"""停止OpenXR串流"""
|
||||
return self.openxr_streamer.stop_streaming()
|
||||
|
||||
def getOpenXRStreamingStatus(self):
|
||||
"""获取OpenXR串流状态"""
|
||||
return self.openxr_streamer.get_streaming_status()
|
||||
|
||||
def setOpenXRStreamQuality(self, width, height, fps, bitrate):
|
||||
"""设置OpenXR串流质量"""
|
||||
return self.openxr_streamer.set_stream_quality(width, height, fps, bitrate)
|
||||
|
||||
def sendHapticFeedback(self, controller_id, duration, intensity):
|
||||
"""发送触觉反馈"""
|
||||
return self.openxr_streamer.send_haptic_feedback(controller_id, duration, intensity)
|
||||
|
||||
def shutdownOpenXRStreaming(self):
|
||||
"""关闭OpenXR串流"""
|
||||
return self.openxr_streamer.shutdown()
|
||||
|
||||
def isOpenXRConnected(self):
|
||||
"""检查OpenXR是否连接"""
|
||||
return self.openxr_streamer.is_connected()
|
||||
|
||||
def isOpenXRStreaming(self):
|
||||
"""检查OpenXR是否在串流"""
|
||||
return self.openxr_streamer.is_streaming()
|
||||
|
||||
# 便捷方法
|
||||
def enableOpenXRMode(self):
|
||||
"""启用OpenXR模式(一键启动)"""
|
||||
print("启用OpenXR模式...")
|
||||
|
||||
# 1. 初始化OpenXR系统
|
||||
if not self.initializeOpenXR():
|
||||
print("OpenXR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 2. 启动OpenXR输入处理
|
||||
if not self.startOpenXRInput():
|
||||
print("OpenXR输入处理启动失败")
|
||||
return False
|
||||
|
||||
# 3. 初始化OpenXR串流
|
||||
if self.initializeOpenXRStreaming():
|
||||
print("✓ OpenXR串流已启用")
|
||||
# 自动开始串流
|
||||
self.startOpenXRStreaming()
|
||||
else:
|
||||
print("⚠ OpenXR串流启用失败,但OpenXR系统仍可用")
|
||||
|
||||
print("✓ OpenXR模式已启用")
|
||||
return True
|
||||
|
||||
def disableOpenXRMode(self):
|
||||
"""禁用OpenXR模式(一键关闭)"""
|
||||
print("禁用OpenXR模式...")
|
||||
|
||||
# 1. 停止OpenXR串流
|
||||
self.stopOpenXRStreaming()
|
||||
self.shutdownOpenXRStreaming()
|
||||
|
||||
# 2. 停止OpenXR输入处理
|
||||
self.stopOpenXRInput()
|
||||
|
||||
# 3. 关闭OpenXR系统
|
||||
self.shutdownOpenXR()
|
||||
|
||||
print("✓ OpenXR模式已禁用")
|
||||
|
||||
def getOpenXRStatus(self):
|
||||
"""获取OpenXR系统总体状态"""
|
||||
return {
|
||||
"openxr_enabled": self.isOpenXREnabled(),
|
||||
"openxr_info": self.getOpenXRInfo(),
|
||||
"controllers": self.getAllControllers(),
|
||||
"openxr_connected": self.isOpenXRConnected(),
|
||||
"openxr_streaming": self.isOpenXRStreaming(),
|
||||
"streaming_status": self.getOpenXRStreamingStatus() if self.isOpenXRConnected() else None
|
||||
}
|
||||
|
||||
# ==================== 脚本系统功能代理 ====================
|
||||
|
||||
# 脚本系统控制方法 - 代理到script_manager
|
||||
@ -510,143 +648,6 @@ class MyWorld(CoreWorld):
|
||||
"""列出所有脚本信息"""
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
# ==================== 项目管理功能代理 ====================
|
||||
@ -681,13 +682,13 @@ def buildPackage(appw):
|
||||
# def open_material_editor(self):
|
||||
# """打开材质编辑器作为子窗口"""
|
||||
# if not self.material_editor:
|
||||
# self.material_editor = MaterialEditor()
|
||||
# # 设置为子窗口
|
||||
# self.material_editor.setParent(self)
|
||||
# self.material_editor.setWindowFlags(Qt.Window)
|
||||
#
|
||||
# self.material_editor.show()
|
||||
#
|
||||
# # self.material_editor = MaterialEditor()
|
||||
# # # 设置为子窗口
|
||||
# # self.material_editor.setParent(self)
|
||||
# # self.material_editor.setWindowFlags(Qt.Window)
|
||||
# #
|
||||
# # self.material_editor.show()
|
||||
# #
|
||||
|
||||
if __name__ == "__main__":
|
||||
world = MyWorld()
|
||||
|
||||
480
openxr_test.py
Executable file
480
openxr_test.py
Executable file
@ -0,0 +1,480 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
OpenXR测试脚本
|
||||
|
||||
用于测试OpenXR功能的基本实现
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from main import MyWorld
|
||||
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QHBoxLayout, QLabel
|
||||
from PyQt5.QtGui import QPixmap, QImage
|
||||
from PyQt5.QtCore import QTimer
|
||||
import numpy as np
|
||||
|
||||
|
||||
def test_openxr_basic_display(world):
|
||||
"""测试基本OpenXR显示功能"""
|
||||
print("=== OpenXR基本显示测试 ===")
|
||||
|
||||
# 加载一些模型来展示
|
||||
print("📦 加载演示场景...")
|
||||
try:
|
||||
# 创建一些简单的几何体作为演示
|
||||
from panda3d.core import CardMaker, Vec4
|
||||
|
||||
# 创建地面
|
||||
cm = CardMaker("ground")
|
||||
cm.setFrame(-10, 10, -10, 10)
|
||||
ground = world.render.attachNewNode(cm.generate())
|
||||
ground.setPos(0, 0, -1)
|
||||
ground.setColor(0.5, 0.8, 0.5, 1.0)
|
||||
ground.setHpr(0, -90, 0)
|
||||
|
||||
# 创建一些立方体
|
||||
for i in range(5):
|
||||
for j in range(5):
|
||||
cube = world.loader.loadModel("models/environment")
|
||||
if not cube:
|
||||
# 如果没有environment模型,创建简单的立方体
|
||||
cm_cube = CardMaker(f"cube_{i}_{j}")
|
||||
cm_cube.setFrame(-0.5, 0.5, -0.5, 0.5)
|
||||
cube = world.render.attachNewNode(cm_cube.generate())
|
||||
|
||||
cube.setPos(i * 2 - 4, j * 2 - 4, 0)
|
||||
cube.setScale(0.5)
|
||||
cube.setColor(i/5.0, j/5.0, 1.0, 1.0)
|
||||
|
||||
print("✓ 演示场景加载完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"场景加载错误: {str(e)}")
|
||||
|
||||
# 初始化OpenXR系统
|
||||
print("\n🥽 初始化OpenXR显示系统...")
|
||||
openxr_success = world.initializeOpenXR()
|
||||
|
||||
if openxr_success:
|
||||
print("✓ OpenXR系统初始化成功")
|
||||
openxr_info = world.getOpenXRInfo()
|
||||
|
||||
print(f"\n📊 OpenXR显示信息:")
|
||||
print(f" - 模式: {openxr_info['mode']}")
|
||||
print(f" - 渲染尺寸: {openxr_info['render_size']}")
|
||||
print(f" - 模拟模式: {openxr_info['simulation_mode']}")
|
||||
print(f" - 有线连接: {openxr_info['wired_connection']}")
|
||||
|
||||
if openxr_info['simulation_mode']:
|
||||
print("\n💡 模拟模式说明:")
|
||||
print(" - 左右眼视图将并排显示在主窗口")
|
||||
print(" - 可以看到立体渲染效果")
|
||||
print(" - 头部会有轻微的模拟摆动")
|
||||
else:
|
||||
print("\n💡 真实OpenXR模式:")
|
||||
print(" - 画面将渲染到VR头盔")
|
||||
print(" - 支持真实的头部追踪")
|
||||
print(" - 控制器交互可用")
|
||||
|
||||
# 创建显示窗口
|
||||
app = QApplication.instance()
|
||||
if app is None:
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 创建主窗口显示OpenXR渲染结果
|
||||
window = QMainWindow()
|
||||
window.setWindowTitle("OpenXR Stereo Rendering Preview")
|
||||
window.setGeometry(100, 100, 800, 300)
|
||||
|
||||
central_widget = QWidget()
|
||||
layout = QHBoxLayout()
|
||||
central_widget.setLayout(layout)
|
||||
|
||||
left_label = QLabel("Left Eye")
|
||||
right_label = QLabel("Right Eye")
|
||||
layout.addWidget(left_label)
|
||||
layout.addWidget(right_label)
|
||||
|
||||
window.setCentralWidget(central_widget)
|
||||
window.show()
|
||||
|
||||
# 定时更新显示
|
||||
def update_display():
|
||||
if hasattr(world.openxr_manager, 'left_eye_texture') and world.openxr_manager.left_eye_texture:
|
||||
# 获取左眼纹理数据
|
||||
tex = world.openxr_manager.left_eye_texture
|
||||
try:
|
||||
if tex.get_x_size() > 0 and tex.get_y_size() > 0:
|
||||
# 添加对纹理数据有效性的检查
|
||||
if tex.might_have_ram_image():
|
||||
# 使用正确的API将纹理数据复制到QImage
|
||||
data = tex.get_ram_image().get_data()
|
||||
if data:
|
||||
img = QImage(data, tex.get_x_size(), tex.get_y_size(), QImage.Format_RGBA8888)
|
||||
pixmap = QPixmap.fromImage(img)
|
||||
left_label.setPixmap(pixmap.scaled(400, 300))
|
||||
left_label.setText("") # 清除文字,只显示图像
|
||||
else:
|
||||
left_label.setText("Left Eye (No Data)")
|
||||
else:
|
||||
left_label.setText("Left Eye (No RAM Image)")
|
||||
else:
|
||||
left_label.setText("Left Eye (Invalid Size)")
|
||||
except Exception as e:
|
||||
print(f"左眼纹理更新错误: {str(e)}")
|
||||
left_label.setText("Left Eye (Error)")
|
||||
else:
|
||||
left_label.setText("Left Eye (No Texture)")
|
||||
|
||||
if hasattr(world.openxr_manager, 'right_eye_texture') and world.openxr_manager.right_eye_texture:
|
||||
# 获取右眼纹理数据
|
||||
tex = world.openxr_manager.right_eye_texture
|
||||
try:
|
||||
if tex.get_x_size() > 0 and tex.get_y_size() > 0:
|
||||
# 添加对纹理数据有效性的检查
|
||||
if tex.might_have_ram_image():
|
||||
# 使用正确的API将纹理数据复制到QImage
|
||||
data = tex.get_ram_image().get_data()
|
||||
if data:
|
||||
img = QImage(data, tex.get_x_size(), tex.get_y_size(), QImage.Format_RGBA8888)
|
||||
pixmap = QPixmap.fromImage(img)
|
||||
right_label.setPixmap(pixmap.scaled(400, 300))
|
||||
right_label.setText("") # 清除文字,只显示图像
|
||||
else:
|
||||
right_label.setText("Right Eye (No Data)")
|
||||
else:
|
||||
right_label.setText("Right Eye (No RAM Image)")
|
||||
else:
|
||||
right_label.setText("Right Eye (Invalid Size)")
|
||||
except Exception as e:
|
||||
print(f"右眼纹理更新错误: {str(e)}")
|
||||
right_label.setText("Right Eye (Error)")
|
||||
else:
|
||||
right_label.setText("Right Eye (No Texture)")
|
||||
|
||||
timer = QTimer()
|
||||
timer.timeout.connect(update_display)
|
||||
timer.start(33) # 约30 FPS
|
||||
|
||||
# 运行几秒钟以展示效果
|
||||
print(f"\n🎮 OpenXR显示测试运行中... (10秒)")
|
||||
print(" 观察主窗口中的立体渲染效果")
|
||||
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 10:
|
||||
# 处理Panda3D事件
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
|
||||
# 处理Qt事件
|
||||
try:
|
||||
app.processEvents()
|
||||
except Exception as e:
|
||||
print(f"Qt事件处理错误: {str(e)}")
|
||||
|
||||
# 添加短暂延迟以避免过度占用CPU
|
||||
time.sleep(0.016) # ~60 FPS
|
||||
|
||||
print("✓ OpenXR显示测试完成")
|
||||
|
||||
else:
|
||||
print("✗ OpenXR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 关闭OpenXR系统
|
||||
world.shutdownOpenXR()
|
||||
print("\n✓ OpenXR系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_openxr_streaming(world):
|
||||
"""测试OpenXR串流功能"""
|
||||
print("=== OpenXR串流测试 ===")
|
||||
|
||||
# 初始化OpenXR系统
|
||||
print("初始化OpenXR系统...")
|
||||
if not world.initializeOpenXR():
|
||||
print("✗ OpenXR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 启动输入处理
|
||||
print("启动OpenXR输入处理...")
|
||||
if not world.startOpenXRInput():
|
||||
print("⚠ OpenXR输入处理启动失败")
|
||||
|
||||
# 初始化串流
|
||||
print("初始化OpenXR串流...")
|
||||
if world.initializeOpenXRStreaming():
|
||||
print("✓ OpenXR串流初始化成功")
|
||||
|
||||
# 开始串流
|
||||
print("开始OpenXR串流...")
|
||||
if world.startOpenXRStreaming():
|
||||
print("✓ OpenXR串流已开始")
|
||||
|
||||
# 创建显示窗口
|
||||
app = QApplication.instance()
|
||||
if app is None:
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 创建主窗口显示OpenXR渲染结果
|
||||
window = QMainWindow()
|
||||
window.setWindowTitle("OpenXR Streaming Preview")
|
||||
window.setGeometry(100, 100, 800, 300)
|
||||
|
||||
central_widget = QWidget()
|
||||
layout = QHBoxLayout()
|
||||
central_widget.setLayout(layout)
|
||||
|
||||
left_label = QLabel("Left Eye")
|
||||
right_label = QLabel("Right Eye")
|
||||
layout.addWidget(left_label)
|
||||
layout.addWidget(right_label)
|
||||
|
||||
window.setCentralWidget(central_widget)
|
||||
window.show()
|
||||
|
||||
# 定时更新显示
|
||||
def update_display():
|
||||
try:
|
||||
# 更新左眼纹理
|
||||
if hasattr(world.openxr_manager, 'left_eye_texture') and world.openxr_manager.left_eye_texture:
|
||||
tex = world.openxr_manager.left_eye_texture
|
||||
# 检查纹理尺寸和数据有效性
|
||||
if tex.get_x_size() > 0 and tex.get_y_size() > 0 and tex.might_have_ram_image():
|
||||
ram_image = tex.get_ram_image()
|
||||
if ram_image and ram_image.get_data():
|
||||
# 使用正确的数据访问方法
|
||||
img = QImage(ram_image.get_data(), tex.get_x_size(), tex.get_y_size(), QImage.Format_RGBA8888)
|
||||
pixmap = QPixmap.fromImage(img)
|
||||
left_label.setPixmap(pixmap.scaled(400, 300))
|
||||
left_label.setText("") # 清除文字,只显示图像
|
||||
|
||||
# 更新右眼纹理
|
||||
if hasattr(world.openxr_manager, 'right_eye_texture') and world.openxr_manager.right_eye_texture:
|
||||
tex = world.openxr_manager.right_eye_texture
|
||||
# 检查纹理尺寸和数据有效性
|
||||
if tex.get_x_size() > 0 and tex.get_y_size() > 0 and tex.might_have_ram_image():
|
||||
ram_image = tex.get_ram_image()
|
||||
if ram_image and ram_image.get_data():
|
||||
# 使用正确的数据访问方法
|
||||
img = QImage(ram_image.get_data(), tex.get_x_size(), tex.get_y_size(), QImage.Format_RGBA8888)
|
||||
pixmap = QPixmap.fromImage(img)
|
||||
right_label.setPixmap(pixmap.scaled(400, 300))
|
||||
right_label.setText("") # 清除文字,只显示图像
|
||||
|
||||
except Exception as e:
|
||||
print(f"纹理更新错误: {str(e)}")
|
||||
# 清除可能已损坏的纹理引用
|
||||
if hasattr(world.openxr_manager, 'left_eye_texture'):
|
||||
world.openxr_manager.left_eye_texture = None
|
||||
if hasattr(world.openxr_manager, 'right_eye_texture'):
|
||||
world.openxr_manager.right_eye_texture = None
|
||||
|
||||
timer = QTimer()
|
||||
timer.timeout.connect(update_display)
|
||||
timer.start(33) # 约30 FPS
|
||||
|
||||
# 运行几秒钟
|
||||
print("OpenXR串流运行中... (5秒)")
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 5:
|
||||
# 处理Panda3D事件
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
|
||||
# 处理Qt事件
|
||||
try:
|
||||
app.processEvents()
|
||||
except Exception as e:
|
||||
print(f"Qt事件处理错误: {str(e)}")
|
||||
|
||||
time.sleep(0.016) # ~60 FPS
|
||||
|
||||
# 停止串流
|
||||
world.stopOpenXRStreaming()
|
||||
print("✓ OpenXR串流已停止")
|
||||
else:
|
||||
print("✗ OpenXR串流启动失败")
|
||||
else:
|
||||
print("✗ OpenXR串流初始化失败")
|
||||
|
||||
# 关闭系统
|
||||
world.shutdownOpenXRStreaming()
|
||||
world.stopOpenXRInput()
|
||||
world.shutdownOpenXR()
|
||||
|
||||
print("✓ OpenXR串流测试完成")
|
||||
return True
|
||||
|
||||
|
||||
def test_openxr_full(world):
|
||||
"""测试完整OpenXR功能"""
|
||||
print("=== 完整OpenXR功能测试 ===")
|
||||
|
||||
# 初始化OpenXR系统
|
||||
print("初始化OpenXR系统...")
|
||||
if not world.initializeOpenXR():
|
||||
print("✗ OpenXR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 创建显示窗口
|
||||
app = QApplication.instance()
|
||||
if app is None:
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 创建主窗口显示OpenXR渲染结果
|
||||
window = QMainWindow()
|
||||
window.setWindowTitle("OpenXR Full Functionality Preview")
|
||||
window.setGeometry(100, 100, 800, 300)
|
||||
|
||||
central_widget = QWidget()
|
||||
layout = QHBoxLayout()
|
||||
central_widget.setLayout(layout)
|
||||
|
||||
left_label = QLabel("Left Eye")
|
||||
right_label = QLabel("Right Eye")
|
||||
layout.addWidget(left_label)
|
||||
layout.addWidget(right_label)
|
||||
|
||||
window.setCentralWidget(central_widget)
|
||||
window.show()
|
||||
|
||||
# 定时更新显示
|
||||
def update_display():
|
||||
try:
|
||||
# 更新左眼纹理
|
||||
if hasattr(world.openxr_manager, 'left_eye_texture') and world.openxr_manager.left_eye_texture:
|
||||
tex = world.openxr_manager.left_eye_texture
|
||||
# 检查纹理尺寸和数据有效性
|
||||
if tex.get_x_size() > 0 and tex.get_y_size() > 0 and tex.might_have_ram_image():
|
||||
ram_image = tex.get_ram_image()
|
||||
if ram_image and ram_image.get_data():
|
||||
# 使用正确的数据访问方法
|
||||
img = QImage(ram_image.get_data(), tex.get_x_size(), tex.get_y_size(), QImage.Format_RGBA8888)
|
||||
pixmap = QPixmap.fromImage(img)
|
||||
left_label.setPixmap(pixmap.scaled(400, 300))
|
||||
left_label.setText("") # 清除文字,只显示图像
|
||||
|
||||
# 更新右眼纹理
|
||||
if hasattr(world.openxr_manager, 'right_eye_texture') and world.openxr_manager.right_eye_texture:
|
||||
tex = world.openxr_manager.right_eye_texture
|
||||
# 检查纹理尺寸和数据有效性
|
||||
if tex.get_x_size() > 0 and tex.get_y_size() > 0 and tex.might_have_ram_image():
|
||||
ram_image = tex.get_ram_image()
|
||||
if ram_image and ram_image.get_data():
|
||||
# 使用正确的数据访问方法
|
||||
img = QImage(ram_image.get_data(), tex.get_x_size(), tex.get_y_size(), QImage.Format_RGBA8888)
|
||||
pixmap = QPixmap.fromImage(img)
|
||||
right_label.setPixmap(pixmap.scaled(400, 300))
|
||||
right_label.setText("") # 清除文字,只显示图像
|
||||
|
||||
except Exception as e:
|
||||
print(f"纹理更新错误: {str(e)}")
|
||||
# 清除可能已损坏的纹理引用
|
||||
if hasattr(world.openxr_manager, 'left_eye_texture'):
|
||||
world.openxr_manager.left_eye_texture = None
|
||||
if hasattr(world.openxr_manager, 'right_eye_texture'):
|
||||
world.openxr_manager.right_eye_texture = None
|
||||
|
||||
timer = QTimer()
|
||||
timer.timeout.connect(update_display)
|
||||
timer.start(33) # 约30 FPS
|
||||
|
||||
# 一键启用OpenXR模式
|
||||
print("启用OpenXR模式...")
|
||||
if world.enableOpenXRMode():
|
||||
print("✓ OpenXR模式已启用")
|
||||
|
||||
# 启动输入处理
|
||||
print("启动OpenXR输入处理...")
|
||||
if not world.startOpenXRInput():
|
||||
print("⚠ OpenXR输入处理启动失败")
|
||||
|
||||
# 获取状态
|
||||
openxr_info = world.getOpenXRInfo()
|
||||
print(f"\n📊 OpenXR状态:")
|
||||
print(f" - 启用: {openxr_info['enabled']}")
|
||||
print(f" - 模式: {openxr_info['mode']}")
|
||||
print(f" - 模拟模式: {openxr_info['simulation_mode']}")
|
||||
print(f" - 渲染尺寸: {openxr_info['render_size']}")
|
||||
print(f" - 有线连接: {openxr_info['wired_connection']}")
|
||||
|
||||
# 获取状态
|
||||
status = world.getOpenXRStatus()
|
||||
print(f"\n📊 OpenXR状态:")
|
||||
print(f" - 启用: {status['openxr_enabled']}")
|
||||
print(f" - 连接: {status['openxr_connected']}")
|
||||
print(f" - 串流: {status['openxr_streaming']}")
|
||||
print(f" - 控制器: {len(status['controllers'])}个")
|
||||
|
||||
if status['streaming_status']:
|
||||
streaming_status = status['streaming_status']
|
||||
print(f"\n📡 串流状态:")
|
||||
print(f" - FPS: {streaming_status['fps']}")
|
||||
print(f" - 分辨率: {streaming_status['resolution']}")
|
||||
print(f" - 延迟: {streaming_status['latency']}ms")
|
||||
|
||||
# 运行一段时间
|
||||
print(f"\n🎮 OpenXR完整功能运行中... (15秒)")
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 15:
|
||||
# 处理Panda3D事件
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
|
||||
# 处理Qt事件
|
||||
try:
|
||||
app.processEvents()
|
||||
except Exception as e:
|
||||
print(f"Qt事件处理错误: {str(e)}")
|
||||
|
||||
time.sleep(0.016) # ~60 FPS
|
||||
|
||||
# 禁用OpenXR模式
|
||||
world.shutdownOpenXR()
|
||||
print("✓ OpenXR模式已禁用")
|
||||
|
||||
else:
|
||||
print("✗ OpenXR模式启用失败")
|
||||
return False
|
||||
|
||||
# 关闭系统
|
||||
world.stopOpenXRInput()
|
||||
world.shutdownOpenXR()
|
||||
|
||||
print("✓ 完整OpenXR功能测试完成")
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("OpenXR功能测试")
|
||||
print("===============")
|
||||
|
||||
# 创建世界实例(只创建一次)
|
||||
world = MyWorld()
|
||||
|
||||
# 运行测试
|
||||
print("1. 基本显示测试")
|
||||
test_openxr_basic_display(world)
|
||||
|
||||
print("\n" + "="*50 + "\n")
|
||||
|
||||
print("2. 串流功能测试")
|
||||
test_openxr_streaming(world)
|
||||
|
||||
print("\n" + "="*50 + "\n")
|
||||
|
||||
print("3. 完整功能测试")
|
||||
test_openxr_full(world)
|
||||
|
||||
print("\n" + "="*50)
|
||||
print("所有测试完成!")
|
||||
23
requirements/openxr-requirements.txt
Normal file
23
requirements/openxr-requirements.txt
Normal file
@ -0,0 +1,23 @@
|
||||
# OpenXR 相关依赖
|
||||
# 注意:OpenXR Python绑定可能需要从源码编译
|
||||
|
||||
# 基础依赖
|
||||
numpy>=1.18.0
|
||||
PyOpenGL>=3.1.0
|
||||
|
||||
# Panda3D(如果尚未安装)
|
||||
panda3d>=1.10.10
|
||||
|
||||
# 网络和串流相关
|
||||
websockets>=8.0
|
||||
aiohttp>=3.7.0
|
||||
|
||||
# 图像处理(用于视频编码/解码)
|
||||
Pillow>=8.0.0
|
||||
opencv-python>=4.5.0
|
||||
|
||||
# 音频处理(可选)
|
||||
pyaudio>=0.2.11
|
||||
|
||||
# 注:实际的OpenXR Python绑定需要根据平台和设备单独安装
|
||||
# 可能需要从 https://github.com/KhronosGroup/OpenXR-SDK-Source 编译
|
||||
279
test_openxr_simple.py
Normal file
279
test_openxr_simple.py
Normal file
@ -0,0 +1,279 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
简单的OpenXR测试脚本
|
||||
|
||||
在主程序基础上直接添加OpenXR相关代码进行测试
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from main import MyWorld
|
||||
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QHBoxLayout, QLabel
|
||||
from PyQt5.QtGui import QPixmap, QImage
|
||||
from PyQt5.QtCore import QTimer
|
||||
|
||||
# Panda3D imports
|
||||
from panda3d.core import Texture, GraphicsOutput, ATSNone, GraphicsPipe, Vec3
|
||||
|
||||
|
||||
def setup_openxr_simulation(world):
|
||||
"""在主程序基础上设置简单的OpenXR模拟"""
|
||||
print("🔧 设置OpenXR模拟环境...")
|
||||
|
||||
# 获取渲染尺寸
|
||||
render_width = 1280
|
||||
render_height = 720
|
||||
|
||||
# 创建左右眼渲染缓冲区
|
||||
from panda3d.core import WindowProperties, FrameBufferProperties, GraphicsPipe
|
||||
|
||||
# 创建窗口属性
|
||||
winprops = WindowProperties()
|
||||
winprops.setSize(render_width//2, render_height)
|
||||
|
||||
# 创建帧缓冲区属性
|
||||
fbprops = FrameBufferProperties()
|
||||
fbprops.setRgbColor(True)
|
||||
fbprops.setRgbaBits(8, 8, 8, 8)
|
||||
fbprops.setDepthBits(24)
|
||||
|
||||
# 设置渲染标志和GSG
|
||||
flags = GraphicsPipe.BFRefuseWindow
|
||||
gsg = world.win.getGsg()
|
||||
|
||||
left_eye_buffer = world.graphicsEngine.makeOutput(
|
||||
world.pipe,
|
||||
"left_eye",
|
||||
-1000,
|
||||
fbprops,
|
||||
winprops,
|
||||
flags,
|
||||
gsg,
|
||||
world.win
|
||||
)
|
||||
|
||||
right_eye_buffer = world.graphicsEngine.makeOutput(
|
||||
world.pipe,
|
||||
"right_eye",
|
||||
-1000,
|
||||
fbprops,
|
||||
winprops,
|
||||
flags,
|
||||
gsg,
|
||||
world.win
|
||||
)
|
||||
|
||||
if not left_eye_buffer or not right_eye_buffer:
|
||||
print("❌ 无法创建眼部渲染缓冲区")
|
||||
return None, None
|
||||
|
||||
# 创建用于显示的左右眼纹理,使用与主程序相同的方式
|
||||
left_eye_texture = Texture("left_eye_texture")
|
||||
right_eye_texture = Texture("right_eye_texture")
|
||||
left_eye_texture.set_format(Texture.F_rgba8) # 使用主程序的设置方式
|
||||
right_eye_texture.set_format(Texture.F_rgba8)
|
||||
left_eye_texture.set_component_type(Texture.T_unsigned_byte)
|
||||
right_eye_texture.set_component_type(Texture.T_unsigned_byte)
|
||||
|
||||
# 将纹理附加到渲染缓冲区,使用与主程序相同的方式
|
||||
left_eye_buffer.add_render_texture(left_eye_texture, GraphicsOutput.RTM_copy_ram)
|
||||
right_eye_buffer.add_render_texture(right_eye_texture, GraphicsOutput.RTM_copy_ram)
|
||||
|
||||
# 创建左右眼相机
|
||||
left_eye_camera = world.makeCamera(left_eye_buffer)
|
||||
right_eye_camera = world.makeCamera(right_eye_buffer)
|
||||
|
||||
# 设置相机参数,确保与主相机一致
|
||||
left_eye_camera.node().get_lens().set_fov(world.camLens.get_fov())
|
||||
right_eye_camera.node().get_lens().set_fov(world.camLens.get_fov())
|
||||
left_eye_camera.node().get_lens().set_near(world.camLens.get_near())
|
||||
right_eye_camera.node().get_lens().set_near(world.camLens.get_near())
|
||||
left_eye_camera.node().get_lens().set_far(world.camLens.get_far())
|
||||
right_eye_camera.node().get_lens().set_far(world.camLens.get_far())
|
||||
|
||||
# 设置相机位置(模拟瞳距)
|
||||
left_eye_camera.setPos(world.cam.getPos() + Vec3(-0.03, 0, 0)) # 瞳距的一半
|
||||
right_eye_camera.setPos(world.cam.getPos() + Vec3(0.03, 0, 0)) # 瞳距的一半
|
||||
|
||||
# 设置相机朝向,确保与主相机一致
|
||||
left_eye_camera.setHpr(world.cam.getHpr())
|
||||
right_eye_camera.setHpr(world.cam.getHpr())
|
||||
|
||||
# 设置相机渲染场景
|
||||
left_eye_camera.node().setScene(world.render)
|
||||
right_eye_camera.node().setScene(world.render)
|
||||
|
||||
# 获取渲染管线的最终输出纹理
|
||||
def get_final_render_texture():
|
||||
"""获取渲染管线的最终输出纹理"""
|
||||
# 修复:正确获取最终渲染纹理的方法
|
||||
if world.win:
|
||||
# 直接从主窗口获取最终渲染纹理
|
||||
return world.win.get_texture()
|
||||
return None
|
||||
|
||||
# 等待并获取渲染结果
|
||||
def wait_for_render_texture():
|
||||
"""等待并获取有效的渲染纹理"""
|
||||
timeout = 100 # 超时帧数
|
||||
for i in range(timeout):
|
||||
final_texture = get_final_render_texture()
|
||||
if final_texture and final_texture.hasRamImage():
|
||||
return final_texture
|
||||
world.graphicsEngine.renderFrame()
|
||||
return None
|
||||
|
||||
# 获取最终渲染纹理作为左右眼纹理的基础
|
||||
final_texture = wait_for_render_texture()
|
||||
if final_texture:
|
||||
# 使用与主渲染管线相同的纹理参数
|
||||
left_eye_texture = final_texture.makeCopy()
|
||||
right_eye_texture = final_texture.makeCopy()
|
||||
|
||||
# 确保纹理数据可用
|
||||
world.graphicsEngine.extractTextureData(left_eye_texture, world.win.getGsg())
|
||||
world.graphicsEngine.extractTextureData(right_eye_texture, world.win.getGsg())
|
||||
|
||||
print("✅ 成功获取渲染管线的最终输出纹理")
|
||||
else:
|
||||
print("⚠️ 无法获取渲染管线的最终输出纹理,使用默认设置")
|
||||
|
||||
print("✅ OpenXR模拟环境设置完成")
|
||||
return left_eye_texture, right_eye_texture
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 启动OpenXR简单测试...")
|
||||
|
||||
# 创建应用程序和世界实例
|
||||
app = QApplication(sys.argv)
|
||||
world = MyWorld()
|
||||
|
||||
# 加载一些测试模型
|
||||
print("📦 加载测试场景...")
|
||||
from panda3d.core import CardMaker
|
||||
|
||||
# 创建地面
|
||||
cm = CardMaker("ground")
|
||||
cm.setFrame(-5, 5, -5, 5)
|
||||
ground = world.render.attachNewNode(cm.generate())
|
||||
ground.setPos(0, 0, -1)
|
||||
ground.setColor(0.5, 0.8, 0.5, 1.0)
|
||||
ground.setHpr(0, -90, 0)
|
||||
|
||||
# 创建测试立方体
|
||||
cube = world.loader.loadModel("models/environment")
|
||||
if cube:
|
||||
cube.reparentTo(world.render)
|
||||
cube.setScale(0.5)
|
||||
cube.setPos(0, 0, 0)
|
||||
else:
|
||||
# 如果没有环境模型,创建简单立方体
|
||||
cm_cube = CardMaker("cube")
|
||||
cm_cube.setFrame(-0.5, 0.5, -0.5, 0.5)
|
||||
cube = world.render.attachNewNode(cm_cube.generate())
|
||||
cube.setPos(0, 0, 0)
|
||||
|
||||
# 设置OpenXR模拟
|
||||
left_tex, right_tex = setup_openxr_simulation(world)
|
||||
|
||||
if not left_tex or not right_tex:
|
||||
print("❌ OpenXR模拟设置失败")
|
||||
return
|
||||
|
||||
# 创建显示窗口
|
||||
window = QMainWindow()
|
||||
window.setWindowTitle("OpenXR Simple Test")
|
||||
window.setGeometry(100, 100, 800, 300)
|
||||
|
||||
central_widget = QWidget()
|
||||
layout = QHBoxLayout()
|
||||
central_widget.setLayout(layout)
|
||||
|
||||
left_label = QLabel("Left Eye")
|
||||
right_label = QLabel("Right Eye")
|
||||
layout.addWidget(left_label)
|
||||
layout.addWidget(right_label)
|
||||
|
||||
window.setCentralWidget(central_widget)
|
||||
window.show()
|
||||
|
||||
def update_display():
|
||||
"""更新显示"""
|
||||
try:
|
||||
# 让RenderPipeline进行渲染
|
||||
world.taskMgr.step()
|
||||
|
||||
# 渲染几帧以确保纹理更新
|
||||
for _ in range(5):
|
||||
world.graphicsEngine.renderFrame()
|
||||
|
||||
# 更新左眼纹理
|
||||
if left_tex:
|
||||
# 检查纹理是否具有RAM图像
|
||||
if not left_tex.hasRamImage():
|
||||
# 如果没有RAM图像,尝试提取
|
||||
gsg = world.win.getGsg()
|
||||
if gsg:
|
||||
world.graphicsEngine.extractTextureData(left_tex, gsg)
|
||||
|
||||
# 现在检查是否有RAM图像和有效数据
|
||||
if left_tex.hasRamImage():
|
||||
data = left_tex.getRamImage().getData()
|
||||
width = left_tex.getXSize()
|
||||
height = left_tex.getYSize()
|
||||
expected_len = width * height * 4
|
||||
|
||||
# 检查数据长度是否正确
|
||||
if len(data) == expected_len:
|
||||
# 使用与主程序相同的方式创建图像
|
||||
img = QImage(data, width, height, QImage.Format_ARGB32).mirrored()
|
||||
pixmap = QPixmap.fromImage(img)
|
||||
left_label.setPixmap(pixmap.scaled(400, 300))
|
||||
left_label.setText("")
|
||||
|
||||
# 更新右眼纹理
|
||||
if right_tex:
|
||||
# 检查纹理是否具有RAM图像
|
||||
if not right_tex.hasRamImage():
|
||||
# 如果没有RAM图像,尝试提取
|
||||
gsg = world.win.getGsg()
|
||||
if gsg:
|
||||
world.graphicsEngine.extractTextureData(right_tex, gsg)
|
||||
|
||||
# 现在检查是否有RAM图像和有效数据
|
||||
if right_tex.hasRamImage():
|
||||
data = right_tex.getRamImage().getData()
|
||||
width = right_tex.getXSize()
|
||||
height = right_tex.getYSize()
|
||||
expected_len = width * height * 4
|
||||
|
||||
# 检查数据长度是否正确
|
||||
if len(data) == expected_len:
|
||||
# 使用与主程序相同的方式创建图像
|
||||
img = QImage(data, width, height, QImage.Format_ARGB32).mirrored()
|
||||
pixmap = QPixmap.fromImage(img)
|
||||
right_label.setPixmap(pixmap.scaled(400, 300))
|
||||
right_label.setText("")
|
||||
except Exception as e:
|
||||
print(f"纹理更新错误: {str(e)}")
|
||||
|
||||
# 定时更新显示
|
||||
timer = QTimer()
|
||||
timer.timeout.connect(update_display)
|
||||
timer.start(33) # 约30 FPS
|
||||
|
||||
# 运行应用程序
|
||||
print("✅ OpenXR简单测试运行中...")
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -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()
|
||||
@ -1,11 +0,0 @@
|
||||
"""
|
||||
VR系统模块 - 提供VR设备集成和画面串流功能
|
||||
|
||||
此模块提供了一个完整的VR系统实现,包括:
|
||||
- VR设备初始化和管理
|
||||
- 立体渲染和显示
|
||||
- VR输入处理
|
||||
- 画面串流到VR设备
|
||||
"""
|
||||
|
||||
from .vr_system import VRSystem
|
||||
@ -1,8 +0,0 @@
|
||||
"""
|
||||
VR设备特定实现模块
|
||||
|
||||
此模块包含针对不同VR设备的特定实现:
|
||||
- OpenXR设备
|
||||
- Oculus设备
|
||||
- SteamVR设备
|
||||
"""
|
||||
379
vr/vr_display.py
379
vr/vr_display.py
@ -1,379 +0,0 @@
|
||||
"""
|
||||
VR显示管理模块
|
||||
|
||||
此模块负责VR立体渲染和显示,包括:
|
||||
- 创建左右眼相机
|
||||
- 设置渲染目标
|
||||
- 应用畸变校正
|
||||
- 管理渲染分辨率和质量
|
||||
"""
|
||||
|
||||
from panda3d.core import (
|
||||
NodePath, Camera, PerspectiveLens,
|
||||
FrameBufferProperties, WindowProperties,
|
||||
GraphicsOutput, GraphicsEngine,
|
||||
Texture, GraphicsBuffer, Vec3,
|
||||
RenderState, ColorWriteAttrib, ShaderAttrib
|
||||
)
|
||||
from direct.filter.FilterManager import FilterManager
|
||||
|
||||
class VRDisplay:
|
||||
"""管理VR显示和渲染"""
|
||||
|
||||
def __init__(self, vr_system):
|
||||
"""
|
||||
初始化VR显示管理器
|
||||
|
||||
Args:
|
||||
vr_system: VR系统实例
|
||||
"""
|
||||
self.vr_system = vr_system
|
||||
self.world = vr_system.world
|
||||
self.device = vr_system.device
|
||||
|
||||
# 渲染目标
|
||||
self.left_buffer = None
|
||||
self.right_buffer = None
|
||||
self.left_texture = None
|
||||
self.right_texture = None
|
||||
|
||||
# 相机
|
||||
self.left_camera = None
|
||||
self.right_camera = None
|
||||
self.left_cam_node = None
|
||||
self.right_cam_node = None
|
||||
|
||||
# 畸变校正
|
||||
self.distortion_manager = None
|
||||
self.is_initialized = False
|
||||
|
||||
# 渲染设置
|
||||
self.render_resolution = vr_system.config.get("render_resolution", [2048, 2048])
|
||||
self.ipd = vr_system.config.get("ipd", 0.064) # 默认瞳距64mm
|
||||
|
||||
def initialize(self):
|
||||
"""初始化VR显示系统"""
|
||||
print("正在初始化VR显示系统...")
|
||||
|
||||
# 1. 创建渲染目标
|
||||
if not self._create_render_targets():
|
||||
print("✗ 创建VR渲染目标失败")
|
||||
return False
|
||||
|
||||
# 2. 创建立体相机
|
||||
if not self._setup_stereo_cameras():
|
||||
print("✗ 设置VR立体相机失败")
|
||||
return False
|
||||
|
||||
# 3. 设置畸变校正
|
||||
if not self._setup_distortion_correction():
|
||||
print("✗ 设置畸变校正失败")
|
||||
# 这不是致命错误,可以继续
|
||||
print("⚠ 将使用未校正的渲染")
|
||||
|
||||
self.is_initialized = True
|
||||
print("✓ VR显示系统初始化完成")
|
||||
return True
|
||||
|
||||
def _create_render_targets(self):
|
||||
"""创建VR渲染目标"""
|
||||
try:
|
||||
# 获取图形引擎和窗口
|
||||
graphics_engine = self.world.graphicsEngine
|
||||
main_window = self.world.win
|
||||
|
||||
# 创建帧缓冲属性
|
||||
fb_props = FrameBufferProperties()
|
||||
fb_props.setRgbColor(True)
|
||||
fb_props.setRgbaBits(8, 8, 8, 8)
|
||||
fb_props.setDepthBits(24)
|
||||
fb_props.setMultisamples(4) # MSAA 4x
|
||||
|
||||
# 创建窗口属性
|
||||
win_props = WindowProperties()
|
||||
win_props.setSize(self.render_resolution[0], self.render_resolution[1])
|
||||
|
||||
# 创建左眼缓冲区
|
||||
self.left_buffer = graphics_engine.makeOutput(
|
||||
main_window.getPipe(), "left_eye_buffer",
|
||||
-1, fb_props, win_props,
|
||||
GraphicsOutput.BF_refuse_window,
|
||||
main_window.getGsg(), main_window
|
||||
)
|
||||
|
||||
if not self.left_buffer:
|
||||
print("✗ 创建左眼缓冲区失败")
|
||||
return False
|
||||
|
||||
self.left_buffer.setClearColor((0, 0, 0, 1))
|
||||
self.left_texture = Texture()
|
||||
self.left_buffer.addRenderTexture(
|
||||
self.left_texture,
|
||||
GraphicsOutput.RTM_copy_ram,
|
||||
GraphicsOutput.RTP_color
|
||||
)
|
||||
|
||||
# 创建右眼缓冲区
|
||||
self.right_buffer = graphics_engine.makeOutput(
|
||||
main_window.getPipe(), "right_eye_buffer",
|
||||
-1, fb_props, win_props,
|
||||
GraphicsOutput.BF_refuse_window,
|
||||
main_window.getGsg(), main_window
|
||||
)
|
||||
|
||||
if not self.right_buffer:
|
||||
print("✗ 创建右眼缓冲区失败")
|
||||
return False
|
||||
|
||||
self.right_buffer.setClearColor((0, 0, 0, 1))
|
||||
self.right_texture = Texture()
|
||||
self.right_buffer.addRenderTexture(
|
||||
self.right_texture,
|
||||
GraphicsOutput.RTM_copy_ram,
|
||||
GraphicsOutput.RTP_color
|
||||
)
|
||||
|
||||
print("✓ VR渲染目标创建成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"创建VR渲染目标时出错: {e}")
|
||||
return False
|
||||
|
||||
def _setup_stereo_cameras(self):
|
||||
"""设置立体相机"""
|
||||
try:
|
||||
# 获取主相机信息
|
||||
main_cam = self.world.camera
|
||||
main_cam_pos = main_cam.getPos()
|
||||
main_cam_hpr = main_cam.getHpr()
|
||||
|
||||
# 创建左眼相机
|
||||
self.left_cam_node = Camera("left_eye_camera")
|
||||
left_lens = PerspectiveLens()
|
||||
left_lens.setFov(90) # 90度视场角
|
||||
left_lens.setNearFar(0.01, 1000)
|
||||
self.left_cam_node.setLens(left_lens)
|
||||
self.left_camera = NodePath(self.left_cam_node)
|
||||
self.left_camera.reparentTo(main_cam)
|
||||
|
||||
# 设置左眼相机位置(向左偏移半个瞳距)
|
||||
self.left_camera.setPos(-self.ipd/2, 0, 0)
|
||||
|
||||
# 创建右眼相机
|
||||
self.right_cam_node = Camera("right_eye_camera")
|
||||
right_lens = PerspectiveLens()
|
||||
right_lens.setFov(90) # 90度视场角
|
||||
right_lens.setNearFar(0.01, 1000)
|
||||
self.right_cam_node.setLens(right_lens)
|
||||
self.right_camera = NodePath(self.right_cam_node)
|
||||
self.right_camera.reparentTo(main_cam)
|
||||
|
||||
# 设置右眼相机位置(向右偏移半个瞳距)
|
||||
self.right_camera.setPos(self.ipd/2, 0, 0)
|
||||
|
||||
# 设置相机显示区域
|
||||
self.left_cam_node.setScene(self.world.render)
|
||||
self.right_cam_node.setScene(self.world.render)
|
||||
|
||||
# 连接相机到缓冲区
|
||||
self.left_buffer.setSort(0)
|
||||
self.left_buffer.setClearActive(True)
|
||||
self.left_buffer.setActive(True)
|
||||
self.left_buffer.addDisplayRegion().setCamera(self.left_camera)
|
||||
|
||||
self.right_buffer.setSort(0)
|
||||
self.right_buffer.setClearActive(True)
|
||||
self.right_buffer.setActive(True)
|
||||
self.right_buffer.addDisplayRegion().setCamera(self.right_camera)
|
||||
|
||||
print("✓ VR立体相机设置成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"设置VR立体相机时出错: {e}")
|
||||
return False
|
||||
|
||||
def _setup_distortion_correction(self):
|
||||
"""设置畸变校正"""
|
||||
try:
|
||||
# 如果设备提供了畸变校正参数
|
||||
if self.device and hasattr(self.device, 'get_distortion_parameters'):
|
||||
distortion_params = self.device.get_distortion_parameters()
|
||||
|
||||
# 创建畸变校正着色器
|
||||
# 这里使用FilterManager来应用后处理效果
|
||||
self.distortion_manager = FilterManager(self.world.win, self.world.cam)
|
||||
|
||||
# 应用畸变校正着色器到左右眼纹理
|
||||
# 具体实现取决于设备提供的畸变参数格式
|
||||
|
||||
print("✓ VR畸变校正设置成功")
|
||||
return True
|
||||
else:
|
||||
print("⚠ 设备未提供畸变校正参数")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"设置畸变校正时出错: {e}")
|
||||
return False
|
||||
|
||||
def update(self):
|
||||
"""更新VR显示(每帧调用)"""
|
||||
if not self.is_initialized:
|
||||
return
|
||||
|
||||
# 1. 获取头部姿态
|
||||
if self.device:
|
||||
head_pose = self.device.get_head_pose()
|
||||
if head_pose:
|
||||
self.update_cameras(head_pose)
|
||||
|
||||
# 2. 如果使用动态分辨率,根据性能调整
|
||||
if self.vr_system.config.get("performance", {}).get("dynamic_resolution", False):
|
||||
self._update_dynamic_resolution()
|
||||
|
||||
def update_cameras(self, head_pose):
|
||||
"""
|
||||
根据头部姿态更新相机位置
|
||||
|
||||
Args:
|
||||
head_pose: 包含位置和旋转的头部姿态数据
|
||||
"""
|
||||
if not self.is_initialized:
|
||||
return
|
||||
|
||||
# 获取主相机
|
||||
main_cam = self.world.camera
|
||||
|
||||
# 应用头部位置和旋转
|
||||
if 'position' in head_pose:
|
||||
main_cam.setPos(Vec3(*head_pose['position']))
|
||||
|
||||
if 'rotation' in head_pose:
|
||||
main_cam.setHpr(Vec3(*head_pose['rotation']))
|
||||
|
||||
def render_frame(self):
|
||||
"""渲染一帧VR画面"""
|
||||
if not self.is_initialized:
|
||||
return None, None
|
||||
|
||||
# 强制渲染左右眼缓冲区
|
||||
self.world.graphicsEngine.renderFrame()
|
||||
|
||||
# 获取渲染结果
|
||||
left_image = self.left_texture.getRamImage()
|
||||
right_image = self.right_texture.getRamImage()
|
||||
|
||||
return left_image, right_image
|
||||
|
||||
def shutdown(self):
|
||||
"""关闭VR显示系统"""
|
||||
if not self.is_initialized:
|
||||
return True
|
||||
|
||||
print("正在关闭VR显示系统...")
|
||||
|
||||
# 移除畸变校正
|
||||
if self.distortion_manager:
|
||||
self.distortion_manager.cleanup()
|
||||
self.distortion_manager = None
|
||||
|
||||
# 移除相机
|
||||
if self.left_camera:
|
||||
self.left_camera.removeNode()
|
||||
self.left_camera = None
|
||||
|
||||
if self.right_camera:
|
||||
self.right_camera.removeNode()
|
||||
self.right_camera = None
|
||||
|
||||
# 移除缓冲区
|
||||
if self.left_buffer:
|
||||
self.world.graphicsEngine.removeWindow(self.left_buffer)
|
||||
self.left_buffer = None
|
||||
|
||||
if self.right_buffer:
|
||||
self.world.graphicsEngine.removeWindow(self.right_buffer)
|
||||
self.right_buffer = None
|
||||
|
||||
self.is_initialized = False
|
||||
print("✓ VR显示系统已关闭")
|
||||
return True
|
||||
|
||||
def set_resolution(self, width, height):
|
||||
"""
|
||||
设置VR渲染分辨率
|
||||
|
||||
Args:
|
||||
width: 宽度(像素)
|
||||
height: 高度(像素)
|
||||
"""
|
||||
if not self.is_initialized:
|
||||
print("VR显示系统未初始化,无法设置分辨率")
|
||||
return False
|
||||
|
||||
try:
|
||||
self.render_resolution = [width, height]
|
||||
|
||||
# 需要重新创建缓冲区
|
||||
self.shutdown()
|
||||
self.initialize()
|
||||
|
||||
print(f"✓ VR渲染分辨率已设置为 {width}x{height}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"设置VR渲染分辨率时出错: {e}")
|
||||
return False
|
||||
|
||||
def set_ipd(self, ipd):
|
||||
"""
|
||||
设置瞳距
|
||||
|
||||
Args:
|
||||
ipd: 瞳距(米)
|
||||
"""
|
||||
if not self.is_initialized:
|
||||
print("VR显示系统未初始化,无法设置瞳距")
|
||||
return False
|
||||
|
||||
try:
|
||||
self.ipd = ipd
|
||||
|
||||
# 更新相机位置
|
||||
if self.left_camera and self.right_camera:
|
||||
self.left_camera.setPos(-self.ipd/2, 0, 0)
|
||||
self.right_camera.setPos(self.ipd/2, 0, 0)
|
||||
|
||||
print(f"✓ VR瞳距已设置为 {ipd}m")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"设置VR瞳距时出错: {e}")
|
||||
return False
|
||||
|
||||
def _update_dynamic_resolution(self):
|
||||
"""根据性能动态调整渲染分辨率"""
|
||||
# 获取当前帧率
|
||||
clock = self.world.globalClock
|
||||
fps = clock.getAverageFrameRate()
|
||||
|
||||
# 目标帧率
|
||||
target_fps = self.vr_system.config.get("refresh_rate", 90)
|
||||
|
||||
# 如果帧率低于目标的90%,降低分辨率
|
||||
if fps < target_fps * 0.9:
|
||||
current_width, current_height = self.render_resolution
|
||||
new_width = int(current_width * 0.9)
|
||||
new_height = int(current_height * 0.9)
|
||||
|
||||
# 设置最低分辨率限制
|
||||
if new_width >= 1024 and new_height >= 1024:
|
||||
self.set_resolution(new_width, new_height)
|
||||
|
||||
# 如果帧率高于目标的110%,提高分辨率
|
||||
elif fps > target_fps * 1.1:
|
||||
current_width, current_height = self.render_resolution
|
||||
new_width = int(current_width * 1.1)
|
||||
new_height = int(current_height * 1.1)
|
||||
|
||||
# 设置最高分辨率限制
|
||||
if new_width <= 4096 and new_height <= 4096:
|
||||
self.set_resolution(new_width, new_height)
|
||||
|
||||
363
vr/vr_input.py
363
vr/vr_input.py
@ -1,363 +0,0 @@
|
||||
"""
|
||||
VR输入处理模块
|
||||
|
||||
此模块负责处理VR输入设备,包括:
|
||||
- 控制器跟踪
|
||||
- 按钮和触摸板输入
|
||||
- 手势识别
|
||||
- 触觉反馈
|
||||
"""
|
||||
|
||||
from panda3d.core import NodePath, LineSegs, Vec3, Vec4
|
||||
from panda3d.core import CollisionRay, CollisionNode, CollisionTraverser, CollisionHandlerQueue
|
||||
from panda3d.core import BitMask32
|
||||
from direct.task import Task
|
||||
import math
|
||||
import time
|
||||
|
||||
class VRInput:
|
||||
"""处理VR输入设备"""
|
||||
|
||||
def __init__(self, vr_system):
|
||||
"""
|
||||
初始化VR输入处理器
|
||||
|
||||
Args:
|
||||
vr_system: VR系统实例
|
||||
"""
|
||||
self.vr_system = vr_system
|
||||
self.world = vr_system.world
|
||||
self.device = vr_system.device
|
||||
|
||||
# 控制器
|
||||
self.controllers = {}
|
||||
self.controller_models = {}
|
||||
self.controller_rays = {}
|
||||
|
||||
# 输入状态
|
||||
self.button_states = {}
|
||||
self.analog_states = {}
|
||||
self.last_button_states = {}
|
||||
|
||||
# 配置
|
||||
self.show_rays = True
|
||||
self.ray_length = 100.0
|
||||
self.ray_color = Vec4(0, 0.5, 1, 0.8)
|
||||
|
||||
# 功能标志
|
||||
self.gesture_enabled = True
|
||||
self.interaction_enabled = True
|
||||
self.haptic_enabled = True
|
||||
|
||||
# 状态
|
||||
self.is_initialized = False
|
||||
self.tracking_origin = None
|
||||
|
||||
# 碰撞检测
|
||||
self.traverser = CollisionTraverser("vr_traverser")
|
||||
self.queue = CollisionHandlerQueue()
|
||||
|
||||
def initialize(self):
|
||||
"""初始化VR输入系统"""
|
||||
print("正在初始化VR输入系统...")
|
||||
|
||||
if not self.device:
|
||||
print("✗ VR设备未初始化,无法设置输入")
|
||||
return False
|
||||
|
||||
# 1. 设置跟踪原点
|
||||
self.tracking_origin = self.world.render.attachNewNode("vr_tracking_origin")
|
||||
|
||||
# 2. 设置控制器
|
||||
if not self._setup_controllers():
|
||||
print("✗ 设置VR控制器失败")
|
||||
# 这不是致命错误,可以继续
|
||||
print("⚠ VR将在没有控制器的情况下运行")
|
||||
|
||||
# 3. 定义输入动作
|
||||
self._define_actions()
|
||||
|
||||
# 4. 创建控制器射线
|
||||
if self.show_rays:
|
||||
self._create_controller_rays()
|
||||
|
||||
self.is_initialized = True
|
||||
print("✓ VR输入系统初始化完成")
|
||||
return True
|
||||
|
||||
def _setup_controllers(self):
|
||||
"""设置控制器跟踪"""
|
||||
try:
|
||||
# 获取设备控制器信息
|
||||
if hasattr(self.device, 'get_controllers'):
|
||||
controllers_info = self.device.get_controllers()
|
||||
|
||||
if not controllers_info:
|
||||
print("⚠ 未检测到VR控制器")
|
||||
return False
|
||||
|
||||
# 为每个控制器创建节点
|
||||
for controller_id, info in controllers_info.items():
|
||||
controller_node = self.tracking_origin.attachNewNode(f"controller_{controller_id}")
|
||||
|
||||
# 加载控制器模型(如果有)
|
||||
model_path = info.get('model_path')
|
||||
if model_path:
|
||||
try:
|
||||
controller_model = self.world.loader.loadModel(model_path)
|
||||
controller_model.reparentTo(controller_node)
|
||||
self.controller_models[controller_id] = controller_model
|
||||
except Exception as e:
|
||||
print(f"加载控制器模型失败: {e}")
|
||||
# 使用默认模型
|
||||
self._create_default_controller_model(controller_node, controller_id)
|
||||
else:
|
||||
# 使用默认模型
|
||||
self._create_default_controller_model(controller_node, controller_id)
|
||||
|
||||
# 存储控制器节点
|
||||
self.controllers[controller_id] = controller_node
|
||||
|
||||
# 初始化按钮状态
|
||||
self.button_states[controller_id] = {}
|
||||
self.analog_states[controller_id] = {}
|
||||
self.last_button_states[controller_id] = {}
|
||||
|
||||
print(f"✓ 已设置 {len(self.controllers)} 个VR控制器")
|
||||
return True
|
||||
else:
|
||||
print("⚠ VR设备不支持控制器跟踪")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"设置VR控制器时出错: {e}")
|
||||
return False
|
||||
|
||||
def _create_default_controller_model(self, parent_node, controller_id):
|
||||
"""创建默认控制器模型"""
|
||||
from panda3d.core import GeomVertexFormat, GeomVertexData, Geom, GeomTriangles
|
||||
from panda3d.core import GeomVertexWriter, GeomNode
|
||||
|
||||
# 创建简单的控制器几何体
|
||||
format = GeomVertexFormat.getV3n3c4()
|
||||
vdata = GeomVertexData('controller', format, Geom.UHStatic)
|
||||
|
||||
vertex = GeomVertexWriter(vdata, 'vertex')
|
||||
normal = GeomVertexWriter(vdata, 'normal')
|
||||
color = GeomVertexWriter(vdata, 'color')
|
||||
|
||||
# 简单的手柄形状
|
||||
# 顶部
|
||||
vertex.addData3f(0, 0, 0.1)
|
||||
normal.addData3f(0, 0, 1)
|
||||
color.addData4f(0.8, 0.8, 0.8, 1)
|
||||
|
||||
# 底部圆形的顶点
|
||||
radius = 0.03
|
||||
segments = 8
|
||||
for i in range(segments):
|
||||
angle = 2.0 * math.pi * i / segments
|
||||
x = radius * math.cos(angle)
|
||||
y = radius * math.sin(angle)
|
||||
vertex.addData3f(x, y, -0.1)
|
||||
normal.addData3f(x, y, 0)
|
||||
color.addData4f(0.5, 0.5, 0.5, 1)
|
||||
|
||||
# 创建三角形
|
||||
tris = GeomTriangles(Geom.UHStatic)
|
||||
|
||||
# 连接顶部到圆形底部
|
||||
for i in range(segments):
|
||||
tris.addVertices(0, i + 1, (i + 1) % segments + 1)
|
||||
tris.closePrimitive()
|
||||
|
||||
# 创建几何体
|
||||
geom = Geom(vdata)
|
||||
geom.addPrimitive(tris)
|
||||
|
||||
# 创建GeomNode
|
||||
node = GeomNode(f'controller_{controller_id}_geom')
|
||||
node.addGeom(geom)
|
||||
|
||||
# 创建NodePath并附加到父节点
|
||||
controller_model = NodePath(node)
|
||||
controller_model.reparentTo(parent_node)
|
||||
|
||||
# 存储模型引用
|
||||
self.controller_models[controller_id] = controller_model
|
||||
|
||||
def _define_actions(self):
|
||||
"""定义输入动作映射"""
|
||||
# 这里可以定义按钮到动作的映射
|
||||
self.input_actions = {
|
||||
"trigger": {
|
||||
"description": "触发器",
|
||||
"button": "trigger",
|
||||
"action": self._on_trigger
|
||||
},
|
||||
"grip": {
|
||||
"description": "握把",
|
||||
"button": "grip",
|
||||
"action": self._on_grip
|
||||
},
|
||||
"menu": {
|
||||
"description": "菜单按钮",
|
||||
"button": "menu",
|
||||
"action": self._on_menu
|
||||
},
|
||||
"thumbstick_press": {
|
||||
"description": "摇杆按下",
|
||||
"button": "thumbstick",
|
||||
"action": self._on_thumbstick_press
|
||||
},
|
||||
"teleport": {
|
||||
"description": "传送",
|
||||
"button": "thumbstick",
|
||||
"analog": True,
|
||||
"direction": "up",
|
||||
"action": self._on_teleport
|
||||
}
|
||||
}
|
||||
|
||||
def _create_controller_rays(self):
|
||||
"""创建控制器射线可视化"""
|
||||
for controller_id, controller in self.controllers.items():
|
||||
# 创建射线线段
|
||||
ls = LineSegs()
|
||||
ls.setThickness(2.0)
|
||||
ls.setColor(self.ray_color)
|
||||
ls.moveTo(0, 0, 0)
|
||||
ls.drawTo(0, self.ray_length, 0)
|
||||
|
||||
# 创建射线节点
|
||||
ray_node = ls.create()
|
||||
ray_path = NodePath(ray_node)
|
||||
ray_path.reparentTo(controller)
|
||||
|
||||
# 默认隐藏
|
||||
ray_path.hide()
|
||||
|
||||
# 存储射线引用
|
||||
self.controller_rays[controller_id] = ray_path
|
||||
|
||||
def update(self):
|
||||
"""更新控制器状态(每帧调用)"""
|
||||
if not self.is_initialized:
|
||||
return
|
||||
|
||||
# 1. 更新控制器位置和旋转
|
||||
self._update_controller_poses()
|
||||
|
||||
# 2. 更新按钮状态
|
||||
self._update_button_states()
|
||||
|
||||
# 3. 处理输入事件
|
||||
self._process_input()
|
||||
|
||||
# 4. 更新射线(如果启用)
|
||||
if self.show_rays:
|
||||
self._update_controller_rays()
|
||||
|
||||
def _update_controller_poses(self):
|
||||
"""更新控制器位置和旋转"""
|
||||
if not self.device or not hasattr(self.device, 'get_controller_poses'):
|
||||
return
|
||||
|
||||
# 获取所有控制器的姿态
|
||||
poses = self.device.get_controller_poses()
|
||||
if not poses:
|
||||
return
|
||||
|
||||
# 更新每个控制器的位置和旋转
|
||||
for controller_id, pose in poses.items():
|
||||
if controller_id in self.controllers:
|
||||
controller = self.controllers[controller_id]
|
||||
|
||||
if 'position' in pose:
|
||||
controller.setPos(Vec3(*pose['position']))
|
||||
|
||||
if 'rotation' in pose:
|
||||
controller.setHpr(Vec3(*pose['rotation']))
|
||||
|
||||
def _update_button_states(self):
|
||||
"""更新按钮状态"""
|
||||
if not self.device or not hasattr(self.device, 'get_controller_inputs'):
|
||||
return
|
||||
|
||||
# 获取所有控制器的输入状态
|
||||
inputs = self.device.get_controller_inputs()
|
||||
if not inputs:
|
||||
return
|
||||
|
||||
# 更新每个控制器的按钮和模拟输入状态
|
||||
for controller_id, input_data in inputs.items():
|
||||
if controller_id in self.controllers:
|
||||
# 保存上一帧的按钮状态
|
||||
self.last_button_states[controller_id] = self.button_states[controller_id].copy()
|
||||
|
||||
# 更新按钮状态
|
||||
if 'buttons' in input_data:
|
||||
self.button_states[controller_id] = input_data['buttons']
|
||||
|
||||
# 更新模拟输入状态
|
||||
if 'analogs' in input_data:
|
||||
self.analog_states[controller_id] = input_data['analogs']
|
||||
|
||||
def _process_input(self):
|
||||
"""处理输入事件"""
|
||||
if not self.interaction_enabled:
|
||||
return
|
||||
|
||||
# 检查每个定义的动作
|
||||
for action_name, action_def in self.input_actions.items():
|
||||
button = action_def.get('button')
|
||||
|
||||
for controller_id in self.controllers:
|
||||
# 处理按钮动作
|
||||
if not action_def.get('analog', False):
|
||||
# 检查按钮是否刚被按下
|
||||
if (button in self.button_states.get(controller_id, {}) and
|
||||
button in self.last_button_states.get(controller_id, {}) and
|
||||
self.button_states[controller_id][button] and
|
||||
not self.last_button_states[controller_id][button]):
|
||||
|
||||
# 调用动作处理函数
|
||||
if 'action' in action_def:
|
||||
action_def['action'](controller_id)
|
||||
|
||||
# 处理模拟输入动作
|
||||
else:
|
||||
if button in self.analog_states.get(controller_id, {}):
|
||||
analog_value = self.analog_states[controller_id][button]
|
||||
direction = action_def.get('direction')
|
||||
|
||||
# 根据方向检查模拟输入
|
||||
if direction == 'up' and analog_value[1] > 0.7:
|
||||
if 'action' in action_def:
|
||||
action_def['action'](controller_id)
|
||||
elif direction == 'down' and analog_value[1] < -0.7:
|
||||
if 'action' in action_def:
|
||||
action_def['action'](controller_id)
|
||||
elif direction == 'left' and analog_value[0] < -0.7:
|
||||
if 'action' in action_def:
|
||||
action_def['action'](controller_id)
|
||||
elif direction == 'right' and analog_value[0] > 0.7:
|
||||
if 'action' in action_def:
|
||||
action_def['action'](controller_id)
|
||||
|
||||
def _update_controller_rays(self):
|
||||
"""更新控制器射线可视化"""
|
||||
for controller_id, ray in self.controller_rays.items():
|
||||
if self.show_rays:
|
||||
ray.show()
|
||||
|
||||
# 执行射线检测
|
||||
if controller_id in self.controllers:
|
||||
controller = self.controllers[controller_id]
|
||||
origin = controller.getPos(self.world.render)
|
||||
direction = Vec3(0, 1, 0) # 控制器前方
|
||||
direction = self.world.render.getRelativeVector(controller, direction)
|
||||
|
||||
# 射线检测
|
||||
result = self._perform_ray_test(origin, direction)
|
||||
|
||||
#
|
||||
380
vr/vr_system.py
380
vr/vr_system.py
@ -1,380 +0,0 @@
|
||||
"""
|
||||
VR系统核心管理模块
|
||||
|
||||
此模块提供VR系统的核心功能,负责初始化和协调其他VR组件。
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from panda3d.core import loadPrcFileData, WindowProperties
|
||||
from direct.task import Task
|
||||
|
||||
class VRSystem:
|
||||
"""VR系统的核心管理类,负责初始化和协调其他VR组件"""
|
||||
|
||||
def __init__(self, world):
|
||||
"""
|
||||
初始化VR系统
|
||||
|
||||
Args:
|
||||
world: 游戏世界实例,提供对渲染器和场景的访问
|
||||
"""
|
||||
self.world = world
|
||||
self.display = None
|
||||
self.input = None
|
||||
self.streaming = None
|
||||
self.device = None
|
||||
self.is_initialized = False
|
||||
self.config = self._load_config()
|
||||
|
||||
# 状态变量
|
||||
self.vr_enabled = False
|
||||
self.streaming_enabled = False
|
||||
|
||||
print("✓ VR系统核心已创建")
|
||||
|
||||
def _load_config(self):
|
||||
"""加载VR配置"""
|
||||
config_path = os.path.join(os.path.dirname(__file__), "vr_config.json")
|
||||
default_config = {
|
||||
"preferred_device": "openxr",
|
||||
"render_resolution": [2048, 2048],
|
||||
"refresh_rate": 90,
|
||||
"ipd": 0.064, # 瞳距,单位:米
|
||||
"streaming": {
|
||||
"enabled": True,
|
||||
"encoder": "h264",
|
||||
"bitrate": 30000000, # 30 Mbps
|
||||
"use_hardware_encoding": True
|
||||
},
|
||||
"performance": {
|
||||
"dynamic_resolution": True,
|
||||
"fixed_foveated_rendering": True,
|
||||
"motion_smoothing": True
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, 'r') as f:
|
||||
return json.load(f)
|
||||
else:
|
||||
# 如果配置文件不存在,创建默认配置
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(default_config, f, indent=4)
|
||||
return default_config
|
||||
except Exception as e:
|
||||
print(f"加载VR配置失败: {e}")
|
||||
return default_config
|
||||
|
||||
def save_config(self):
|
||||
"""保存VR配置"""
|
||||
config_path = os.path.join(os.path.dirname(__file__), "vr_config.json")
|
||||
try:
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(self.config, f, indent=4)
|
||||
print("✓ VR配置已保存")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"保存VR配置失败: {e}")
|
||||
return False
|
||||
|
||||
def initialize(self):
|
||||
"""初始化VR系统"""
|
||||
if self.is_initialized:
|
||||
print("VR系统已经初始化")
|
||||
return True
|
||||
|
||||
print("正在初始化VR系统...")
|
||||
|
||||
# 1. 导入必要的模块(延迟导入以避免循环依赖)
|
||||
from .vr_display import VRDisplay
|
||||
from .vr_input import VRInput
|
||||
from .vr_streaming import VRStreaming
|
||||
|
||||
# 2. 根据配置选择设备实现
|
||||
device_type = self.config.get("preferred_device", "openxr")
|
||||
device_created = self._create_device(device_type)
|
||||
|
||||
if not device_created:
|
||||
print("✗ 创建VR设备失败")
|
||||
return False
|
||||
|
||||
# 3. 创建显示、输入和串流组件
|
||||
self.display = VRDisplay(self)
|
||||
self.input = VRInput(self)
|
||||
self.streaming = VRStreaming(self)
|
||||
|
||||
# 4. 初始化各组件
|
||||
display_initialized = self.display.initialize()
|
||||
if not display_initialized:
|
||||
print("✗ 初始化VR显示失败")
|
||||
return False
|
||||
|
||||
input_initialized = self.input.initialize()
|
||||
if not input_initialized:
|
||||
print("✗ 初始化VR输入失败")
|
||||
return False
|
||||
|
||||
# 5. 设置更新任务
|
||||
taskMgr = self.world.taskMgr
|
||||
taskMgr.add(self.update, "vr_system_update")
|
||||
|
||||
self.is_initialized = True
|
||||
self.vr_enabled = True
|
||||
print("✓ VR系统初始化完成")
|
||||
return True
|
||||
|
||||
def _create_device(self, device_type):
|
||||
"""创建VR设备实现"""
|
||||
try:
|
||||
if device_type == "openxr":
|
||||
from .devices.openxr_device import OpenXRDevice
|
||||
self.device = OpenXRDevice()
|
||||
elif device_type == "oculus":
|
||||
from .devices.oculus_device import OculusDevice
|
||||
self.device = OculusDevice()
|
||||
elif device_type == "steamvr":
|
||||
from .devices.steamvr_device import SteamVRDevice
|
||||
self.device = SteamVRDevice()
|
||||
else:
|
||||
print(f"不支持的设备类型: {device_type}")
|
||||
return False
|
||||
|
||||
device_initialized = self.device.initialize()
|
||||
if not device_initialized:
|
||||
print(f"✗ 初始化{device_type}设备失败")
|
||||
return False
|
||||
|
||||
print(f"✓ 已创建并初始化{device_type}设备")
|
||||
return True
|
||||
except ImportError as e:
|
||||
print(f"导入设备模块失败: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"创建设备实例失败: {e}")
|
||||
return False
|
||||
|
||||
def shutdown(self):
|
||||
"""关闭VR系统"""
|
||||
if not self.is_initialized:
|
||||
print("VR系统未初始化")
|
||||
return True
|
||||
|
||||
print("正在关闭VR系统...")
|
||||
|
||||
# 1. 停止串流
|
||||
if self.streaming and self.streaming_enabled:
|
||||
self.stop_streaming()
|
||||
|
||||
# 2. 关闭各组件
|
||||
if self.input:
|
||||
self.input.shutdown()
|
||||
|
||||
if self.display:
|
||||
self.display.shutdown()
|
||||
|
||||
if self.device:
|
||||
self.device.shutdown()
|
||||
|
||||
# 3. 移除更新任务
|
||||
taskMgr = self.world.taskMgr
|
||||
taskMgr.remove("vr_system_update")
|
||||
|
||||
self.is_initialized = False
|
||||
self.vr_enabled = False
|
||||
print("✓ VR系统已关闭")
|
||||
return True
|
||||
|
||||
def update(self, task):
|
||||
"""更新VR系统状态(每帧调用)"""
|
||||
if not self.is_initialized:
|
||||
return Task.cont
|
||||
|
||||
# 1. 更新设备状态
|
||||
if self.device:
|
||||
self.device.update()
|
||||
|
||||
# 2. 更新输入
|
||||
if self.input:
|
||||
self.input.update()
|
||||
|
||||
# 3. 更新显示
|
||||
if self.display:
|
||||
self.display.update()
|
||||
|
||||
# 4. 更新串流
|
||||
if self.streaming and self.streaming_enabled:
|
||||
self.streaming.update()
|
||||
|
||||
return Task.cont
|
||||
|
||||
def start_streaming(self):
|
||||
"""开始VR画面串流"""
|
||||
if not self.is_initialized:
|
||||
print("VR系统未初始化,无法开始串流")
|
||||
return False
|
||||
|
||||
if not self.streaming:
|
||||
print("VR串流组件未创建")
|
||||
return False
|
||||
|
||||
if self.streaming_enabled:
|
||||
print("VR串流已经启动")
|
||||
return True
|
||||
|
||||
result = self.streaming.start()
|
||||
if result:
|
||||
self.streaming_enabled = True
|
||||
print("✓ VR串流已启动")
|
||||
else:
|
||||
print("✗ 启动VR串流失败")
|
||||
|
||||
return result
|
||||
|
||||
def stop_streaming(self):
|
||||
"""停止VR画面串流"""
|
||||
if not self.streaming or not self.streaming_enabled:
|
||||
print("VR串流未启动")
|
||||
return True
|
||||
|
||||
result = self.streaming.stop()
|
||||
if result:
|
||||
self.streaming_enabled = False
|
||||
print("✓ VR串流已停止")
|
||||
else:
|
||||
print("✗ 停止VR串流失败")
|
||||
|
||||
return result
|
||||
|
||||
def get_status(self):
|
||||
"""获取VR系统状态信息"""
|
||||
status = {
|
||||
"initialized": self.is_initialized,
|
||||
"vr_enabled": self.vr_enabled,
|
||||
"streaming_enabled": self.streaming_enabled,
|
||||
"device_type": self.config.get("preferred_device", "unknown"),
|
||||
"render_resolution": self.config.get("render_resolution", [0, 0]),
|
||||
"refresh_rate": self.config.get("refresh_rate", 0)
|
||||
}
|
||||
|
||||
# 添加设备特定信息
|
||||
if self.device:
|
||||
device_status = self.device.get_status()
|
||||
status.update(device_status)
|
||||
|
||||
# 添加串流信息
|
||||
if self.streaming:
|
||||
streaming_status = self.streaming.get_statistics()
|
||||
status["streaming"] = streaming_status
|
||||
|
||||
return status
|
||||
|
||||
def set_render_resolution(self, width, height):
|
||||
"""设置VR渲染分辨率"""
|
||||
if not self.is_initialized:
|
||||
print("VR系统未初始化,无法设置渲染分辨率")
|
||||
return False
|
||||
|
||||
self.config["render_resolution"] = [width, height]
|
||||
|
||||
if self.display:
|
||||
result = self.display.set_resolution(width, height)
|
||||
if result:
|
||||
print(f"✓ VR渲染分辨率已设置为 {width}x{height}")
|
||||
self.save_config()
|
||||
return result
|
||||
|
||||
return False
|
||||
|
||||
def set_refresh_rate(self, rate):
|
||||
"""设置VR刷新率"""
|
||||
if not self.is_initialized:
|
||||
print("VR系统未初始化,无法设置刷新率")
|
||||
return False
|
||||
|
||||
self.config["refresh_rate"] = rate
|
||||
|
||||
if self.device:
|
||||
result = self.device.set_refresh_rate(rate)
|
||||
if result:
|
||||
print(f"✓ VR刷新率已设置为 {rate}Hz")
|
||||
self.save_config()
|
||||
return result
|
||||
|
||||
return False
|
||||
|
||||
def set_ipd(self, ipd):
|
||||
"""设置瞳距(单位:米)"""
|
||||
if not self.is_initialized:
|
||||
print("VR系统未初始化,无法设置瞳距")
|
||||
return False
|
||||
|
||||
self.config["ipd"] = ipd
|
||||
|
||||
if self.display:
|
||||
result = self.display.set_ipd(ipd)
|
||||
if result:
|
||||
print(f"✓ VR瞳距已设置为 {ipd}m")
|
||||
self.save_config()
|
||||
return result
|
||||
|
||||
return False
|
||||
|
||||
def set_streaming_quality(self, bitrate=None, encoder=None, use_hardware=None):
|
||||
"""设置串流质量参数"""
|
||||
if not self.streaming:
|
||||
print("VR串流组件未创建,无法设置串流质量")
|
||||
return False
|
||||
|
||||
streaming_config = self.config.get("streaming", {})
|
||||
|
||||
if bitrate is not None:
|
||||
streaming_config["bitrate"] = bitrate
|
||||
|
||||
if encoder is not None:
|
||||
streaming_config["encoder"] = encoder
|
||||
|
||||
if use_hardware is not None:
|
||||
streaming_config["use_hardware_encoding"] = use_hardware
|
||||
|
||||
self.config["streaming"] = streaming_config
|
||||
|
||||
result = self.streaming.set_quality(
|
||||
bitrate=streaming_config.get("bitrate"),
|
||||
encoder=streaming_config.get("encoder"),
|
||||
use_hardware=streaming_config.get("use_hardware_encoding")
|
||||
)
|
||||
|
||||
if result:
|
||||
print("✓ VR串流质量参数已更新")
|
||||
self.save_config()
|
||||
|
||||
return result
|
||||
|
||||
def calibrate(self):
|
||||
"""启动VR校准过程"""
|
||||
if not self.is_initialized:
|
||||
print("VR系统未初始化,无法校准")
|
||||
return False
|
||||
|
||||
print("正在启动VR校准...")
|
||||
|
||||
# 导入校准模块
|
||||
from .vr_calibration import start_calibration
|
||||
|
||||
result = start_calibration(self)
|
||||
if result:
|
||||
print("✓ VR校准完成")
|
||||
else:
|
||||
print("✗ VR校准失败或被取消")
|
||||
|
||||
return result
|
||||
|
||||
def is_vr_enabled(self):
|
||||
"""检查VR是否启用"""
|
||||
return self.vr_enabled
|
||||
|
||||
def is_streaming(self):
|
||||
"""检查是否正在串流"""
|
||||
return self.streaming_enabled
|
||||
@ -1,500 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
VR显示功能演示脚本
|
||||
演示引擎画面在VR中的显示效果,包括ALVR串流
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from main import MyWorld
|
||||
|
||||
|
||||
def demo_vr_basic_display():
|
||||
"""演示基本VR显示功能"""
|
||||
print("=== VR基本显示演示 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 加载一些模型来展示
|
||||
print("📦 加载演示场景...")
|
||||
try:
|
||||
# 创建一些简单的几何体作为演示
|
||||
from panda3d.core import CardMaker, Vec4
|
||||
|
||||
# 创建地面
|
||||
cm = CardMaker("ground")
|
||||
cm.setFrame(-10, 10, -10, 10)
|
||||
ground = world.render.attachNewNode(cm.generate())
|
||||
ground.setPos(0, 0, -1)
|
||||
ground.setColor(0.5, 0.8, 0.5, 1.0)
|
||||
ground.setHpr(0, -90, 0)
|
||||
|
||||
# 创建一些立方体
|
||||
for i in range(5):
|
||||
for j in range(5):
|
||||
cube = world.loader.loadModel("environment")
|
||||
if not cube:
|
||||
# 如果没有environment模型,创建简单的立方体
|
||||
cm_cube = CardMaker(f"cube_{i}_{j}")
|
||||
cm_cube.setFrame(-0.5, 0.5, -0.5, 0.5)
|
||||
cube = world.render.attachNewNode(cm_cube.generate())
|
||||
|
||||
cube.setPos(i * 2 - 4, j * 2 - 4, 0)
|
||||
cube.setScale(0.5)
|
||||
cube.setColor(i/5.0, j/5.0, 1.0, 1.0)
|
||||
|
||||
print("✓ 演示场景加载完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"场景加载错误: {str(e)}")
|
||||
|
||||
# 初始化VR系统
|
||||
print("\n🥽 初始化VR显示系统...")
|
||||
vr_success = world.initializeVR()
|
||||
|
||||
if vr_success:
|
||||
print("✓ VR系统初始化成功")
|
||||
vr_info = world.getVRInfo()
|
||||
|
||||
print(f"\n📊 VR显示信息:")
|
||||
print(f" - 模式: {vr_info['mode']}")
|
||||
print(f" - 渲染尺寸: {vr_info['render_size']}")
|
||||
print(f" - 模拟模式: {vr_info['simulation_mode']}")
|
||||
|
||||
if vr_info['simulation_mode']:
|
||||
print("\n💡 模拟模式说明:")
|
||||
print(" - 左右眼视图将并排显示在主窗口")
|
||||
print(" - 可以看到立体渲染效果")
|
||||
print(" - 头部会有轻微的模拟摆动")
|
||||
else:
|
||||
print("\n💡 真实VR模式:")
|
||||
print(" - 画面将渲染到VR头盔")
|
||||
print(" - 支持真实的头部追踪")
|
||||
print(" - 控制器交互可用")
|
||||
|
||||
# 运行几秒钟以展示效果
|
||||
print(f"\n🎮 VR显示演示运行中... (10秒)")
|
||||
print(" 观察主窗口中的立体渲染效果")
|
||||
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 10:
|
||||
# 处理Panda3D事件
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
time.sleep(0.016) # ~60 FPS
|
||||
|
||||
print("✓ VR显示演示完成")
|
||||
|
||||
else:
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 关闭VR系统
|
||||
world.shutdownVR()
|
||||
print("\n✓ VR系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def demo_alvr_streaming():
|
||||
"""演示ALVR串流功能"""
|
||||
print("=== ALVR串流显示演示 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 首先初始化VR系统
|
||||
print("🥽 初始化VR系统...")
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 检查是否为直连模式(SteamVR已运行)
|
||||
if world.alvr_streamer._is_steamvr_active():
|
||||
print("💡 检测到SteamVR正在运行,将使用直连模式")
|
||||
print(" 在直连模式下,画面将直接通过SteamVR传输到头显")
|
||||
print(" 无需通过ALVR服务器进行额外的串流处理")
|
||||
|
||||
# 初始化ALVR串流
|
||||
print("\n📡 初始化ALVR串流...")
|
||||
print("💡 检测到VR设备已连接,正在尝试连接ALVR服务器...")
|
||||
alvr_success = world.initializeALVR()
|
||||
|
||||
if alvr_success:
|
||||
print("✓ ALVR串流初始化成功")
|
||||
|
||||
# 设置串流质量
|
||||
print("\n🎥 配置串流质量...")
|
||||
quality_set = world.setALVRStreamQuality(2880, 1700, 72, 150)
|
||||
if quality_set:
|
||||
print(" ✓ 串流质量: 2880x1700@72fps, 150Mbps")
|
||||
|
||||
# 获取串流状态
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f"\n📊 ALVR串流状态:")
|
||||
print(f" - 连接状态: {status['connected']}")
|
||||
print(f" - 串流状态: {status['streaming']}")
|
||||
print(f" - 分辨率: {status['resolution']}")
|
||||
print(f" - 帧率: {status['fps']}")
|
||||
print(f" - 码率: {status['bitrate']}Mbps")
|
||||
|
||||
# 开始串流
|
||||
print(f"\n🚀 开始ALVR串流...")
|
||||
if world.startALVRStreaming():
|
||||
print("✓ ALVR串流已开始")
|
||||
|
||||
print(f"\n💡 ALVR串流说明:")
|
||||
print(" - 引擎画面正在串流到VR头盔")
|
||||
print(" - 支持Quest 2/3/Pico 4等VR设备")
|
||||
print(" - 确保ALVR客户端在头盔中运行")
|
||||
print(" - 检查网络连接质量")
|
||||
|
||||
# 运行串流演示
|
||||
print(f"\n📡 ALVR串流演示运行中... (15秒)")
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 15:
|
||||
# 更新串流状态
|
||||
current_status = world.getALVRStreamingStatus()
|
||||
if time.time() - start_time > 5: # 5秒后显示状态更新
|
||||
print(f" 当前FPS: {current_status['fps']}, 延迟: {current_status['latency']}ms")
|
||||
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
time.sleep(0.016)
|
||||
|
||||
# 停止串流
|
||||
print("\n🛑 停止ALVR串流...")
|
||||
world.stopALVRStreaming()
|
||||
print("✓ ALVR串流已停止")
|
||||
|
||||
else:
|
||||
print("⚠ ALVR串流启动失败")
|
||||
print(" 可能原因:")
|
||||
print(" - ALVR服务器未运行")
|
||||
print(" - VR客户端未连接")
|
||||
print(" - 网络连接问题")
|
||||
print(" - 防火墙阻止连接")
|
||||
|
||||
else:
|
||||
print("⚠ ALVR串流初始化失败")
|
||||
print("💡 这可能是因为:")
|
||||
print(" 1. ALVR服务器未运行")
|
||||
print(" 2. SteamVR正在使用独占模式")
|
||||
print(" 3. 端口冲突")
|
||||
print("\n🔧 Pico 4有线连接设置建议:")
|
||||
print(" 1. 确保ALVR服务器正在运行")
|
||||
print(" 2. 检查任务管理器中是否有alvr_server进程")
|
||||
print(" 3. 尝试重启ALVR服务器")
|
||||
print(" 4. 确认防火墙允许ALVR通信")
|
||||
print(" 5. 检查ALVR服务器端口设置")
|
||||
print(" 6. 如果SteamVR正在运行,尝试先关闭它")
|
||||
print("\n💡 直连模式说明:")
|
||||
print(" 如果您使用的是有线连接(如Pico 4通过USB-C连接)")
|
||||
print(" 并且SteamVR已经可以正常显示画面,您可能不需要ALVR串流")
|
||||
print(" SteamVR会直接处理渲染并将画面传输到头显")
|
||||
|
||||
# 关闭系统
|
||||
world.shutdownALVR()
|
||||
world.shutdownVR()
|
||||
print("\n✓ 所有系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def demo_vr_interaction():
|
||||
"""演示VR交互功能"""
|
||||
print("=== VR交互显示演示 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 初始化VR系统
|
||||
print("🥽 初始化VR系统...")
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 启动VR输入处理
|
||||
print("\n🎮 启动VR输入处理...")
|
||||
if world.startVRInput():
|
||||
print("✓ VR输入处理已启动")
|
||||
|
||||
# 显示控制器射线
|
||||
world.showControllerRays(True)
|
||||
print("✓ 控制器射线已启用")
|
||||
|
||||
# 启用VR交互
|
||||
world.setVRInteractionEnabled(True)
|
||||
world.setVRGestureEnabled(True)
|
||||
print("✓ VR交互和手势识别已启用")
|
||||
|
||||
# 获取控制器状态
|
||||
print(f"\n🎮 控制器状态:")
|
||||
controllers = world.getAllControllers()
|
||||
for i, controller in enumerate(controllers):
|
||||
if controller:
|
||||
state = world.getControllerState(i)
|
||||
print(f" 控制器 {i}: 可用")
|
||||
if state:
|
||||
print(f" - 连接: {state.get('connected', False)}")
|
||||
print(f" - 扳机: {state.get('trigger', 0):.2f}")
|
||||
print(f" - 握把: {state.get('grip', 0):.2f}")
|
||||
|
||||
# 运行交互演示
|
||||
print(f"\n🤏 VR交互演示运行中... (10秒)")
|
||||
print(" - 控制器射线可见")
|
||||
print(" - 扳机:抓取物体")
|
||||
print(" - 握把:切换交互模式")
|
||||
print(" - 触摸板:导航控制")
|
||||
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 10:
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
time.sleep(0.016)
|
||||
|
||||
# 停止VR输入
|
||||
world.stopVRInput()
|
||||
print("✓ VR输入处理已停止")
|
||||
|
||||
else:
|
||||
print("⚠ VR输入处理启动失败")
|
||||
|
||||
# 关闭VR系统
|
||||
world.shutdownVR()
|
||||
print("\n✓ VR系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def demo_comprehensive_vr():
|
||||
"""综合VR显示演示"""
|
||||
print("=== 综合VR显示演示 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 创建丰富的演示场景
|
||||
print("🎨 创建VR演示场景...")
|
||||
try:
|
||||
from panda3d.core import CardMaker, Vec4, AmbientLight, DirectionalLight
|
||||
|
||||
# 添加光照
|
||||
ambient_light = AmbientLight('ambient')
|
||||
ambient_light.setColor(Vec4(0.3, 0.3, 0.3, 1))
|
||||
ambient_light_np = world.render.attachNewNode(ambient_light)
|
||||
world.render.setLight(ambient_light_np)
|
||||
|
||||
directional_light = DirectionalLight('directional')
|
||||
directional_light.setColor(Vec4(0.8, 0.8, 0.8, 1))
|
||||
directional_light.setDirection(Vec4(-1, -1, -1, 0))
|
||||
directional_light_np = world.render.attachNewNode(directional_light)
|
||||
world.render.setLight(directional_light_np)
|
||||
|
||||
# 创建地面网格
|
||||
for i in range(-5, 6):
|
||||
for j in range(-5, 6):
|
||||
cm = CardMaker(f"grid_{i}_{j}")
|
||||
cm.setFrame(-0.4, 0.4, -0.4, 0.4)
|
||||
grid = world.render.attachNewNode(cm.generate())
|
||||
grid.setPos(i * 1.0, j * 1.0, -1)
|
||||
grid.setHpr(0, -90, 0)
|
||||
|
||||
# 棋盘格颜色
|
||||
if (i + j) % 2 == 0:
|
||||
grid.setColor(0.8, 0.8, 0.8, 1.0)
|
||||
else:
|
||||
grid.setColor(0.6, 0.6, 0.6, 1.0)
|
||||
|
||||
# 创建一些彩色立方体
|
||||
for i in range(8):
|
||||
cm = CardMaker(f"cube_{i}")
|
||||
cm.setFrame(-0.3, 0.3, -0.3, 0.3)
|
||||
cube = world.render.attachNewNode(cm.generate())
|
||||
|
||||
import math
|
||||
angle = i * math.pi * 2 / 8
|
||||
radius = 3
|
||||
cube.setPos(math.cos(angle) * radius, math.sin(angle) * radius, 0.5)
|
||||
cube.setHpr(i * 45, 0, 0)
|
||||
cube.setScale(1, 1, 2)
|
||||
|
||||
# 彩虹颜色
|
||||
hue = i / 8.0
|
||||
r = abs(math.sin(hue * math.pi * 2))
|
||||
g = abs(math.sin((hue + 0.33) * math.pi * 2))
|
||||
b = abs(math.sin((hue + 0.66) * math.pi * 2))
|
||||
cube.setColor(r, g, b, 1.0)
|
||||
|
||||
print("✓ VR演示场景创建完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"场景创建错误: {str(e)}")
|
||||
|
||||
# 完整的VR系统演示
|
||||
print("\n🥽 启动完整VR系统...")
|
||||
|
||||
# 1. 初始化VR
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
print("✓ VR显示系统已启动")
|
||||
|
||||
# 2. 启动VR输入
|
||||
if world.startVRInput():
|
||||
world.showControllerRays(True)
|
||||
world.setVRInteractionEnabled(True)
|
||||
print("✓ VR交互系统已启动")
|
||||
|
||||
# 3. 尝试启动ALVR串流
|
||||
if world.initializeALVR():
|
||||
world.setALVRStreamQuality(2880, 1700, 72, 100)
|
||||
if world.startALVRStreaming():
|
||||
print("✓ ALVR串流已启动")
|
||||
else:
|
||||
print("⚠ ALVR串流启动失败")
|
||||
else:
|
||||
print("⚠ ALVR初始化失败(正常,如果没有ALVR服务器)")
|
||||
|
||||
# 运行综合演示
|
||||
print(f"\n🎬 综合VR显示演示运行中... (20秒)")
|
||||
print("💡 功能说明:")
|
||||
print(" - 立体渲染显示")
|
||||
print(" - 头部追踪(模拟或真实)")
|
||||
print(" - 控制器可视化")
|
||||
print(" - ALVR无线串流")
|
||||
print(" - VR交互功能")
|
||||
|
||||
start_time = time.time()
|
||||
frame_count = 0
|
||||
|
||||
while time.time() - start_time < 20:
|
||||
# 处理引擎更新
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
|
||||
frame_count += 1
|
||||
|
||||
# 每5秒显示一次状态
|
||||
elapsed = time.time() - start_time
|
||||
if frame_count % 300 == 0: # 约每5秒
|
||||
fps = frame_count / elapsed
|
||||
print(f" 运行状态: {elapsed:.1f}s, FPS: {fps:.1f}")
|
||||
|
||||
# 显示VR状态
|
||||
vr_info = world.getVRInfo()
|
||||
print(f" VR模式: {vr_info['mode']}, 分辨率: {vr_info['render_size']}")
|
||||
|
||||
if world.isALVRStreaming():
|
||||
alvr_status = world.getALVRStreamingStatus()
|
||||
print(f" ALVR: FPS {alvr_status['fps']}, 延迟 {alvr_status['latency']}ms")
|
||||
|
||||
time.sleep(0.016) # ~60 FPS
|
||||
|
||||
print("✓ 综合VR演示完成")
|
||||
|
||||
# 关闭所有系统
|
||||
world.stopVRInput()
|
||||
world.stopALVRStreaming()
|
||||
world.shutdownALVR()
|
||||
world.shutdownVR()
|
||||
print("\n✓ 所有VR系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def print_vr_display_guide():
|
||||
"""打印VR显示指南"""
|
||||
print("📋 VR显示功能指南")
|
||||
print("=" * 50)
|
||||
|
||||
print("\n🎯 功能概述:")
|
||||
print(" - 立体渲染:为左右眼生成不同视角的画面")
|
||||
print(" - 头部追踪:根据头部运动调整视角")
|
||||
print(" - ALVR串流:无线串流到Quest等VR设备")
|
||||
print(" - 控制器交互:支持VR控制器输入")
|
||||
|
||||
print("\n🔧 使用方法:")
|
||||
print(" 1. 运行引擎:python3 main.py")
|
||||
print(" 2. 初始化VR:world.initializeVR()")
|
||||
print(" 3. 启动ALVR:world.initializeALVR()")
|
||||
print(" 4. 开始串流:world.startALVRStreaming()")
|
||||
|
||||
print("\n📱 ALVR设置步骤:")
|
||||
print(" 1. 下载ALVR服务器到PC")
|
||||
print(" 2. 在Quest中安装ALVR客户端")
|
||||
print(" 3. 确保PC和Quest在同一WiFi网络")
|
||||
print(" 4. 启动ALVR服务器")
|
||||
print(" 5. 在Quest中连接到PC")
|
||||
print(" 6. 运行引擎VR演示")
|
||||
|
||||
print("\n🎮 VR模式说明:")
|
||||
print(" 模拟模式:")
|
||||
print(" - 无需VR硬件")
|
||||
print(" - 立体视图显示在主窗口")
|
||||
print(" - 模拟头部和控制器追踪")
|
||||
print(" - 适合开发和调试")
|
||||
print(" ")
|
||||
print(" 真实VR模式:")
|
||||
print(" - 需要VR头盔和SteamVR")
|
||||
print(" - 真实的立体渲染和追踪")
|
||||
print(" - 支持控制器交互")
|
||||
print(" - 完整的VR体验")
|
||||
|
||||
print("\n🛠 故障排除:")
|
||||
print(" - VR初始化失败 → 自动切换到模拟模式")
|
||||
print(" - ALVR连接失败 → 检查网络和服务器")
|
||||
print(" - 画面不显示 → 检查VR头盔和SteamVR")
|
||||
print(" - 延迟过高 → 降低串流质量或使用有线连接")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🎮 VR显示功能演示")
|
||||
print("=" * 50)
|
||||
|
||||
print("请选择演示类型:")
|
||||
print("1. 基本VR显示演示")
|
||||
print("2. ALVR串流演示")
|
||||
print("3. VR交互演示")
|
||||
print("4. 综合VR演示")
|
||||
print("5. VR显示功能指南")
|
||||
|
||||
try:
|
||||
choice = input("请输入选择 (1-5): ")
|
||||
|
||||
if choice == "1":
|
||||
success = demo_vr_basic_display()
|
||||
elif choice == "2":
|
||||
success = demo_alvr_streaming()
|
||||
elif choice == "3":
|
||||
success = demo_vr_interaction()
|
||||
elif choice == "4":
|
||||
success = demo_comprehensive_vr()
|
||||
elif choice == "5":
|
||||
print_vr_display_guide()
|
||||
return
|
||||
else:
|
||||
print("无效的选择")
|
||||
return
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
if success:
|
||||
print("✓ VR显示演示成功完成")
|
||||
print("🎉 引擎画面VR显示功能正常工作!")
|
||||
else:
|
||||
print("✗ VR显示演示遇到问题")
|
||||
print("💡 请检查VR系统设置和连接")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n用户中断演示")
|
||||
except Exception as e:
|
||||
print(f"演示过程中发生错误: {str(e)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,316 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
VR显示优化工具
|
||||
优化VR渲染性能和显示质量
|
||||
"""
|
||||
|
||||
from panda3d.core import RenderState, CullFaceAttrib, DepthTestAttrib, AlphaTestAttrib
|
||||
from core.vr_manager import VRManager
|
||||
|
||||
|
||||
class VRDisplayOptimizer:
|
||||
"""VR显示优化器"""
|
||||
|
||||
def __init__(self, world):
|
||||
self.world = world
|
||||
self.vr_manager = world.vr_manager if hasattr(world, 'vr_manager') else None
|
||||
self.optimization_enabled = True
|
||||
|
||||
def optimize_vr_rendering(self):
|
||||
"""优化VR渲染设置"""
|
||||
if not self.vr_manager or not self.vr_manager.is_vr_enabled():
|
||||
print("VR系统未启用,跳过优化")
|
||||
return False
|
||||
|
||||
print("🚀 开始VR渲染优化...")
|
||||
|
||||
# 1. 优化眼部缓冲区设置
|
||||
self._optimize_eye_buffers()
|
||||
|
||||
# 2. 优化渲染状态
|
||||
self._optimize_render_states()
|
||||
|
||||
# 3. 设置VR特定的渲染选项
|
||||
self._setup_vr_render_options()
|
||||
|
||||
# 4. 优化纹理设置
|
||||
self._optimize_textures()
|
||||
|
||||
# 5. 设置LOD和裁剪
|
||||
self._setup_lod_and_culling()
|
||||
|
||||
print("✓ VR渲染优化完成")
|
||||
return True
|
||||
|
||||
def _optimize_eye_buffers(self):
|
||||
"""优化眼部缓冲区"""
|
||||
try:
|
||||
if not self.vr_manager.left_eye_buffer or not self.vr_manager.right_eye_buffer:
|
||||
return
|
||||
|
||||
# 设置缓冲区优化选项
|
||||
for buffer in [self.vr_manager.left_eye_buffer, self.vr_manager.right_eye_buffer]:
|
||||
# 启用多重采样抗锯齿
|
||||
buffer.setMultisample(4)
|
||||
|
||||
# 设置深度缓冲精度
|
||||
buffer.addDepthRenderTexture()
|
||||
|
||||
# 启用硬件加速
|
||||
buffer.setOneShot(False)
|
||||
buffer.setActive(True)
|
||||
|
||||
print(" ✓ 眼部缓冲区优化完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 眼部缓冲区优化失败: {str(e)}")
|
||||
|
||||
def _optimize_render_states(self):
|
||||
"""优化渲染状态"""
|
||||
try:
|
||||
# 设置全局渲染状态
|
||||
render_state = RenderState.make(
|
||||
CullFaceAttrib.make(CullFaceAttrib.MCullCounterClockwise),
|
||||
DepthTestAttrib.make(DepthTestAttrib.MLess),
|
||||
AlphaTestAttrib.make(AlphaTestAttrib.MGreater, 0.5)
|
||||
)
|
||||
|
||||
# 应用到render节点
|
||||
self.world.render.setState(render_state)
|
||||
|
||||
print(" ✓ 渲染状态优化完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 渲染状态优化失败: {str(e)}")
|
||||
|
||||
def _setup_vr_render_options(self):
|
||||
"""设置VR特定的渲染选项"""
|
||||
try:
|
||||
# 启用VR优化
|
||||
if hasattr(self.world, 'win') and self.world.win:
|
||||
# 设置VR特定的窗口属性
|
||||
properties = self.world.win.getProperties()
|
||||
properties.setFrameBufferMode(properties.FBMHardware)
|
||||
|
||||
# 启用垂直同步以减少延迟
|
||||
properties.setSync(True)
|
||||
|
||||
# 优化摄像机设置
|
||||
if hasattr(self.world, 'camera'):
|
||||
# 设置适合VR的FOV和近远裁剪面
|
||||
lens = self.world.camera.node().getLens()
|
||||
lens.setNearFar(0.1, 1000.0) # VR推荐的近远裁剪面
|
||||
|
||||
print(" ✓ VR渲染选项设置完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ VR渲染选项设置失败: {str(e)}")
|
||||
|
||||
def _optimize_textures(self):
|
||||
"""优化纹理设置"""
|
||||
try:
|
||||
from panda3d.core import Texture
|
||||
|
||||
# 设置全局纹理优化
|
||||
Texture.setTexturesPower2(Texture.ATPPad) # 填充到2的幂次方
|
||||
Texture.setKeepRAMImage(False) # 不保留RAM图像以节省内存
|
||||
|
||||
# 启用纹理压缩
|
||||
Texture.setCompressionMode(Texture.CMDefault)
|
||||
|
||||
print(" ✓ 纹理优化完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 纹理优化失败: {str(e)}")
|
||||
|
||||
def _setup_lod_and_culling(self):
|
||||
"""设置LOD和裁剪"""
|
||||
try:
|
||||
# 启用视锥裁剪
|
||||
from panda3d.core import CullTraverser
|
||||
self.world.render.setTwoSided(False)
|
||||
|
||||
# 设置距离裁剪
|
||||
if hasattr(self.world, 'camera'):
|
||||
# 根据VR需求调整裁剪距离
|
||||
lens = self.world.camera.node().getLens()
|
||||
lens.setFar(500.0) # 减少远裁剪面以提高性能
|
||||
|
||||
print(" ✓ LOD和裁剪设置完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ LOD和裁剪设置失败: {str(e)}")
|
||||
|
||||
def optimize_alvr_streaming(self):
|
||||
"""优化ALVR串流性能"""
|
||||
if not hasattr(self.world, 'alvr_streamer'):
|
||||
print("ALVR串流器不可用,跳过优化")
|
||||
return False
|
||||
|
||||
print("📡 开始ALVR串流优化...")
|
||||
|
||||
alvr = self.world.alvr_streamer
|
||||
|
||||
# 1. 优化串流质量设置
|
||||
self._optimize_stream_quality(alvr)
|
||||
|
||||
# 2. 优化网络设置
|
||||
self._optimize_network_settings(alvr)
|
||||
|
||||
# 3. 优化编码设置
|
||||
self._optimize_encoding_settings(alvr)
|
||||
|
||||
print("✓ ALVR串流优化完成")
|
||||
return True
|
||||
|
||||
def _optimize_stream_quality(self, alvr):
|
||||
"""优化串流质量"""
|
||||
try:
|
||||
# 根据性能自动调整质量
|
||||
vr_info = self.vr_manager.get_vr_info()
|
||||
render_width, render_height = vr_info['render_size']
|
||||
|
||||
# 针对不同设备优化
|
||||
if render_width >= 2880: # Quest 2/3高质量
|
||||
alvr.set_stream_quality(2880, 1700, 72, 150)
|
||||
print(" ✓ 设置为高质量模式 (Quest 2/3)")
|
||||
elif render_width >= 2160: # 中等质量
|
||||
alvr.set_stream_quality(2160, 1200, 60, 100)
|
||||
print(" ✓ 设置为中等质量模式")
|
||||
else: # 低质量模式
|
||||
alvr.set_stream_quality(1920, 1080, 60, 80)
|
||||
print(" ✓ 设置为低质量模式")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 串流质量优化失败: {str(e)}")
|
||||
|
||||
def _optimize_network_settings(self, alvr):
|
||||
"""优化网络设置"""
|
||||
try:
|
||||
# 这里可以添加网络优化逻辑
|
||||
# 例如调整缓冲区大小、连接超时等
|
||||
print(" ✓ 网络设置优化完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 网络设置优化失败: {str(e)}")
|
||||
|
||||
def _optimize_encoding_settings(self, alvr):
|
||||
"""优化编码设置"""
|
||||
try:
|
||||
# 优化H.264编码参数
|
||||
alvr.codec = "h264"
|
||||
|
||||
# 根据硬件能力调整编码设置
|
||||
print(" ✓ 编码设置优化完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 编码设置优化失败: {str(e)}")
|
||||
|
||||
def monitor_performance(self, duration=10):
|
||||
"""监控VR性能"""
|
||||
print(f"📊 开始VR性能监控 ({duration}秒)...")
|
||||
|
||||
import time
|
||||
start_time = time.time()
|
||||
frame_count = 0
|
||||
last_report_time = start_time
|
||||
|
||||
while time.time() - start_time < duration:
|
||||
# 处理一帧
|
||||
if hasattr(self.world, 'taskMgr'):
|
||||
self.world.taskMgr.step()
|
||||
|
||||
frame_count += 1
|
||||
current_time = time.time()
|
||||
|
||||
# 每2秒报告一次性能
|
||||
if current_time - last_report_time >= 2.0:
|
||||
elapsed = current_time - last_report_time
|
||||
fps = (frame_count / (current_time - start_time))
|
||||
|
||||
print(f" 性能报告: FPS {fps:.1f}")
|
||||
|
||||
# 报告VR系统状态
|
||||
if self.vr_manager and self.vr_manager.is_vr_enabled():
|
||||
vr_info = self.vr_manager.get_vr_info()
|
||||
print(f" VR模式: {vr_info['mode']}")
|
||||
print(f" 渲染尺寸: {vr_info['render_size']}")
|
||||
|
||||
# 报告ALVR状态
|
||||
if hasattr(self.world, 'alvr_streamer') and self.world.alvr_streamer.is_streaming():
|
||||
status = self.world.alvr_streamer.get_streaming_status()
|
||||
print(f" ALVR FPS: {status['fps']}, 延迟: {status['latency']}ms")
|
||||
|
||||
last_report_time = current_time
|
||||
|
||||
time.sleep(0.016) # ~60 FPS目标
|
||||
|
||||
final_fps = frame_count / (time.time() - start_time)
|
||||
print(f"✓ 性能监控完成,平均FPS: {final_fps:.1f}")
|
||||
|
||||
return final_fps
|
||||
|
||||
def auto_optimize(self):
|
||||
"""自动优化VR显示性能"""
|
||||
print("🔧 开始自动VR优化...")
|
||||
|
||||
# 1. 优化VR渲染
|
||||
if self.optimize_vr_rendering():
|
||||
print(" ✓ VR渲染优化完成")
|
||||
|
||||
# 2. 优化ALVR串流
|
||||
if hasattr(self.world, 'alvr_streamer'):
|
||||
if self.optimize_alvr_streaming():
|
||||
print(" ✓ ALVR串流优化完成")
|
||||
|
||||
# 3. 监控性能
|
||||
print("\n📊 开始性能测试...")
|
||||
fps = self.monitor_performance(5)
|
||||
|
||||
# 4. 根据性能调整设置
|
||||
if fps < 45: # VR最低要求
|
||||
print("⚠ 性能不足,降低质量设置...")
|
||||
self._apply_low_quality_settings()
|
||||
elif fps > 80:
|
||||
print("✓ 性能良好,可以提高质量设置")
|
||||
self._apply_high_quality_settings()
|
||||
|
||||
print("✓ 自动VR优化完成")
|
||||
|
||||
def _apply_low_quality_settings(self):
|
||||
"""应用低质量设置"""
|
||||
try:
|
||||
# 降低渲染分辨率
|
||||
if self.vr_manager:
|
||||
self.vr_manager.render_width = int(self.vr_manager.render_width * 0.8)
|
||||
self.vr_manager.render_height = int(self.vr_manager.render_height * 0.8)
|
||||
|
||||
# 降低ALVR质量
|
||||
if hasattr(self.world, 'alvr_streamer'):
|
||||
self.world.alvr_streamer.set_stream_quality(1920, 1080, 60, 60)
|
||||
|
||||
print(" ✓ 低质量设置已应用")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 低质量设置应用失败: {str(e)}")
|
||||
|
||||
def _apply_high_quality_settings(self):
|
||||
"""应用高质量设置"""
|
||||
try:
|
||||
# 提高ALVR质量
|
||||
if hasattr(self.world, 'alvr_streamer'):
|
||||
self.world.alvr_streamer.set_stream_quality(2880, 1700, 90, 200)
|
||||
|
||||
print(" ✓ 高质量设置已应用")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 高质量设置应用失败: {str(e)}")
|
||||
|
||||
|
||||
# 使用示例
|
||||
def optimize_vr_display(world):
|
||||
"""优化VR显示的便捷函数"""
|
||||
optimizer = VRDisplayOptimizer(world)
|
||||
optimizer.auto_optimize()
|
||||
return optimizer
|
||||
@ -1,263 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
VR快速启动脚本
|
||||
一键启动VR显示功能,包括自动优化和ALVR串流
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from main import MyWorld
|
||||
from vr_display_optimizer import VRDisplayOptimizer
|
||||
|
||||
|
||||
def quick_vr_setup():
|
||||
"""快速VR设置"""
|
||||
print("🚀 VR快速启动")
|
||||
print("=" * 40)
|
||||
|
||||
# 创建世界实例
|
||||
print("🌍 初始化引擎...")
|
||||
world = MyWorld()
|
||||
|
||||
# 创建优化器
|
||||
optimizer = VRDisplayOptimizer(world)
|
||||
|
||||
# 设置基本场景
|
||||
print("🎨 设置VR演示场景...")
|
||||
setup_demo_scene(world)
|
||||
|
||||
# 初始化VR系统
|
||||
print("🥽 启动VR系统...")
|
||||
vr_success = world.initializeVR()
|
||||
|
||||
if not vr_success:
|
||||
print("❌ VR初始化失败")
|
||||
return None, None
|
||||
|
||||
print("✅ VR系统已启动")
|
||||
|
||||
# 优化VR性能
|
||||
print("⚡ 优化VR性能...")
|
||||
optimizer.optimize_vr_rendering()
|
||||
|
||||
# 尝试启动ALVR
|
||||
print("📡 尝试启动ALVR串流...")
|
||||
alvr_success = world.initializeALVR()
|
||||
|
||||
if alvr_success:
|
||||
optimizer.optimize_alvr_streaming()
|
||||
if world.startALVRStreaming():
|
||||
print("✅ ALVR串流已启动")
|
||||
else:
|
||||
print("⚠️ ALVR串流启动失败")
|
||||
else:
|
||||
print("⚠️ ALVR初始化失败(正常,如果没有服务器)")
|
||||
|
||||
# 启动VR输入
|
||||
print("🎮 启动VR交互...")
|
||||
if world.startVRInput():
|
||||
world.showControllerRays(True)
|
||||
world.setVRInteractionEnabled(True)
|
||||
print("✅ VR交互已启用")
|
||||
|
||||
# 显示状态信息
|
||||
print_vr_status(world)
|
||||
|
||||
return world, optimizer
|
||||
|
||||
|
||||
def setup_demo_scene(world):
|
||||
"""设置演示场景"""
|
||||
try:
|
||||
from panda3d.core import CardMaker, Vec4, AmbientLight, DirectionalLight
|
||||
import math
|
||||
|
||||
# 添加光照
|
||||
ambient = AmbientLight('ambient')
|
||||
ambient.setColor(Vec4(0.4, 0.4, 0.4, 1))
|
||||
ambient_np = world.render.attachNewNode(ambient)
|
||||
world.render.setLight(ambient_np)
|
||||
|
||||
directional = DirectionalLight('sun')
|
||||
directional.setColor(Vec4(1, 1, 0.9, 1))
|
||||
directional.setDirection(Vec4(-1, -1, -1, 0))
|
||||
sun_np = world.render.attachNewNode(directional)
|
||||
world.render.setLight(sun_np)
|
||||
|
||||
# 创建地面
|
||||
cm = CardMaker("ground")
|
||||
cm.setFrame(-20, 20, -20, 20)
|
||||
ground = world.render.attachNewNode(cm.generate())
|
||||
ground.setPos(0, 0, -2)
|
||||
ground.setHpr(0, -90, 0)
|
||||
ground.setColor(0.3, 0.7, 0.3, 1)
|
||||
|
||||
# 创建彩色立方体环
|
||||
for i in range(12):
|
||||
cm_cube = CardMaker(f"cube_{i}")
|
||||
cm_cube.setFrame(-0.5, 0.5, -0.5, 0.5)
|
||||
cube = world.render.attachNewNode(cm_cube.generate())
|
||||
|
||||
angle = i * math.pi * 2 / 12
|
||||
radius = 5
|
||||
height = math.sin(i * 0.5) * 2
|
||||
|
||||
cube.setPos(math.cos(angle) * radius, math.sin(angle) * radius, height)
|
||||
cube.setHpr(i * 30, 0, 0)
|
||||
cube.setScale(1, 1, 2 + height * 0.5)
|
||||
|
||||
# HSV到RGB转换
|
||||
h = i / 12.0 * 360
|
||||
s = 0.8
|
||||
v = 1.0
|
||||
r, g, b = hsv_to_rgb(h, s, v)
|
||||
cube.setColor(r, g, b, 1)
|
||||
|
||||
print(" ✅ 演示场景创建完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 场景创建失败: {str(e)}")
|
||||
|
||||
|
||||
def hsv_to_rgb(h, s, v):
|
||||
"""HSV到RGB颜色转换"""
|
||||
import math
|
||||
h = h / 60.0
|
||||
c = v * s
|
||||
x = c * (1 - abs((h % 2) - 1))
|
||||
m = v - c
|
||||
|
||||
if 0 <= h < 1:
|
||||
r, g, b = c, x, 0
|
||||
elif 1 <= h < 2:
|
||||
r, g, b = x, c, 0
|
||||
elif 2 <= h < 3:
|
||||
r, g, b = 0, c, x
|
||||
elif 3 <= h < 4:
|
||||
r, g, b = 0, x, c
|
||||
elif 4 <= h < 5:
|
||||
r, g, b = x, 0, c
|
||||
else:
|
||||
r, g, b = c, 0, x
|
||||
|
||||
return r + m, g + m, b + m
|
||||
|
||||
|
||||
def print_vr_status(world):
|
||||
"""显示VR状态信息"""
|
||||
print("\n📊 VR系统状态")
|
||||
print("-" * 30)
|
||||
|
||||
if world.isVREnabled():
|
||||
vr_info = world.getVRInfo()
|
||||
print(f"🥽 VR模式: {vr_info['mode']}")
|
||||
print(f"📐 渲染尺寸: {vr_info['render_size']}")
|
||||
print(f"🎮 模拟模式: {vr_info['simulation_mode']}")
|
||||
|
||||
if world.isALVRConnected():
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f"📡 ALVR状态: {'串流中' if world.isALVRStreaming() else '已连接'}")
|
||||
print(f"🎥 分辨率: {status['resolution']}")
|
||||
print(f"⚡ FPS: {status['fps']}")
|
||||
else:
|
||||
print("📡 ALVR状态: 未连接")
|
||||
else:
|
||||
print("🥽 VR状态: 未启用")
|
||||
|
||||
|
||||
def run_vr_experience(world, optimizer, duration=30):
|
||||
"""运行VR体验"""
|
||||
print(f"\n🎮 VR体验开始 ({duration}秒)")
|
||||
print("💡 控制说明:")
|
||||
print(" - 观察窗口中的立体视图")
|
||||
print(" - VR头盔中可看到完整场景")
|
||||
print(" - 控制器射线用于交互")
|
||||
print(" - 按Ctrl+C退出")
|
||||
|
||||
start_time = time.time()
|
||||
frame_count = 0
|
||||
|
||||
try:
|
||||
while time.time() - start_time < duration:
|
||||
# 处理引擎更新
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
|
||||
frame_count += 1
|
||||
|
||||
# 每5秒显示状态
|
||||
elapsed = time.time() - start_time
|
||||
if frame_count % 300 == 0: # ~5秒
|
||||
fps = frame_count / elapsed
|
||||
print(f" 📈 运行状态: {elapsed:.1f}s, FPS: {fps:.1f}")
|
||||
|
||||
if world.isALVRStreaming():
|
||||
alvr_status = world.getALVRStreamingStatus()
|
||||
print(f" 📡 ALVR: {alvr_status['fps']} FPS, {alvr_status['latency']}ms延迟")
|
||||
|
||||
time.sleep(0.016) # ~60 FPS
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⏹️ 用户中断VR体验")
|
||||
|
||||
final_fps = frame_count / (time.time() - start_time)
|
||||
print(f"✅ VR体验完成,平均FPS: {final_fps:.1f}")
|
||||
|
||||
|
||||
def cleanup_vr(world):
|
||||
"""清理VR资源"""
|
||||
print("\n🧹 清理VR资源...")
|
||||
|
||||
try:
|
||||
if hasattr(world, 'stopVRInput'):
|
||||
world.stopVRInput()
|
||||
|
||||
if hasattr(world, 'stopALVRStreaming'):
|
||||
world.stopALVRStreaming()
|
||||
|
||||
if hasattr(world, 'shutdownALVR'):
|
||||
world.shutdownALVR()
|
||||
|
||||
if hasattr(world, 'shutdownVR'):
|
||||
world.shutdownVR()
|
||||
|
||||
print("✅ VR资源清理完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ VR清理失败: {str(e)}")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🎮 VR引擎快速启动")
|
||||
print("=" * 50)
|
||||
|
||||
# 快速设置VR
|
||||
world, optimizer = quick_vr_setup()
|
||||
|
||||
if not world:
|
||||
print("❌ VR启动失败")
|
||||
return
|
||||
|
||||
try:
|
||||
# 运行VR体验
|
||||
run_vr_experience(world, optimizer, 60) # 60秒演示
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ VR运行错误: {str(e)}")
|
||||
|
||||
finally:
|
||||
# 清理资源
|
||||
cleanup_vr(world)
|
||||
|
||||
print("\n🎉 VR体验结束")
|
||||
print("💡 要在Quest中体验:")
|
||||
print(" 1. 安装ALVR服务器和客户端")
|
||||
print(" 2. 连接同一WiFi网络")
|
||||
print(" 3. 重新运行此脚本")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,264 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
VR串流测试脚本
|
||||
专门用于测试和验证ALVR串流功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from main import MyWorld
|
||||
|
||||
|
||||
def test_vr_streaming():
|
||||
"""测试VR串流功能"""
|
||||
print("=== VR串流功能测试 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 初始化VR系统
|
||||
print("🥽 初始化VR系统...")
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
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']}")
|
||||
|
||||
# 检查是否为直连模式(SteamVR已运行)
|
||||
if world.alvr_streamer._is_steamvr_active():
|
||||
print("\n💡 检测到SteamVR正在运行,将使用直连模式")
|
||||
print(" 在直连模式下,画面将直接通过SteamVR传输到头显")
|
||||
print(" 无需通过ALVR服务器进行额外的串流处理")
|
||||
|
||||
# 初始化ALVR串流
|
||||
print("\n📡 初始化ALVR串流...")
|
||||
print("💡 检测到您已连接VR设备,正在尝试连接ALVR服务器...")
|
||||
alvr_success = world.initializeALVR()
|
||||
|
||||
if alvr_success:
|
||||
print("✓ ALVR串流初始化成功")
|
||||
|
||||
# 设置串流质量
|
||||
print("\n🎥 配置串流质量...")
|
||||
quality_set = world.setALVRStreamQuality(1920, 1080, 60, 50)
|
||||
if quality_set:
|
||||
print(" ✓ 串流质量设置成功: 1920x1080@60fps, 50Mbps")
|
||||
|
||||
# 获取串流状态
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f"\n📊 ALVR串流初始状态:")
|
||||
print(f" - 连接状态: {status['connected']}")
|
||||
print(f" - 串流状态: {status['streaming']}")
|
||||
print(f" - 分辨率: {status['resolution']}")
|
||||
print(f" - 帧率: {status['fps']}")
|
||||
print(f" - 码率: {status['bitrate']}Mbps")
|
||||
|
||||
# 开始串流
|
||||
print(f"\n🚀 开始ALVR串流...")
|
||||
if world.startALVRStreaming():
|
||||
print("✓ ALVR串流已开始")
|
||||
|
||||
# 运行串流测试
|
||||
print(f"\n📡 ALVR串流测试运行中... (10秒)")
|
||||
start_time = time.time()
|
||||
frame_count = 0
|
||||
|
||||
while time.time() - start_time < 10:
|
||||
# 更新串流状态
|
||||
current_status = world.getALVRStreamingStatus()
|
||||
|
||||
# 每2秒显示一次状态更新
|
||||
if frame_count % 120 == 0: # 约每2秒
|
||||
elapsed = time.time() - start_time
|
||||
print(f" [{elapsed:.1f}s] FPS: {current_status['fps']}, 延迟: {current_status['latency']}ms")
|
||||
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
frame_count += 1
|
||||
time.sleep(0.016)
|
||||
|
||||
# 停止串流
|
||||
print("\n🛑 停止ALVR串流...")
|
||||
world.stopALVRStreaming()
|
||||
print("✓ ALVR串流已停止")
|
||||
|
||||
else:
|
||||
print("⚠ ALVR串流启动失败")
|
||||
print(" 可能原因:")
|
||||
print(" - ALVR服务器未运行")
|
||||
print(" - VR客户端未连接")
|
||||
print(" - 网络连接问题")
|
||||
|
||||
else:
|
||||
print("⚠ ALVR串流初始化失败")
|
||||
print("💡 根据您提供的信息,您已经可以通过SteamVR看到画面,但串流测试无法连接到ALVR服务器")
|
||||
print("\n💡 直连模式说明:")
|
||||
print(" 如果您使用的是有线连接(如Pico 4通过USB-C连接)")
|
||||
print(" 并且SteamVR已经可以正常显示画面,您实际上已经在使用直连模式")
|
||||
print(" SteamVR会直接处理渲染并将画面传输到头显,无需额外的串流步骤")
|
||||
print("\n🔧 建议操作:")
|
||||
print(" 1. 确认SteamVR是否正常工作并显示画面")
|
||||
print(" 2. 如果正常工作,您无需进行额外的ALVR串流设置")
|
||||
print(" 3. 如果需要使用ALVR的额外功能,可以尝试:")
|
||||
print(" - 关闭SteamVR")
|
||||
print(" - 确保ALVR服务器正在运行")
|
||||
print(" - 重新运行测试")
|
||||
|
||||
# 提供替代方案
|
||||
print("\n🔄 替代方案:")
|
||||
print(" 您可以尝试运行:")
|
||||
print(" python3 vr_display_demo.py")
|
||||
print(" 并选择'2. ALVR串流演示'来测试串流功能")
|
||||
|
||||
# 关闭系统
|
||||
world.shutdownALVR()
|
||||
world.shutdownVR()
|
||||
print("\n✓ 所有系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_vr_streaming_detailed():
|
||||
"""详细测试VR串流功能"""
|
||||
print("=== VR串流详细功能测试 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 初始化VR系统
|
||||
print("🥽 初始化VR系统...")
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
print("✓ VR系统初始化成功")
|
||||
|
||||
# 检查VR纹理是否正确创建
|
||||
print("\n🔍 检查VR纹理...")
|
||||
if hasattr(world.vr_manager, 'left_eye_texture') and world.vr_manager.left_eye_texture:
|
||||
print(" ✓ 左眼纹理已创建")
|
||||
print(f" 纹理尺寸: {world.vr_manager.left_eye_texture.getXSize()}x{world.vr_manager.left_eye_texture.getYSize()}")
|
||||
else:
|
||||
print(" ✗ 左眼纹理未创建")
|
||||
|
||||
if hasattr(world.vr_manager, 'right_eye_texture') and world.vr_manager.right_eye_texture:
|
||||
print(" ✓ 右眼纹理已创建")
|
||||
print(f" 纹理尺寸: {world.vr_manager.right_eye_texture.getXSize()}x{world.vr_manager.right_eye_texture.getYSize()}")
|
||||
else:
|
||||
print(" ✗ 右眼纹理未创建")
|
||||
|
||||
# 初始化ALVR串流
|
||||
print("\n📡 初始化ALVR串流...")
|
||||
alvr_success = world.initializeALVR()
|
||||
|
||||
if alvr_success:
|
||||
print("✓ ALVR串流初始化成功")
|
||||
|
||||
# 测试多种串流质量设置
|
||||
print("\n🎥 测试多种串流质量设置:")
|
||||
quality_settings = [
|
||||
(1280, 720, 60, 25),
|
||||
(1920, 1080, 60, 50),
|
||||
(1920, 1080, 30, 25)
|
||||
]
|
||||
|
||||
for width, height, fps, bitrate in quality_settings:
|
||||
success = world.setALVRStreamQuality(width, height, fps, bitrate)
|
||||
if success:
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f" ✓ 设置 {width}x{height}@{fps}fps, {bitrate}Mbps: 成功")
|
||||
print(f" 实际分辨率: {status['resolution']}")
|
||||
else:
|
||||
print(f" ✗ 设置 {width}x{height}@{fps}fps, {bitrate}Mbps: 失败")
|
||||
|
||||
# 测试串流控制
|
||||
print("\n🎮 测试串流控制:")
|
||||
|
||||
# 开始串流
|
||||
if world.startALVRStreaming():
|
||||
print(" ✓ 串流开始成功")
|
||||
|
||||
# 检查串流状态
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f" 串流状态: {status['streaming']}")
|
||||
print(f" 连接状态: {status['connected']}")
|
||||
|
||||
# 等待2秒
|
||||
time.sleep(2)
|
||||
|
||||
# 再次检查状态
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f" 2秒后状态 - FPS: {status['fps']}, 延迟: {status['latency']}ms")
|
||||
|
||||
# 停止串流
|
||||
world.stopALVRStreaming()
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f" ✓ 串流停止成功")
|
||||
print(f" 串流状态: {status['streaming']}")
|
||||
|
||||
else:
|
||||
print(" ✗ 串流开始失败")
|
||||
|
||||
else:
|
||||
print("⚠ ALVR串流初始化失败")
|
||||
print("\n🔧 Pico 4连接故障排除:")
|
||||
print(" 1. 确认ALVR服务器正在运行")
|
||||
print(" 2. 检查USB连接是否正常")
|
||||
print(" 3. 确认Pico 4上已安装并启动ALVR客户端")
|
||||
print(" 4. 检查防火墙设置")
|
||||
print(" 5. 确认SteamVR可以识别到头显")
|
||||
|
||||
# 关闭系统
|
||||
world.shutdownALVR()
|
||||
world.shutdownVR()
|
||||
print("\n✓ 所有系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🎮 VR串流测试脚本")
|
||||
print("="*50)
|
||||
|
||||
print("\n请选择测试类型:")
|
||||
print("1. 基本串流测试")
|
||||
print("2. 详细串流测试")
|
||||
|
||||
try:
|
||||
choice = input("请输入选择 (1-2): ")
|
||||
|
||||
if choice == "1":
|
||||
success = test_vr_streaming()
|
||||
elif choice == "2":
|
||||
success = test_vr_streaming_detailed()
|
||||
else:
|
||||
print("无效的选择")
|
||||
return
|
||||
|
||||
print("\n" + "="*50)
|
||||
if success:
|
||||
print("✓ 串流测试成功完成")
|
||||
else:
|
||||
print("✗ 串流测试遇到问题")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n用户中断测试")
|
||||
except Exception as e:
|
||||
print(f"测试过程中发生错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
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