Merge remote-tracking branch 'refs/remotes/origin/main' into addRender

# Conflicts:
#	RenderPipelineFile/samples/06-Car/main.py
#	main.py
#	ui/main_window.py
This commit is contained in:
Hector 2025-09-29 11:24:45 +08:00
commit 9633a32c3b
32 changed files with 9068 additions and 3128 deletions

112
CLAUDE.md Normal file
View File

@ -0,0 +1,112 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 项目概述
这是一个基于Panda3D的3D渲染引擎和场景编辑器集成了PyQt5界面和多种高级功能
- 3D场景编辑器模型导入、材质系统、碰撞检测
- GUI元素管理2D/3D GUI组件
- 项目管理系统(场景保存/加载、项目打包)
- Cesium地图集成
- 渲染管线增强RenderPipelineFile
## 运行和构建命令
### 启动应用程序
```bash
python Start_Run.py [project_path]
```
或者直接:
```bash
python main.py
```
### 依赖安装
```bash
# 主要依赖
pip install -r requirements/requirements.txt
# Conda环境依赖
pip install -r requirements/conda-requirements.txt
```
### 工具脚本
```bash
# 安装FBX到GLTF转换工具
./install_fbx2gltf.sh
```
## 核心架构
### 主要模块结构
```
EG/
├── main.py # 应用程序入口点
├── Start_Run.py # 启动脚本(路径配置)
├── core/ # 核心功能模块
│ ├── world.py # 3D世界核心继承Panda3DWorld
│ ├── scene_manager.py # 场景和模型管理
│ ├── selection.py # 对象选择系统
│ ├── event_handler.py # 事件处理
│ └── tool_manager.py # 工具系统
├── gui/ # GUI元素管理
│ └── gui_manager.py # 2D/3D GUI组件
├── ui/ # 用户界面
│ ├── widgets.py # 自定义Qt组件
│ ├── property_panel.py # 属性面板
│ └── interface_manager.py # 界面管理
├── scene/ # 场景相关
│ └── scene_manager.py # 场景管理器
├── project/ # 项目管理
│ └── project_manager.py # 项目生命周期
├── RenderPipelineFile/ # 渲染管线扩展
└── QPanda3D/ # Panda3D Qt集成
```
### 核心设计模式
1. **模块化架构**: 每个功能模块独立,通过管理器类协调
2. **事件驱动**: EventHandler统一处理用户交互和系统事件
3. **组件系统**: SelectionSystem、ToolManager等可插拔组件
4. **MVC分离**: UI组件、核心逻辑和数据管理分离
### 主要依赖集成
- **Panda3D 1.10.15**: 3D渲染引擎
- **PyQt5**: GUI框架
- **QPanda3D**: Panda3D的Qt集成
- **RenderPipeline**: 高级渲染功能
## 开发指南
### 添加新功能模块
1. 在对应目录下创建新的Python文件
2. 继承相应的基类如Panda3DWorld用于3D功能
3. 在main.py中集成新模块
4. 更新界面管理器以添加UI控制
### 材质和渲染
- 材质系统集成在scene/scene_manager.py
- 支持PBR材质和自定义着色器
- RenderPipelineFile提供高级渲染特性
### GUI开发
- 使用PyQt5构建主界面
- 3D GUI元素通过gui/gui_manager.py管理
- 自定义组件在ui/widgets.py中定义
## 文件约定
- Python文件使用UTF-8编码
- 中文注释和文档字符串
- 模块顶部包含功能描述注释
- 类和方法使用描述性命名
## 注意事项
- 项目依赖多个大型库Panda3D、PyQt5、RenderPipeline
- Cesium集成需要WebEngine支持
- 某些功能可能需要特定的系统配置

View File

@ -70,6 +70,14 @@ class Panda3DWorld(ShowBase):
loadPrcFileData("", f"win-size {width} {height}")
loadPrcFileData("", "win-fixed-size #f") # 允许窗口调整大小
# 🚀 VR性能优化配置
loadPrcFileData("", "prefer-single-buffer true") # 减少缓冲区交换开销
loadPrcFileData("", "gl-force-flush false") # 避免强制glFlush导致的性能损失
loadPrcFileData("", "sync-video false") # 禁用默认VSync让OpenVR控制
loadPrcFileData("", "support-stencil false") # 禁用不必要的模板缓冲区
loadPrcFileData("", "clock-mode non-real-time") # 禁用Panda3D帧率控制让OpenVR控制
# loadPrcFileData("", "gl-debug true") # 调试时可启用OpenGL调试
if (is_fullscreen):
loadPrcFileData("", "fullscreen #t")

View File

@ -53,7 +53,7 @@ class Application(ShowBase):
# ------ End of render pipeline code, thats it! ------
# Set time of day
self.render_pipeline.daytime_mgr.time = "12:43"
self.render_pipeline.daytime_mgr.time = "6:43"
# Load the scene
model = loader.loadModel("scene/Scene.bam")

View File

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

View File

@ -1,509 +0,0 @@
"""
ALVR串流处理器
负责与ALVR服务器通信和视频流传输
支持Quest等VR头显的无线串流
"""
import socket
import struct
import threading
import json
import time
import subprocess
import psutil
from direct.showbase.DirectObject import DirectObject
from panda3d.core import Texture, PNMImage
class ALVRStreamer(DirectObject):
"""ALVR串流处理器"""
def __init__(self, world, vr_manager):
super().__init__()
self.world = world
self.vr_manager = vr_manager
# ALVR服务器配置
self.alvr_server_ip = "127.0.0.1"
self.alvr_server_port = 9943
self.alvr_streaming_port = 9944
# 连接状态
self.connected = False
self.streaming = False
self.server_socket = None
self.streaming_socket = None
# 流媒体配置
self.stream_width = 2880 # Quest 2 推荐分辨率
self.stream_height = 1700
self.stream_fps = 72
self.bitrate = 150 # Mbps
self.codec = "h264"
# 线程管理
self.connection_thread = None
self.streaming_thread = None
self.running = False
# 性能统计
self.frame_count = 0
self.last_fps_time = time.time()
self.current_fps = 0
self.latency = 0
print("✓ ALVR串流处理器初始化完成")
def initialize(self):
"""初始化ALVR串流"""
try:
# 检查ALVR服务器是否运行
if not self._check_alvr_server():
print("ALVR服务器未运行尝试启动...")
if not self._start_alvr_server():
print("无法启动ALVR服务器")
return False
# 连接到ALVR服务器
if not self._connect_to_server():
print("无法连接到ALVR服务器")
return False
# 配置流媒体设置
self._configure_streaming()
# 启动串流线程
self._start_streaming_threads()
print("✓ ALVR串流初始化成功")
return True
except Exception as e:
print(f"ALVR初始化错误: {str(e)}")
return False
def _check_alvr_server(self):
"""检查ALVR服务器是否运行"""
try:
# 检查进程
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
if 'alvr' in proc.info['name'].lower():
return True
# 尝试连接端口
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2)
result = sock.connect_ex((self.alvr_server_ip, self.alvr_server_port))
sock.close()
return result == 0
except Exception as e:
print(f"检查ALVR服务器错误: {str(e)}")
return False
def _start_alvr_server(self):
"""启动ALVR服务器"""
try:
# 尝试启动ALVR服务器
# 这里需要根据实际的ALVR安装路径调整
alvr_paths = [
"/usr/local/bin/alvr_server",
"/usr/bin/alvr_server",
"C:/Program Files/ALVR/alvr_server.exe",
"C:/ALVR/alvr_server.exe"
]
for path in alvr_paths:
try:
subprocess.Popen([path], shell=True)
time.sleep(3) # 等待服务器启动
if self._check_alvr_server():
return True
except FileNotFoundError:
continue
return False
except Exception as e:
print(f"启动ALVR服务器错误: {str(e)}")
return False
def _connect_to_server(self):
"""连接到ALVR服务器"""
try:
# 创建TCP连接
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.settimeout(5)
self.server_socket.connect((self.alvr_server_ip, self.alvr_server_port))
# 发送握手消息
handshake_data = {
"type": "handshake",
"client_name": "Panda3D_VR_Engine",
"version": "1.0",
"capabilities": {
"video": True,
"audio": True,
"tracking": True,
"haptics": True
}
}
self._send_message(handshake_data)
# 接收响应
response = self._receive_message()
if response and response.get("type") == "handshake_response":
self.connected = True
print("✓ 已连接到ALVR服务器")
return True
return False
except Exception as e:
print(f"连接ALVR服务器错误: {str(e)}")
return False
def _send_message(self, data):
"""发送消息到ALVR服务器"""
try:
message = json.dumps(data).encode('utf-8')
length = struct.pack('<I', len(message))
self.server_socket.send(length + message)
except Exception as e:
print(f"发送消息错误: {str(e)}")
def _receive_message(self):
"""从ALVR服务器接收消息"""
try:
# 接收消息长度
length_data = self.server_socket.recv(4)
if not length_data:
return None
length = struct.unpack('<I', length_data)[0]
# 接收消息内容
message_data = b''
while len(message_data) < length:
chunk = self.server_socket.recv(length - len(message_data))
if not chunk:
return None
message_data += chunk
return json.loads(message_data.decode('utf-8'))
except Exception as e:
print(f"接收消息错误: {str(e)}")
return None
def _configure_streaming(self):
"""配置流媒体设置"""
try:
# 发送流媒体配置
config_data = {
"type": "stream_config",
"video": {
"width": self.stream_width,
"height": self.stream_height,
"fps": self.stream_fps,
"bitrate": self.bitrate,
"codec": self.codec
},
"audio": {
"enabled": True,
"sample_rate": 48000,
"channels": 2
}
}
self._send_message(config_data)
# 接收配置响应
response = self._receive_message()
if response and response.get("type") == "config_response":
if response.get("status") == "success":
print("✓ 流媒体配置成功")
return True
return False
except Exception as e:
print(f"配置流媒体错误: {str(e)}")
return False
def _start_streaming_threads(self):
"""启动串流线程"""
self.running = True
# 启动连接管理线程
self.connection_thread = threading.Thread(target=self._connection_handler)
self.connection_thread.daemon = True
self.connection_thread.start()
# 启动流媒体线程
self.streaming_thread = threading.Thread(target=self._streaming_handler)
self.streaming_thread.daemon = True
self.streaming_thread.start()
def _connection_handler(self):
"""连接处理线程"""
while self.running:
try:
if self.connected:
# 发送心跳
heartbeat = {"type": "heartbeat", "timestamp": time.time()}
self._send_message(heartbeat)
# 接收消息
response = self._receive_message()
if response:
self._handle_server_message(response)
time.sleep(0.1)
except Exception as e:
print(f"连接处理错误: {str(e)}")
self.connected = False
time.sleep(1)
def _streaming_handler(self):
"""流媒体处理线程"""
while self.running:
try:
if self.connected and self.streaming:
# 获取VR渲染帧
frame_data = self._get_vr_frame()
if frame_data:
# 发送帧数据
self._send_frame(frame_data)
# 更新性能统计
self._update_performance_stats()
time.sleep(1.0 / self.stream_fps)
except Exception as e:
print(f"流媒体处理错误: {str(e)}")
time.sleep(0.1)
def _handle_server_message(self, message):
"""处理服务器消息"""
msg_type = message.get("type")
if msg_type == "start_streaming":
self.streaming = True
print("✓ 开始VR串流")
elif msg_type == "stop_streaming":
self.streaming = False
print("✓ 停止VR串流")
elif msg_type == "client_connected":
print(f"✓ VR客户端已连接: {message.get('client_info', {})}")
elif msg_type == "client_disconnected":
print("✓ VR客户端已断开")
elif msg_type == "tracking_data":
self._handle_tracking_data(message.get("data"))
elif msg_type == "haptic_feedback":
self._handle_haptic_feedback(message.get("data"))
def _handle_tracking_data(self, tracking_data):
"""处理跟踪数据"""
if not tracking_data:
return
# 更新VR管理器的跟踪数据
# 这里可以处理从ALVR客户端发送的跟踪数据
pass
def _handle_haptic_feedback(self, haptic_data):
"""处理触觉反馈"""
if not haptic_data:
return
# 处理触觉反馈请求
# 这里可以控制VR控制器的震动等
pass
def _get_vr_frame(self):
"""获取VR渲染帧"""
try:
if not self.vr_manager.is_vr_enabled():
return None
# 获取左右眼纹理
left_texture = self.vr_manager.eye_textures.get('left')
right_texture = self.vr_manager.eye_textures.get('right')
if not left_texture or not right_texture:
return None
# 合成立体帧
frame_data = self._compose_stereo_frame(left_texture, right_texture)
return frame_data
except Exception as e:
print(f"获取VR帧错误: {str(e)}")
return None
def _compose_stereo_frame(self, left_texture, right_texture):
"""合成立体帧"""
try:
# 创建组合图像
combined_image = PNMImage(self.stream_width, self.stream_height)
# 获取左右眼图像
left_image = PNMImage()
right_image = PNMImage()
if left_texture.store(left_image) and right_texture.store(right_image):
# 将左右眼图像合并Side-by-Side布局
left_width = self.stream_width // 2
# 缩放左眼图像到左半部分
left_scaled = PNMImage(left_width, self.stream_height)
left_scaled.quickFilterFrom(left_image)
combined_image.copySubImage(left_scaled, 0, 0)
# 缩放右眼图像到右半部分
right_scaled = PNMImage(left_width, self.stream_height)
right_scaled.quickFilterFrom(right_image)
combined_image.copySubImage(right_scaled, left_width, 0)
# 转换为字节数据
return combined_image.makeRamImage()
return None
except Exception as e:
print(f"合成立体帧错误: {str(e)}")
return None
def _send_frame(self, frame_data):
"""发送帧数据"""
try:
if not self.server_socket:
return
# 创建帧消息
frame_message = {
"type": "video_frame",
"timestamp": time.time(),
"width": self.stream_width,
"height": self.stream_height,
"format": "rgb",
"data": frame_data.hex() # 转换为十六进制字符串
}
self._send_message(frame_message)
except Exception as e:
print(f"发送帧错误: {str(e)}")
def _update_performance_stats(self):
"""更新性能统计"""
self.frame_count += 1
current_time = time.time()
if current_time - self.last_fps_time >= 1.0:
self.current_fps = self.frame_count
self.frame_count = 0
self.last_fps_time = current_time
def start_streaming(self):
"""开始串流"""
if not self.connected:
print("未连接到ALVR服务器")
return False
start_message = {"type": "start_streaming"}
self._send_message(start_message)
return True
def stop_streaming(self):
"""停止串流"""
if not self.connected:
return
stop_message = {"type": "stop_streaming"}
self._send_message(stop_message)
self.streaming = False
def send_haptic_feedback(self, controller_id, duration, intensity):
"""发送触觉反馈"""
if not self.connected:
return
haptic_message = {
"type": "haptic_feedback",
"controller_id": controller_id,
"duration": duration,
"intensity": intensity
}
self._send_message(haptic_message)
def get_streaming_status(self):
"""获取串流状态"""
return {
"connected": self.connected,
"streaming": self.streaming,
"fps": self.current_fps,
"latency": self.latency,
"resolution": f"{self.stream_width}x{self.stream_height}",
"bitrate": self.bitrate
}
def set_stream_quality(self, width, height, fps, bitrate):
"""设置串流质量"""
self.stream_width = width
self.stream_height = height
self.stream_fps = fps
self.bitrate = bitrate
# 如果正在串流,重新配置
if self.streaming:
self._configure_streaming()
def shutdown(self):
"""关闭串流"""
print("关闭ALVR串流...")
self.running = False
self.streaming = False
# 发送断开消息
if self.connected:
disconnect_message = {"type": "disconnect"}
self._send_message(disconnect_message)
# 关闭连接
if self.server_socket:
self.server_socket.close()
if self.streaming_socket:
self.streaming_socket.close()
# 等待线程结束
if self.connection_thread:
self.connection_thread.join(timeout=2)
if self.streaming_thread:
self.streaming_thread.join(timeout=2)
self.connected = False
print("✓ ALVR串流已关闭")
def is_connected(self):
"""检查是否连接"""
return self.connected
def is_streaming(self):
"""检查是否在串流"""
return self.streaming

583
core/vr_actions.py Normal file
View File

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

432
core/vr_controller.py Normal file
View File

@ -0,0 +1,432 @@
"""
VR手柄管理模块
基于panda3d-openvr参考实现提供完整的VR手柄追踪和交互功能
- 手柄位置和姿态追踪
- 按钮和触摸板输入处理
- 手柄可视化和射线显示
- 震动反馈支持
"""
from panda3d.core import (
NodePath, PandaNode, Vec3, Mat4, LVector3, LMatrix4,
GeomNode, LineSegs, CardMaker, Texture, RenderState,
TransparencyAttrib, ColorAttrib, Vec4
)
from direct.actor.Actor import Actor
from direct.showbase.DirectObject import DirectObject
try:
import openvr
OPENVR_AVAILABLE = True
except ImportError:
OPENVR_AVAILABLE = False
# 导入可视化器
from .vr_visualization import VRControllerVisualizer
class VRController(DirectObject):
"""VR手柄基类 - 管理单个手柄的追踪和交互"""
def __init__(self, vr_manager, name, hand_path, device_index=None):
"""初始化VR手柄
Args:
vr_manager: VR管理器实例
name: 手柄名称 ('left' 'right')
hand_path: OpenVR手部路径 ('/user/hand/left' '/user/hand/right')
device_index: OpenVR设备索引可选
"""
super().__init__()
self.vr_manager = vr_manager
self.name = name
self.hand_path = hand_path
self.device_index = device_index
# 手柄状态
self.is_connected = False
self.is_pose_valid = False
self.pose = Mat4.identMat()
self.velocity = Vec3(0, 0, 0)
self.angular_velocity = Vec3(0, 0, 0)
# 按钮状态
self.button_states = {}
self.previous_button_states = {}
self.trigger_value = 0.0
self.grip_value = 0.0
self.touchpad_pos = Vec3(0, 0, 0)
self.touchpad_touched = False
# 摇杆状态 - 用于传送和转向交互
self.joystick_pos = Vec3(0, 0, 0) # 摇杆位置 (x, y, 0)
self.joystick_touched = False # 摇杆是否被触摸
self.joystick_pressed = False # 摇杆是否被按下
self.previous_joystick_pos = Vec3(0, 0, 0) # 上一帧摇杆位置
# 3D节点和可视化
self.anchor_node = None
self.visualizer = None
self.ray_length = 10.0
# 初始化
self._create_anchor()
self._create_visualizer()
print(f"{name}手柄控制器初始化完成")
def _create_anchor(self):
"""创建手柄锚点节点"""
if self.vr_manager.tracking_space:
self.anchor_node = self.vr_manager.tracking_space.attachNewNode(f'{self.name}-controller')
self.anchor_node.hide() # 初始隐藏,直到获得有效姿态
def _create_visualizer(self):
"""创建手柄可视化器"""
if self.anchor_node and hasattr(self.vr_manager, 'world'):
self.visualizer = VRControllerVisualizer(self, self.vr_manager.world.render)
elif self.anchor_node:
# 如果没有世界对象,使用基础渲染节点
from panda3d.core import NodePath
render = NodePath('render')
self.visualizer = VRControllerVisualizer(self, render)
def set_device_index(self, device_index):
"""设置OpenVR设备索引"""
self.device_index = device_index
self.is_connected = True
print(f"📱 {self.name}手柄连接 (设备索引: {device_index})")
def update_pose(self, pose_data):
"""更新手柄姿态
Args:
pose_data: OpenVR TrackedDevicePose_t数据
"""
if not pose_data.bPoseIsValid:
self.is_pose_valid = False
if self.anchor_node:
self.anchor_node.hide()
return
self.is_pose_valid = True
# 转换OpenVR矩阵到Panda3D
if hasattr(self.vr_manager, 'convert_mat') and hasattr(self.vr_manager, 'coord_mat_inv') and hasattr(self.vr_manager, 'coord_mat'):
modelview = self.vr_manager.convert_mat(pose_data.mDeviceToAbsoluteTracking)
self.pose = self.vr_manager.coord_mat_inv * modelview * self.vr_manager.coord_mat
else:
# 直接使用矩阵数据
m = pose_data.mDeviceToAbsoluteTracking.m
self.pose = LMatrix4(
m[0][0], m[1][0], m[2][0], m[3][0],
m[0][1], m[1][1], m[2][1], m[3][1],
m[0][2], m[1][2], m[2][2], m[3][2],
m[0][3], m[1][3], m[2][3], m[3][3]
)
# 更新锚点变换
if self.anchor_node:
self.anchor_node.setMat(self.pose)
self.anchor_node.show()
# 更新可视化
if self.visualizer:
self.visualizer.update()
# 更新速度信息
vel = pose_data.vVelocity
self.velocity = Vec3(vel[0], vel[1], vel[2])
ang_vel = pose_data.vAngularVelocity
self.angular_velocity = Vec3(ang_vel[0], ang_vel[1], ang_vel[2])
def update_input_state(self, vr_system):
"""更新输入状态
Args:
vr_system: OpenVR系统实例
"""
if not self.is_connected or not OPENVR_AVAILABLE or not vr_system:
return
# 保存上一帧的按钮状态和摇杆位置
self.previous_button_states = self.button_states.copy()
self.previous_joystick_pos = Vec3(self.joystick_pos)
# 获取控制器状态
try:
result, state = vr_system.getControllerState(self.device_index)
if result:
# 更新按钮状态 - 使用正确的OpenVR属性名
for i in range(openvr.k_EButton_Max):
button_mask = 1 << i
# OpenVR Python绑定中使用ulButtonPressed而不是rButtonPressed
self.button_states[i] = (state.ulButtonPressed & button_mask) != 0
# 更新轴状态(扳机、握把、触摸板、摇杆)
# 兼容不同版本的OpenVR Python绑定
axis_data = None
if hasattr(state, 'rAxis'):
axis_data = state.rAxis # 旧版本使用rAxis
elif hasattr(state, 'vAxis'):
axis_data = state.vAxis # 新版本使用vAxis
if axis_data is not None and len(axis_data) > 0:
# 调试输出 - 显示所有轴数据(仅当有变化时)
self._debug_axis_data(axis_data)
# 扳机轴通常在axis[1].x
if len(axis_data) > 1:
self.trigger_value = axis_data[1].x
# 触摸板/摇杆轴通常在axis[0]
if len(axis_data) > 0:
self.touchpad_pos = Vec3(axis_data[0].x, axis_data[0].y, 0)
# 摇杆和触摸板通常使用同一个轴,但可以区分设备类型
self.joystick_pos = Vec3(axis_data[0].x, axis_data[0].y, 0)
# 额外检查其他轴(某些控制器可能将摇杆分配到不同轴)
if len(axis_data) > 2:
# 有些控制器可能在axis[2]有摇杆数据
axis2_magnitude = abs(axis_data[2].x) + abs(axis_data[2].y)
if axis2_magnitude > 0.1: # 如果有显著输入,使用这个轴作为摇杆
self.joystick_pos = Vec3(axis_data[2].x, axis_data[2].y, 0)
# 检查axis[3]和axis[4]Quest控制器可能使用这些轴
for axis_idx in range(3, min(len(axis_data), 5)):
axis_magnitude = abs(axis_data[axis_idx].x) + abs(axis_data[axis_idx].y)
if axis_magnitude > 0.1: # 如果有显著输入
# 覆盖之前的摇杆数据,使用最有活动的轴
self.joystick_pos = Vec3(axis_data[axis_idx].x, axis_data[axis_idx].y, 0)
# 调试输出
if not hasattr(self, '_last_axis_notify'):
self._last_axis_notify = {}
if self._last_axis_notify.get(axis_idx, 0) == 0:
print(f"🎮 {self.name}手检测到axis[{axis_idx}]活动: ({axis_data[axis_idx].x:.3f}, {axis_data[axis_idx].y:.3f})")
self._last_axis_notify[axis_idx] = 60 # 60帧后再次提醒
else:
self._last_axis_notify[axis_idx] -= 1
# 触摸板和摇杆触摸状态
self.touchpad_touched = (state.ulButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 0
# 摇杆触摸状态(检查多个可能的按钮)
joystick_touch_mask = 0
if hasattr(openvr, 'k_EButton_Joystick'):
joystick_touch_mask |= (1 << openvr.k_EButton_Joystick)
if hasattr(openvr, 'k_EButton_Thumbstick'):
joystick_touch_mask |= (1 << openvr.k_EButton_Thumbstick)
self.joystick_touched = (state.ulButtonTouched & joystick_touch_mask) != 0 or self.touchpad_touched
# 摇杆按下状态
joystick_press_mask = 0
if hasattr(openvr, 'k_EButton_Joystick'):
joystick_press_mask |= (1 << openvr.k_EButton_Joystick)
if hasattr(openvr, 'k_EButton_Thumbstick'):
joystick_press_mask |= (1 << openvr.k_EButton_Thumbstick)
self.joystick_pressed = (state.ulButtonPressed & joystick_press_mask) != 0
except Exception as e:
# 减少错误输出频率
if not hasattr(self, '_last_input_error_frame'):
self._last_input_error_frame = 0
# 获取当前帧数通过VR管理器
current_frame = getattr(self.vr_manager, 'frame_count', 0)
# 每5秒最多输出一次错误300帧@60fps
if current_frame - self._last_input_error_frame > 300:
print(f"⚠️ 更新{self.name}手柄输入状态失败: {e}")
self._last_input_error_frame = current_frame
def is_button_pressed(self, button_id):
"""检查按钮是否被按下"""
return self.button_states.get(button_id, False)
def is_button_just_pressed(self, button_id):
"""检查按钮是否刚刚被按下(上升沿)"""
current = self.button_states.get(button_id, False)
previous = self.previous_button_states.get(button_id, False)
return current and not previous
def is_button_just_released(self, button_id):
"""检查按钮是否刚刚被释放(下降沿)"""
current = self.button_states.get(button_id, False)
previous = self.previous_button_states.get(button_id, False)
return not current and previous
def is_trigger_pressed(self, threshold=0.1):
"""检查扳机是否被按下"""
return self.trigger_value > threshold
def is_grip_pressed(self, threshold=0.1):
"""检查握把是否被按下"""
return self.grip_value > threshold
def show_ray(self, show=True):
"""显示或隐藏交互射线"""
if self.visualizer:
if show:
self.visualizer.show_ray()
else:
self.visualizer.hide_ray()
def set_ray_color(self, color):
"""设置射线颜色"""
if self.visualizer and len(color) >= 3:
from panda3d.core import Vec4
color_vec = Vec4(color[0], color[1], color[2], color[3] if len(color) > 3 else 1.0)
self.visualizer.set_ray_color(color_vec)
def trigger_haptic_feedback(self, duration=0.001, strength=1.0):
"""触发震动反馈
Args:
duration: 震动持续时间
strength: 震动强度 (0.0-1.0)
"""
if not self.is_connected or not OPENVR_AVAILABLE:
return
try:
if hasattr(self.vr_manager, 'vr_system') and self.vr_manager.vr_system:
# OpenVR的震动API
duration_microseconds = int(duration * 1000000)
self.vr_manager.vr_system.triggerHapticPulse(
self.device_index,
0, # axis ID (通常为0)
int(strength * 3999) # 强度 (0-3999)
)
except Exception as e:
print(f"⚠️ {self.name}手柄震动反馈失败: {e}")
def get_world_position(self):
"""获取手柄在世界坐标系中的位置"""
if self.anchor_node:
return self.anchor_node.getPos(self.vr_manager.world.render)
return Vec3(0, 0, 0)
def get_world_rotation(self):
"""获取手柄在世界坐标系中的旋转"""
if self.anchor_node:
return self.anchor_node.getHpr(self.vr_manager.world.render)
return Vec3(0, 0, 0)
def get_forward_direction(self):
"""获取手柄指向的方向向量(包含视角转向)"""
if self.anchor_node:
# 获取相对于世界坐标系的方向包含tracking_space的旋转
if hasattr(self.vr_manager, 'world') and self.vr_manager.world:
# 使用世界变换,包含所有父节点的旋转
world_transform = self.anchor_node.getMat(self.vr_manager.world.render)
forward = Vec3(world_transform.getRow3(1)) # Y轴 = 前方
else:
# 备选:使用局部变换
forward = Vec3(self.anchor_node.getMat().getRow3(1))
if forward.length() > 0:
return forward.normalized()
return Vec3(0, 1, 0)
def is_joystick_touched(self):
"""检查摇杆是否被触摸"""
return self.joystick_touched
def is_joystick_pressed(self):
"""检查摇杆是否被按下"""
return self.joystick_pressed
def get_joystick_position(self):
"""获取摇杆位置
Returns:
Vec3: 摇杆位置 (x, y, 0)范围 [-1, 1]
"""
return Vec3(self.joystick_pos)
def get_joystick_delta(self):
"""获取摇杆位置变化
Returns:
Vec3: 摇杆位置变化向量
"""
return self.joystick_pos - self.previous_joystick_pos
def is_joystick_moved(self, threshold=0.01):
"""检查摇杆是否移动
Args:
threshold: 移动阈值
Returns:
bool: 是否移动
"""
delta = self.get_joystick_delta()
return delta.length() > threshold
def _debug_axis_data(self, axis_data):
"""调试输出轴数据"""
try:
# 只在有活动时输出调试信息
has_activity = False
active_axes = []
for i, axis in enumerate(axis_data):
magnitude = abs(axis.x) + abs(axis.y)
if magnitude > 0.01: # 检测到活动
has_activity = True
active_axes.append(f"axis[{i}]: ({axis.x:.3f}, {axis.y:.3f})")
if has_activity:
# 初始化调试计数器
if not hasattr(self, '_debug_axis_counter'):
self._debug_axis_counter = 0
self._debug_axis_counter += 1
# 每30帧输出一次详细信息
if self._debug_axis_counter % 30 == 1:
print(f"🔍 {self.name}手轴数据调试:")
print(f" 总轴数: {len(axis_data)}")
print(f" 活跃轴: {', '.join(active_axes)}")
# 显示所有轴的当前值(不管是否活跃)
all_axes = []
for i, axis in enumerate(axis_data):
all_axes.append(f"[{i}]:({axis.x:.3f},{axis.y:.3f})")
print(f" 所有轴: {' '.join(all_axes)}")
except Exception as e:
print(f"⚠️ 轴数据调试失败: {e}")
def cleanup(self):
"""清理资源"""
self.ignoreAll()
if self.visualizer:
self.visualizer.cleanup()
if self.anchor_node:
self.anchor_node.removeNode()
self.is_connected = False
print(f"🧹 {self.name}手柄控制器已清理")
class LeftController(VRController):
"""左手控制器"""
def __init__(self, vr_manager):
super().__init__(vr_manager, 'left', '/user/hand/left')
class RightController(VRController):
"""右手控制器"""
def __init__(self, vr_manager):
super().__init__(vr_manager, 'right', '/user/hand/right')

View File

@ -1,430 +0,0 @@
"""
VR输入处理器
处理VR控制器输入手势识别和VR交互
支持多种VR控制器和手势输入
"""
from direct.showbase.DirectObject import DirectObject
from panda3d.core import Vec3, Point3, CollisionRay, CollisionNode, CollisionHandlerQueue
from direct.task import Task
import time
class VRInputHandler(DirectObject):
"""VR输入处理器"""
def __init__(self, world, vr_manager):
super().__init__()
self.world = world
self.vr_manager = vr_manager
# 控制器状态
self.controllers = {}
self.controller_nodes = {}
self.controller_rays = {}
# 手势识别
self.gesture_enabled = True
self.gesture_history = []
self.gesture_threshold = 0.1
# 交互系统
self.interaction_enabled = True
self.selected_object = None
self.grab_offset = Vec3(0, 0, 0)
# 输入映射
self.input_mappings = {
'trigger': self._handle_trigger,
'grip': self._handle_grip,
'touchpad': self._handle_touchpad,
'menu': self._handle_menu,
'system': self._handle_system
}
print("✓ VR输入处理器初始化完成")
def start_input_handling(self):
"""启动输入处理"""
if not self.vr_manager.is_vr_enabled():
print("VR未启用无法启动输入处理")
return False
# 启动输入更新任务
self.world.taskMgr.add(self._update_input, "vr_input_update")
# 设置控制器可视化
self._setup_controller_visualization()
print("✓ VR输入处理已启动")
return True
def stop_input_handling(self):
"""停止输入处理"""
self.world.taskMgr.remove("vr_input_update")
self._cleanup_controller_visualization()
print("✓ VR输入处理已停止")
def _update_input(self, task):
"""更新输入处理"""
if not self.vr_manager.is_vr_enabled():
return Task.cont
try:
# 更新所有控制器
self._update_controllers()
# 处理手势识别
if self.gesture_enabled:
self._process_gestures()
# 处理交互
if self.interaction_enabled:
self._process_interactions()
except Exception as e:
print(f"VR输入更新错误: {str(e)}")
return Task.cont
def _update_controllers(self):
"""更新控制器状态"""
# 获取控制器姿态
controller_poses = self.vr_manager.controller_poses
for controller_id, pose in controller_poses.items():
# 获取控制器输入
input_data = self.vr_manager.get_controller_input(controller_id)
if not input_data:
continue
# 更新控制器状态
if controller_id not in self.controllers:
self.controllers[controller_id] = {}
prev_state = self.controllers[controller_id].copy()
self.controllers[controller_id] = input_data
# 更新控制器可视化
self._update_controller_visualization(controller_id, pose)
# 处理输入事件
self._process_controller_input(controller_id, input_data, prev_state)
def _process_controller_input(self, controller_id, current_state, prev_state):
"""处理控制器输入"""
# 检查按钮状态变化
for input_type, handler in self.input_mappings.items():
if input_type in current_state:
current_value = current_state[input_type]
prev_value = prev_state.get(input_type, 0)
# 处理按钮按下/释放
if isinstance(current_value, (int, float)):
if current_value > 0.5 and prev_value <= 0.5:
handler(controller_id, 'press', current_value)
elif current_value <= 0.5 and prev_value > 0.5:
handler(controller_id, 'release', current_value)
elif current_value > 0.5:
handler(controller_id, 'hold', current_value)
# 处理触摸板
elif isinstance(current_value, tuple) and len(current_value) == 2:
if current_value != prev_value:
handler(controller_id, 'move', current_value)
def _handle_trigger(self, controller_id, action, value):
"""处理扳机输入"""
if action == 'press':
print(f"控制器 {controller_id} 扳机按下 (强度: {value:.2f})")
self._try_grab_object(controller_id)
elif action == 'release':
print(f"控制器 {controller_id} 扳机释放")
self._try_release_object(controller_id)
def _handle_grip(self, controller_id, action, value):
"""处理握持输入"""
if action == 'press':
print(f"控制器 {controller_id} 握持按下 (强度: {value:.2f})")
self._toggle_interaction_mode(controller_id)
elif action == 'release':
print(f"控制器 {controller_id} 握持释放")
def _handle_touchpad(self, controller_id, action, value):
"""处理触摸板输入"""
if action == 'move':
x, y = value
print(f"控制器 {controller_id} 触摸板: ({x:.2f}, {y:.2f})")
# 根据触摸板位置执行不同操作
if abs(x) > 0.7: # 左右滑动
self._handle_horizontal_swipe(controller_id, x)
elif abs(y) > 0.7: # 上下滑动
self._handle_vertical_swipe(controller_id, y)
def _handle_menu(self, controller_id, action, value):
"""处理菜单按钮"""
if action == 'press':
print(f"控制器 {controller_id} 菜单按钮按下")
self._show_vr_menu(controller_id)
def _handle_system(self, controller_id, action, value):
"""处理系统按钮"""
if action == 'press':
print(f"控制器 {controller_id} 系统按钮按下")
# 系统按钮通常由VR系统处理
def _handle_horizontal_swipe(self, controller_id, direction):
"""处理水平滑动"""
if direction > 0:
print(f"控制器 {controller_id} 右滑")
self._switch_tool(controller_id, 'next')
else:
print(f"控制器 {controller_id} 左滑")
self._switch_tool(controller_id, 'prev')
def _handle_vertical_swipe(self, controller_id, direction):
"""处理垂直滑动"""
if direction > 0:
print(f"控制器 {controller_id} 上滑")
self._zoom_in(controller_id)
else:
print(f"控制器 {controller_id} 下滑")
self._zoom_out(controller_id)
def _try_grab_object(self, controller_id):
"""尝试抓取对象"""
if controller_id not in self.controllers:
return
# 获取控制器射线
ray = self._get_controller_ray(controller_id)
if not ray:
return
# 执行射线检测
hit_object = self._raycast_from_controller(controller_id)
if hit_object:
self.selected_object = hit_object
controller_pose = self.controllers[controller_id].get('pose')
if controller_pose:
# 计算抓取偏移
object_pos = hit_object.getPos()
controller_pos = controller_pose.getTranslate()
self.grab_offset = object_pos - controller_pos
print(f"抓取对象: {hit_object.getName()}")
# 发送抓取事件
self.world.event_handler.messenger.send('vr-object-grabbed', [hit_object, controller_id])
def _try_release_object(self, controller_id):
"""尝试释放对象"""
if self.selected_object:
print(f"释放对象: {self.selected_object.getName()}")
# 发送释放事件
self.world.event_handler.messenger.send('vr-object-released', [self.selected_object, controller_id])
self.selected_object = None
self.grab_offset = Vec3(0, 0, 0)
def _raycast_from_controller(self, controller_id):
"""从控制器发射射线检测"""
if controller_id not in self.controllers:
return None
controller_pose = self.controllers[controller_id].get('pose')
if not controller_pose:
return None
# 获取控制器位置和方向
controller_pos = controller_pose.getTranslate()
controller_forward = controller_pose.getQuat().getForward()
# 创建射线
ray = CollisionRay()
ray.setOrigin(controller_pos)
ray.setDirection(controller_forward)
# 执行碰撞检测
traverser = self.world.cTrav if hasattr(self.world, 'cTrav') else None
if not traverser:
return None
handler = CollisionHandlerQueue()
collision_node = CollisionNode('vr_controller_ray')
collision_node.addSolid(ray)
ray_np = self.world.render.attachNewNode(collision_node)
traverser.addCollider(ray_np, handler)
# 遍历碰撞
traverser.traverse(self.world.render)
# 清理
ray_np.removeNode()
# 返回最近的碰撞对象
if handler.getNumEntries() > 0:
handler.sortEntries()
entry = handler.getEntry(0)
return entry.getIntoNodePath()
return None
def _get_controller_ray(self, controller_id):
"""获取控制器射线"""
return self.controller_rays.get(controller_id)
def _setup_controller_visualization(self):
"""设置控制器可视化"""
print("设置控制器可视化...")
# 为每个控制器创建可视化节点
for controller_id in self.controllers:
self._create_controller_model(controller_id)
def _create_controller_model(self, controller_id):
"""创建控制器模型"""
# 创建简单的控制器模型(立方体)
from panda3d.core import CardMaker
cm = CardMaker(f"controller_{controller_id}")
cm.setFrame(-0.05, 0.05, -0.05, 0.05)
controller_node = self.world.render.attachNewNode(cm.generate())
controller_node.setColor(0.2, 0.8, 1.0, 0.8)
controller_node.setScale(0.1, 0.2, 0.05)
self.controller_nodes[controller_id] = controller_node
# 创建控制器射线可视化
self._create_controller_ray_visual(controller_id)
def _create_controller_ray_visual(self, controller_id):
"""创建控制器射线可视化"""
from panda3d.core import LineSegs
# 创建射线线段
lines = LineSegs()
lines.setColor(1, 0, 0, 0.5)
lines.moveTo(0, 0, 0)
lines.drawTo(0, 2, 0) # 2米长的射线
ray_node = self.world.render.attachNewNode(lines.create())
ray_node.setRenderModeWireframe()
ray_node.hide() # 默认隐藏
self.controller_rays[controller_id] = ray_node
def _update_controller_visualization(self, controller_id, pose):
"""更新控制器可视化"""
if controller_id in self.controller_nodes:
node = self.controller_nodes[controller_id]
node.setMat(pose)
if controller_id in self.controller_rays:
ray_node = self.controller_rays[controller_id]
ray_node.setMat(pose)
def _cleanup_controller_visualization(self):
"""清理控制器可视化"""
for node in self.controller_nodes.values():
node.removeNode()
for ray in self.controller_rays.values():
ray.removeNode()
self.controller_nodes.clear()
self.controller_rays.clear()
def _process_gestures(self):
"""处理手势识别"""
# 简单的手势识别逻辑
# 这里可以实现更复杂的手势识别算法
pass
def _process_interactions(self):
"""处理交互逻辑"""
# 如果有选中的对象,更新其位置
if self.selected_object:
self._update_grabbed_object()
def _update_grabbed_object(self):
"""更新被抓取对象的位置"""
if not self.selected_object:
return
# 找到抓取该对象的控制器
grabbing_controller = None
for controller_id, controller_state in self.controllers.items():
if controller_state.get('trigger', 0) > 0.5:
grabbing_controller = controller_id
break
if not grabbing_controller:
return
# 更新对象位置
controller_pose = self.controllers[grabbing_controller].get('pose')
if controller_pose:
controller_pos = controller_pose.getTranslate()
new_pos = controller_pos + self.grab_offset
self.selected_object.setPos(new_pos)
def _toggle_interaction_mode(self, controller_id):
"""切换交互模式"""
self.interaction_enabled = not self.interaction_enabled
print(f"交互模式: {'启用' if self.interaction_enabled else '禁用'}")
def _show_vr_menu(self, controller_id):
"""显示VR菜单"""
print(f"显示VR菜单 (控制器 {controller_id})")
# 这里可以实现VR菜单显示逻辑
pass
def _switch_tool(self, controller_id, direction):
"""切换工具"""
print(f"切换工具: {direction} (控制器 {controller_id})")
# 这里可以实现工具切换逻辑
pass
def _zoom_in(self, controller_id):
"""放大"""
print(f"放大 (控制器 {controller_id})")
# 实现放大逻辑
pass
def _zoom_out(self, controller_id):
"""缩小"""
print(f"缩小 (控制器 {controller_id})")
# 实现缩小逻辑
pass
def show_controller_rays(self, show=True):
"""显示/隐藏控制器射线"""
for ray in self.controller_rays.values():
if show:
ray.show()
else:
ray.hide()
def get_controller_state(self, controller_id):
"""获取控制器状态"""
return self.controllers.get(controller_id, {})
def get_all_controllers(self):
"""获取所有控制器"""
return list(self.controllers.keys())
def set_gesture_enabled(self, enabled):
"""设置手势识别启用状态"""
self.gesture_enabled = enabled
print(f"手势识别: {'启用' if enabled else '禁用'}")
def set_interaction_enabled(self, enabled):
"""设置交互启用状态"""
self.interaction_enabled = enabled
print(f"VR交互: {'启用' if enabled else '禁用'}")

432
core/vr_interaction.py Normal file
View File

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

701
core/vr_joystick.py Normal file
View File

@ -0,0 +1,701 @@
"""
VR摇杆交互系统模块
提供类似SteamVR的摇杆交互功能
- 摇杆左右转向旋转视角
- 摇杆向前传送预览抛物线轨迹
- 松开摇杆执行传送
- 死区处理和平滑控制
"""
import math
from panda3d.core import Vec2, Vec3, Vec4
from direct.showbase.DirectObject import DirectObject
try:
import openvr
OPENVR_AVAILABLE = True
except ImportError:
OPENVR_AVAILABLE = False
class VRJoystickManager(DirectObject):
"""VR摇杆管理器 - 处理手柄摇杆的转向和传送功能"""
def __init__(self, vr_manager):
"""初始化VR摇杆管理器
Args:
vr_manager: VR管理器实例
"""
super().__init__()
self.vr_manager = vr_manager
self.teleport_system = None # 传送系统引用,稍后初始化
# 摇杆参数配置
self.deadzone = 0.15 # 摇杆死区 (0-1)
self.turn_threshold = 0.3 # 转向激活阈值
self.teleport_threshold = 0.5 # 传送激活阈值
self.turn_sensitivity = 250.0 # 转向灵敏度(度/秒)- 增加速度
self.smooth_turning = True # 是否平滑转向
self.snap_turn_angle = 30.0 # 分段转向角度(度)
# 摇杆状态跟踪
self.left_joystick_state = JoystickState()
self.right_joystick_state = JoystickState()
# 转向状态
self.left_turn_cooldown = 0.0 # 分段转向冷却时间
self.right_turn_cooldown = 0.0
self.snap_turn_cooldown = 0.3 # 分段转向间隔(秒)
# 传送状态
self.active_teleport_controller = None # 正在传送的控制器
self.teleport_preview_active = False # 传送预览是否激活
# 互斥状态管理 - 防止同时触发多种操作
self.interaction_mode = 'none' # 当前交互模式: 'none', 'turning', 'teleporting'
self.left_controller_mode = 'none' # 左手控制器状态
self.right_controller_mode = 'none' # 右手控制器状态
self.mode_lock_timeout = 0.1 # 模式锁定超时时间(秒)
self.left_mode_timer = 0.0 # 左手模式计时器
self.right_mode_timer = 0.0 # 右手模式计时器
print("✓ VR摇杆管理器初始化完成")
def initialize(self, teleport_system):
"""初始化摇杆系统
Args:
teleport_system: VR传送系统实例
"""
self.teleport_system = teleport_system
print("✅ VR摇杆系统初始化成功")
def update(self, dt):
"""更新摇杆系统 - 每帧调用
Args:
dt: 帧间隔时间
"""
if not self.vr_manager.are_controllers_connected():
return
# 调试计数器
if not hasattr(self, '_debug_frame_count'):
self._debug_frame_count = 0
print("🎮 VR摇杆系统开始更新")
self._debug_frame_count += 1
# 更新转向冷却时间
if self.left_turn_cooldown > 0:
self.left_turn_cooldown -= dt
if self.right_turn_cooldown > 0:
self.right_turn_cooldown -= dt
# 更新互斥状态计时器
self._update_interaction_modes(dt)
# 处理左手控制器摇杆
if self.vr_manager.left_controller:
self._update_controller_joystick(
self.vr_manager.left_controller,
self.left_joystick_state,
'left',
dt
)
# 处理右手控制器摇杆
if self.vr_manager.right_controller:
self._update_controller_joystick(
self.vr_manager.right_controller,
self.right_joystick_state,
'right',
dt
)
# 每5秒输出一次状态报告
if self._debug_frame_count % 300 == 1: # 假设60fps
self._print_debug_status()
def _update_controller_joystick(self, controller, joystick_state, hand, dt):
"""更新单个控制器的摇杆状态
Args:
controller: VR控制器实例
joystick_state: 摇杆状态对象
hand: 'left' 'right'
dt: 帧间隔时间
"""
# 获取摇杆输入
joystick_input = self._get_joystick_input(controller, hand)
if joystick_input is None:
return
# 应用死区
filtered_input = self._apply_deadzone(joystick_input)
# 更新摇杆状态
joystick_state.update(filtered_input)
# 处理转向(左右移动)
self._handle_turning(filtered_input, hand, dt)
# 处理传送(向前移动)
self._handle_teleport(controller, filtered_input, joystick_state, hand)
def _update_interaction_modes(self, dt):
"""更新互斥交互模式状态"""
# 更新左手模式计时器
if self.left_mode_timer > 0:
self.left_mode_timer -= dt
if self.left_mode_timer <= 0:
self.left_controller_mode = 'none'
# 更新右手模式计时器
if self.right_mode_timer > 0:
self.right_mode_timer -= dt
if self.right_mode_timer <= 0:
self.right_controller_mode = 'none'
# 更新全局交互模式
if self.left_controller_mode == 'none' and self.right_controller_mode == 'none':
if self.interaction_mode != 'none':
# 所有操作结束,恢复自由模式
self.interaction_mode = 'none'
print("🔓 摇杆交互模式解锁,恢复自由操作")
def _set_controller_mode(self, hand, mode):
"""设置控制器交互模式
Args:
hand: 'left' 'right'
mode: 'turning', 'teleporting', 'none'
"""
if hand == 'left':
if self.left_controller_mode != mode:
old_mode = self.left_controller_mode
self.left_controller_mode = mode
self.left_mode_timer = self.mode_lock_timeout
if mode != 'none':
print(f"🔒 左手控制器: {old_mode}{mode}模式")
else:
print(f"🔓 左手控制器解锁: {old_mode} → 自由")
else:
if self.right_controller_mode != mode:
old_mode = self.right_controller_mode
self.right_controller_mode = mode
self.right_mode_timer = self.mode_lock_timeout
if mode != 'none':
print(f"🔒 右手控制器: {old_mode}{mode}模式")
else:
print(f"🔓 右手控制器解锁: {old_mode} → 自由")
# 更新全局模式
if mode != 'none' and self.interaction_mode != mode:
self.interaction_mode = mode
def _can_use_mode(self, hand, requested_mode):
"""检查是否可以使用指定的交互模式
Args:
hand: 'left' 'right'
requested_mode: 'turning' 'teleporting'
Returns:
bool: 是否可以使用该模式
"""
current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode
# 如果当前控制器已经是该模式,允许继续
if current_mode == requested_mode:
return True
# 如果当前控制器是空闲的,且全局模式兼容,允许切换
if current_mode == 'none':
if self.interaction_mode == 'none' or self.interaction_mode == requested_mode:
return True
# 其他情况不允许
return False
def _get_joystick_input(self, controller, hand):
"""获取摇杆输入
Args:
controller: VR控制器实例
hand: 'left' 'right'
Returns:
Vec2: 摇杆位置 (x, y) None
"""
# 直接从控制器读取摇杆输入(绕过动作系统)
joystick_input = Vec2(0, 0)
# 优先读取joystick_pos
if hasattr(controller, 'joystick_pos') and controller.joystick_pos:
joystick_input = Vec2(controller.joystick_pos.x, controller.joystick_pos.y)
# 检查是否有有效输入
if joystick_input.length() > 0.01:
# 调试输出 - 仅在有输入时显示
if not hasattr(self, '_last_debug_time'):
self._last_debug_time = 0
self._debug_counter = 0
self._debug_counter += 1
# 每30帧输出一次调试信息
if self._debug_counter % 30 == 1:
print(f"🎮 {hand}手摇杆输入: ({joystick_input.x:.3f}, {joystick_input.y:.3f})")
return joystick_input
# 备选方案读取touchpad_posQuest等设备
if hasattr(controller, 'touchpad_pos') and controller.touchpad_pos:
touchpad_input = Vec2(controller.touchpad_pos.x, controller.touchpad_pos.y)
# 检查是否有有效输入
if touchpad_input.length() > 0.01:
# 调试输出 - 仅在有输入时显示
if not hasattr(self, '_last_debug_time'):
self._last_debug_time = 0
self._debug_counter = 0
self._debug_counter += 1
# 每30帧输出一次调试信息
if self._debug_counter % 30 == 1:
print(f"🎮 {hand}手触摸板输入: ({touchpad_input.x:.3f}, {touchpad_input.y:.3f})")
return touchpad_input
# 可选:尝试从动作系统获取(如果可用)
if (self.vr_manager.action_manager and
hasattr(self.vr_manager.action_manager, 'get_analog_action_value')):
try:
device_path = f'/user/hand/{hand}'
# 尝试摇杆
joystick_value, _ = self.vr_manager.action_manager.get_analog_action_value('joystick', device_path)
if joystick_value is not None:
return Vec2(joystick_value.x, joystick_value.y)
# 尝试触摸板
trackpad_value, _ = self.vr_manager.action_manager.get_analog_action_value('trackpad', device_path)
if trackpad_value is not None:
return Vec2(trackpad_value.x, trackpad_value.y)
except Exception:
# 静默忽略动作系统错误
pass
return Vec2(0, 0)
def _apply_deadzone(self, input_vec):
"""应用摇杆死区
Args:
input_vec: 原始摇杆输入
Returns:
Vec2: 应用死区后的输入
"""
magnitude = input_vec.length()
if magnitude < self.deadzone:
return Vec2(0, 0)
# 重新映射到 [0, 1] 范围
normalized_magnitude = (magnitude - self.deadzone) / (1.0 - self.deadzone)
normalized_magnitude = min(normalized_magnitude, 1.0)
if magnitude > 0:
direction = input_vec / magnitude
return direction * normalized_magnitude
return Vec2(0, 0)
def _handle_turning(self, input_vec, hand, dt):
"""处理摇杆转向
Args:
input_vec: 摇杆输入向量
hand: 'left' 'right'
dt: 帧间隔时间
"""
# 检查是否超过转向阈值
if abs(input_vec.x) < self.turn_threshold:
# 没有转向输入,重置该控制器的转向模式
current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode
if current_mode == 'turning':
self._set_controller_mode(hand, 'none')
return
# 检查是否可以使用转向模式
if not self._can_use_mode(hand, 'turning'):
# 当前控制器被传送锁定,忽略转向输入
current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode
if current_mode != 'turning':
print(f"{hand}手转向被阻止 - 当前模式: {current_mode}")
return
# 激活转向模式
self._set_controller_mode(hand, 'turning')
# 检查冷却时间(分段转向)
cooldown = self.left_turn_cooldown if hand == 'left' else self.right_turn_cooldown
if self.smooth_turning:
# 平滑转向 - 反转方向:摇杆右移(+x)应该向右转(-angle)
turn_amount = -input_vec.x * self.turn_sensitivity * dt
self._apply_rotation(turn_amount)
# 调试输出转向
if not hasattr(self, '_turn_debug_counter'):
self._turn_debug_counter = 0
self._turn_debug_counter += 1
if self._turn_debug_counter % 60 == 1: # 每秒输出一次
print(f"🔄 {hand}手转向: 输入={input_vec.x:.3f}, 角度={turn_amount:.1f}°")
else:
# 分段转向
if cooldown <= 0:
# 反转方向:摇杆右移应该向右转
turn_amount = -self.snap_turn_angle if input_vec.x > 0 else self.snap_turn_angle
self._apply_rotation(turn_amount)
print(f"🔄 {hand}手分段转向: 输入={input_vec.x:.3f}, 角度={turn_amount:.1f}°")
# 设置冷却时间
if hand == 'left':
self.left_turn_cooldown = self.snap_turn_cooldown
else:
self.right_turn_cooldown = self.snap_turn_cooldown
def _apply_rotation(self, angle_degrees):
"""应用旋转到VR跟踪空间
Args:
angle_degrees: 旋转角度
"""
if not self.vr_manager.tracking_space:
return
# 绕Z轴旋转垂直轴
current_h = self.vr_manager.tracking_space.getH()
new_h = current_h + angle_degrees
self.vr_manager.tracking_space.setH(new_h)
def _handle_teleport(self, controller, input_vec, joystick_state, hand):
"""处理摇杆传送
Args:
controller: VR控制器实例
input_vec: 摇杆输入向量
joystick_state: 摇杆状态对象
hand: 'left' 'right'
"""
# 检查Y轴向前移动是否超过阈值
forward_input = input_vec.y
if forward_input > self.teleport_threshold:
# 检查是否可以使用传送模式
if not self._can_use_mode(hand, 'teleporting'):
# 当前控制器被转向锁定,忽略传送输入
current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode
if current_mode != 'teleporting':
print(f"{hand}手传送被阻止 - 当前模式: {current_mode}")
return
# 激活传送模式
self._set_controller_mode(hand, 'teleporting')
# 开始或更新传送预览
if not joystick_state.teleport_active:
joystick_state.teleport_active = True
self.active_teleport_controller = controller
self.teleport_preview_active = True
# 触发震动反馈
controller.trigger_haptic_feedback(0.002, 0.3)
# 计算传送方向
direction = self._calculate_teleport_direction(controller, input_vec)
# 更新传送预览
if self.teleport_system:
if joystick_state.teleport_just_started:
self.teleport_system.start_teleport_preview(controller, direction)
joystick_state.teleport_just_started = False
else:
self.teleport_system.update_teleport_preview(controller, direction)
else:
# 检查是否需要执行传送
if joystick_state.teleport_active:
# 松开摇杆,执行传送
self._execute_teleport(controller)
joystick_state.teleport_active = False
joystick_state.teleport_just_started = True
# 重置该控制器的传送模式
self._set_controller_mode(hand, 'none')
else:
# 没有传送输入,检查是否需要重置传送模式
current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode
if current_mode == 'teleporting':
self._set_controller_mode(hand, 'none')
def _calculate_teleport_direction(self, controller, input_vec):
"""计算传送方向向量 - 基于手柄姿态
Args:
controller: VR控制器实例
input_vec: 摇杆输入向量仅用于激活不影响方向
Returns:
Vec3: 世界坐标中的传送方向
"""
# 方法1直接使用手柄指向推荐
if controller and hasattr(controller, 'get_forward_direction'):
try:
# 获取手柄的前向方向
controller_forward = controller.get_forward_direction()
# 确保方向向量在水平面上
horizontal_direction = Vec3(controller_forward.x, controller_forward.y, 0)
if horizontal_direction.length() > 0:
horizontal_direction.normalize()
# 调试输出每30帧输出一次
if not hasattr(self, '_direction_debug_counter'):
self._direction_debug_counter = 0
self._direction_debug_counter += 1
if self._direction_debug_counter % 30 == 1:
# 获取当前tracking_space的旋转角度用于调试
tracking_rotation = 0
if self.vr_manager.tracking_space:
tracking_rotation = self.vr_manager.tracking_space.getH()
print(f"🎯 综合传送方向: 视角旋转({tracking_rotation:.1f}°) + 手柄姿态({controller_forward.x:.2f},{controller_forward.y:.2f},{controller_forward.z:.2f}) → 最终方向({horizontal_direction.x:.2f},{horizontal_direction.y:.2f})")
return horizontal_direction
except Exception as e:
print(f"⚠️ 获取手柄方向失败: {e}")
# 备选方案:使用玩家朝向(保留原逻辑作为备选)
print("🔄 使用备选传送方向计算...")
# 获取玩家当前朝向的变换矩阵
player_transform = None
# 优先使用头显的世界变换
if self.vr_manager.hmd_anchor and hasattr(self.vr_manager, 'world') and self.vr_manager.world:
player_transform = self.vr_manager.hmd_anchor.getMat(self.vr_manager.world.render)
# 备选使用tracking_space的世界变换
elif self.vr_manager.tracking_space and hasattr(self.vr_manager, 'world') and self.vr_manager.world:
player_transform = self.vr_manager.tracking_space.getMat(self.vr_manager.world.render)
if player_transform:
# 从变换矩阵提取前向向量
forward = Vec3(player_transform.getRow3(1)) # Y轴 = 前向
# 确保向量在水平面上
forward.z = 0
if forward.length() > 0:
forward.normalize()
return forward
# 最终备选方案:默认前向
print("⚠️ 无法获取任何朝向,使用默认方向")
return Vec3(0, 1, 0)
def _execute_teleport(self, controller):
"""执行传送
Args:
controller: VR控制器实例
"""
if self.teleport_system and self.teleport_preview_active:
success = self.teleport_system.execute_teleport()
if success:
# 传送成功,触发震动反馈
controller.trigger_haptic_feedback(0.005, 0.8)
else:
# 传送失败,触发不同的震动反馈
controller.trigger_haptic_feedback(0.001, 0.2)
# 停止传送预览
self.teleport_system.stop_teleport_preview()
self.teleport_preview_active = False
self.active_teleport_controller = None
def set_turning_mode(self, smooth=True):
"""设置转向模式
Args:
smooth: True为平滑转向False为分段转向
"""
self.smooth_turning = smooth
print(f"✓ 转向模式设置为: {'平滑转向' if smooth else '分段转向'}")
def set_turn_sensitivity(self, sensitivity):
"""设置转向灵敏度
Args:
sensitivity: 转向灵敏度/
"""
self.turn_sensitivity = max(10.0, min(180.0, sensitivity))
print(f"✓ 转向灵敏度设置为: {self.turn_sensitivity}度/秒")
def apply_config(self, config):
"""应用配置
Args:
config: VRJoystickConfig配置实例
"""
try:
self.deadzone = config.deadzone
self.turn_threshold = config.turn_threshold
self.teleport_threshold = config.teleport_threshold
self.turn_sensitivity = config.turn_sensitivity
self.smooth_turning = (config.turn_mode.value == 'smooth')
self.snap_turn_angle = config.snap_turn_angle
self.snap_turn_cooldown = config.snap_turn_cooldown
# 应用传送系统配置
if self.teleport_system and hasattr(config, 'teleport_range'):
self.teleport_system.teleport_range = config.teleport_range
if hasattr(config, 'teleport_arc_resolution'):
self.teleport_system.arc_resolution = config.teleport_arc_resolution
if hasattr(config, 'teleport_initial_velocity'):
self.teleport_system.initial_velocity = config.teleport_initial_velocity
if hasattr(config, 'min_teleport_distance'):
self.teleport_system.min_teleport_distance = config.min_teleport_distance
print("✅ 摇杆配置已成功应用")
except Exception as e:
print(f"⚠️ 应用配置失败: {e}")
def get_current_config(self):
"""获取当前配置
Returns:
dict: 当前配置参数
"""
return {
'deadzone': self.deadzone,
'turn_threshold': self.turn_threshold,
'teleport_threshold': self.teleport_threshold,
'turn_sensitivity': self.turn_sensitivity,
'smooth_turning': self.smooth_turning,
'snap_turn_angle': self.snap_turn_angle,
'snap_turn_cooldown': self.snap_turn_cooldown
}
def _print_debug_status(self):
"""打印调试状态信息"""
try:
print("🔍 ======= VR摇杆调试状态 =======")
# 控制器连接状态
left_connected = self.vr_manager.left_controller is not None
right_connected = self.vr_manager.right_controller is not None
print(f"📱 控制器状态: 左手={left_connected}, 右手={right_connected}")
# 检查控制器属性
if left_connected:
left_ctrl = self.vr_manager.left_controller
has_joystick = hasattr(left_ctrl, 'joystick_pos')
has_touchpad = hasattr(left_ctrl, 'touchpad_pos')
print(f"📊 左手控制器: joystick_pos={has_joystick}, touchpad_pos={has_touchpad}")
if has_joystick and left_ctrl.joystick_pos:
pos = left_ctrl.joystick_pos
print(f" 当前摇杆位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})")
if has_touchpad and left_ctrl.touchpad_pos:
pos = left_ctrl.touchpad_pos
print(f" 当前触摸板位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})")
if right_connected:
right_ctrl = self.vr_manager.right_controller
has_joystick = hasattr(right_ctrl, 'joystick_pos')
has_touchpad = hasattr(right_ctrl, 'touchpad_pos')
print(f"📊 右手控制器: joystick_pos={has_joystick}, touchpad_pos={has_touchpad}")
if has_joystick and right_ctrl.joystick_pos:
pos = right_ctrl.joystick_pos
print(f" 当前摇杆位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})")
if has_touchpad and right_ctrl.touchpad_pos:
pos = right_ctrl.touchpad_pos
print(f" 当前触摸板位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})")
# 摇杆配置
print(f"⚙️ 摇杆配置:")
print(f" 死区: {self.deadzone}")
print(f" 转向阈值: {self.turn_threshold}")
print(f" 传送阈值: {self.teleport_threshold}")
print(f" 转向模式: {'平滑' if self.smooth_turning else '分段'}")
# 动作系统状态
action_mgr_available = (self.vr_manager.action_manager is not None)
print(f"🎯 动作系统: {'可用' if action_mgr_available else '不可用'}")
# 传送系统状态
teleport_available = (self.teleport_system is not None)
print(f"🚀 传送系统: {'可用' if teleport_available else '不可用'}")
print("🔍 ==============================")
except Exception as e:
print(f"⚠️ 调试状态输出失败: {e}")
def cleanup(self):
"""清理摇杆系统资源"""
try:
# 停止任何活跃的传送预览
if self.teleport_preview_active and self.teleport_system:
self.teleport_system.stop_teleport_preview()
self.teleport_preview_active = False
self.active_teleport_controller = None
self.ignoreAll()
print("🧹 VR摇杆系统已清理")
except Exception as e:
print(f"⚠️ 清理摇杆系统失败: {e}")
class JoystickState:
"""摇杆状态跟踪类"""
def __init__(self):
self.current_input = Vec2(0, 0) # 当前摇杆输入
self.previous_input = Vec2(0, 0) # 上一帧摇杆输入
self.teleport_active = False # 传送是否激活
self.teleport_just_started = True # 传送是否刚开始
def update(self, new_input):
"""更新摇杆状态
Args:
new_input: 新的摇杆输入
"""
self.previous_input = Vec2(self.current_input)
self.current_input = Vec2(new_input)
def is_input_changed(self, threshold=0.01):
"""检查输入是否发生变化
Args:
threshold: 变化阈值
Returns:
bool: 是否发生变化
"""
diff = self.current_input - self.previous_input
return diff.length() > threshold

268
core/vr_joystick_config.py Normal file
View File

@ -0,0 +1,268 @@
"""
VR摇杆配置模块
提供摇杆交互的配置选项和预设
- 不同的转向模式和灵敏度设置
- 传送参数调整
- 用户体验优化选项
"""
from enum import Enum
class TurnMode(Enum):
"""转向模式枚举"""
SMOOTH = "smooth" # 平滑转向
SNAP = "snap" # 分段转向
class JoystickProfile(Enum):
"""摇杆配置预设"""
COMFORTABLE = "comfortable" # 舒适模式 - 低灵敏度,平滑转向
STANDARD = "standard" # 标准模式 - 中等灵敏度,分段转向
GAMING = "gaming" # 游戏模式 - 高灵敏度,快速响应
ACCESSIBLE = "accessible" # 无障碍模式 - 更大死区,更高阈值
class VRJoystickConfig:
"""VR摇杆配置类"""
def __init__(self):
"""初始化默认配置"""
# 基础参数
self.deadzone = 0.15 # 摇杆死区 (0-1)
self.turn_threshold = 0.3 # 转向激活阈值
self.teleport_threshold = 0.5 # 传送激活阈值
# 转向设置
self.turn_mode = TurnMode.SNAP # 转向模式
self.turn_sensitivity = 250.0 # 转向灵敏度(度/秒)- 增加速度
self.snap_turn_angle = 30.0 # 分段转向角度(度)
self.snap_turn_cooldown = 0.3 # 分段转向间隔(秒)
# 传送设置
self.teleport_range = 20.0 # 最大传送距离
self.teleport_arc_resolution = 50 # 抛物线精度
self.teleport_initial_velocity = 10.0 # 传送初始速度
self.min_teleport_distance = 1.0 # 最小传送距离
# 反馈设置
self.haptic_feedback_enabled = True # 是否启用震动反馈
self.teleport_start_haptic = (0.002, 0.3) # 传送开始震动 (时长, 强度)
self.teleport_success_haptic = (0.005, 0.8) # 传送成功震动
self.teleport_fail_haptic = (0.001, 0.2) # 传送失败震动
# 可视化设置
self.show_teleport_arc = True # 显示传送抛物线
self.show_teleport_target = True # 显示传送目标标记
self.arc_valid_color = (0.2, 0.9, 0.2, 0.8) # 有效抛物线颜色
self.arc_invalid_color = (0.9, 0.2, 0.2, 0.8) # 无效抛物线颜色
def apply_profile(self, profile: JoystickProfile):
"""应用预设配置
Args:
profile: 配置预设
"""
if profile == JoystickProfile.COMFORTABLE:
self._apply_comfortable_profile()
elif profile == JoystickProfile.STANDARD:
self._apply_standard_profile()
elif profile == JoystickProfile.GAMING:
self._apply_gaming_profile()
elif profile == JoystickProfile.ACCESSIBLE:
self._apply_accessible_profile()
def _apply_comfortable_profile(self):
"""舒适模式 - 适合长时间使用"""
self.deadzone = 0.2
self.turn_threshold = 0.4
self.teleport_threshold = 0.6
self.turn_mode = TurnMode.SMOOTH
self.turn_sensitivity = 100.0 # 适中的转向速度
self.snap_turn_cooldown = 0.4
print("✓ 已应用舒适模式配置")
def _apply_standard_profile(self):
"""标准模式 - 平衡的体验"""
self.deadzone = 0.15
self.turn_threshold = 0.3
self.teleport_threshold = 0.5
self.turn_mode = TurnMode.SNAP
self.turn_sensitivity = 150.0 # 更快的转向速度
self.snap_turn_angle = 30.0
self.snap_turn_cooldown = 0.3
print("✓ 已应用标准模式配置")
def _apply_gaming_profile(self):
"""游戏模式 - 快速响应"""
self.deadzone = 0.1
self.turn_threshold = 0.2
self.teleport_threshold = 0.4
self.turn_mode = TurnMode.SMOOTH
self.turn_sensitivity = 200.0 # 高速转向
self.snap_turn_cooldown = 0.2
print("✓ 已应用游戏模式配置")
def _apply_accessible_profile(self):
"""无障碍模式 - 更容易控制"""
self.deadzone = 0.25
self.turn_threshold = 0.5
self.teleport_threshold = 0.7
self.turn_mode = TurnMode.SNAP
self.turn_sensitivity = 80.0 # 较慢但可控的转向
self.snap_turn_angle = 45.0
self.snap_turn_cooldown = 0.5
print("✓ 已应用无障碍模式配置")
def set_turn_mode(self, mode: TurnMode):
"""设置转向模式
Args:
mode: 转向模式
"""
self.turn_mode = mode
print(f"✓ 转向模式设置为: {mode.value}")
def set_turn_sensitivity(self, sensitivity: float):
"""设置转向灵敏度
Args:
sensitivity: 转向灵敏度/
"""
self.turn_sensitivity = max(10.0, min(180.0, sensitivity))
print(f"✓ 转向灵敏度设置为: {self.turn_sensitivity}度/秒")
def set_deadzone(self, deadzone: float):
"""设置摇杆死区
Args:
deadzone: 死区大小 (0-1)
"""
self.deadzone = max(0.0, min(0.5, deadzone))
print(f"✓ 摇杆死区设置为: {self.deadzone}")
def set_teleport_range(self, range_meters: float):
"""设置传送范围
Args:
range_meters: 传送范围
"""
self.teleport_range = max(5.0, min(50.0, range_meters))
print(f"✓ 传送范围设置为: {self.teleport_range}")
def enable_haptic_feedback(self, enabled: bool):
"""启用或禁用震动反馈
Args:
enabled: 是否启用
"""
self.haptic_feedback_enabled = enabled
print(f"✓ 震动反馈: {'启用' if enabled else '禁用'}")
def get_config_dict(self):
"""获取配置字典
Returns:
dict: 配置参数字典
"""
return {
'deadzone': self.deadzone,
'turn_threshold': self.turn_threshold,
'teleport_threshold': self.teleport_threshold,
'turn_mode': self.turn_mode.value,
'turn_sensitivity': self.turn_sensitivity,
'snap_turn_angle': self.snap_turn_angle,
'snap_turn_cooldown': self.snap_turn_cooldown,
'teleport_range': self.teleport_range,
'haptic_feedback_enabled': self.haptic_feedback_enabled
}
def apply_to_joystick_manager(self, joystick_manager):
"""将配置应用到摇杆管理器
Args:
joystick_manager: VRJoystickManager实例
"""
try:
joystick_manager.deadzone = self.deadzone
joystick_manager.turn_threshold = self.turn_threshold
joystick_manager.teleport_threshold = self.teleport_threshold
joystick_manager.turn_sensitivity = self.turn_sensitivity
joystick_manager.smooth_turning = (self.turn_mode == TurnMode.SMOOTH)
joystick_manager.snap_turn_angle = self.snap_turn_angle
joystick_manager.snap_turn_cooldown = self.snap_turn_cooldown
# 应用传送系统配置
if hasattr(joystick_manager, 'teleport_system') and joystick_manager.teleport_system:
teleport_sys = joystick_manager.teleport_system
teleport_sys.teleport_range = self.teleport_range
teleport_sys.arc_resolution = self.teleport_arc_resolution
teleport_sys.initial_velocity = self.teleport_initial_velocity
teleport_sys.min_teleport_distance = self.min_teleport_distance
print("✅ 配置已成功应用到摇杆管理器")
except Exception as e:
print(f"⚠️ 应用配置失败: {e}")
# 预定义的配置实例
COMFORTABLE_CONFIG = VRJoystickConfig()
COMFORTABLE_CONFIG.apply_profile(JoystickProfile.COMFORTABLE)
STANDARD_CONFIG = VRJoystickConfig()
STANDARD_CONFIG.apply_profile(JoystickProfile.STANDARD)
GAMING_CONFIG = VRJoystickConfig()
GAMING_CONFIG.apply_profile(JoystickProfile.GAMING)
ACCESSIBLE_CONFIG = VRJoystickConfig()
ACCESSIBLE_CONFIG.apply_profile(JoystickProfile.ACCESSIBLE)
def create_custom_config(**kwargs):
"""创建自定义配置
Args:
**kwargs: 配置参数
Returns:
VRJoystickConfig: 自定义配置实例
"""
config = VRJoystickConfig()
for key, value in kwargs.items():
if hasattr(config, key):
setattr(config, key, value)
else:
print(f"⚠️ 未知的配置参数: {key}")
return config
def print_usage_guide():
"""打印使用指南"""
print("🎮 ======= VR摇杆交互使用指南 =======")
print()
print("📋 基本操作:")
print(" • 摇杆左右移动 → 转向(旋转视角)")
print(" • 摇杆向前推 → 显示传送抛物线")
print(" • 松开摇杆 → 执行传送到落点")
print()
print("⚙️ 配置选项:")
print(" • 舒适模式 → 低灵敏度,适合长时间使用")
print(" • 标准模式 → 平衡体验,推荐大多数用户")
print(" • 游戏模式 → 高灵敏度,快速响应")
print(" • 无障碍模式 → 更大死区,容易控制")
print()
print("🎯 使用建议:")
print(" • 首次使用建议从标准模式开始")
print(" • 根据个人喜好调整转向模式(平滑/分段)")
print(" • 可以随时调整死区和灵敏度")
print(" • 传送范围可根据场景大小调整")
print()
if __name__ == "__main__":
print_usage_guide()

File diff suppressed because it is too large Load Diff

411
core/vr_teleport.py Normal file
View File

@ -0,0 +1,411 @@
"""
VR传送系统模块
提供VR传送功能
- 抛物线轨迹计算和可视化
- 传送点有效性检测
- 传送执行
- 可视化反馈抛物线落点标记
"""
import math
from panda3d.core import (
Vec3, Vec4, Mat4, Point3, CollisionRay, CollisionTraverser,
CollisionNode, CollisionHandlerQueue, BitMask32, NodePath,
CollisionSphere, CollisionPlane, LineSegs, GeomNode, Material,
RenderState, TransparencyAttrib, ColorAttrib
)
from direct.showbase.DirectObject import DirectObject
class VRTeleportSystem(DirectObject):
"""VR传送系统 - 处理传送功能和可视化"""
def __init__(self, vr_manager):
"""初始化VR传送系统
Args:
vr_manager: VR管理器实例
"""
super().__init__()
self.vr_manager = vr_manager
self.world = vr_manager.world if hasattr(vr_manager, 'world') else None
# 传送参数
self.teleport_range = 20.0 # 最大传送距离
self.arc_resolution = 50 # 抛物线精度
self.gravity = -9.8 # 重力系数
self.initial_velocity = 10.0 # 初始速度
self.min_teleport_distance = 1.0 # 最小传送距离
# 可视化元素
self.teleport_arc_node = None # 抛物线节点
self.teleport_target_node = None # 落点标记节点
self.teleport_invalid_node = None # 无效位置标记
# 传送状态
self.is_teleport_active = False # 是否正在预览传送
self.teleport_target_pos = None # 传送目标位置
self.teleport_valid = False # 传送位置是否有效
self.active_controller = None # 正在使用传送的控制器
# 碰撞检测
self.teleport_collision_traverser = CollisionTraverser()
self.teleport_collision_queue = CollisionHandlerQueue()
# 可视化颜色
self.valid_arc_color = Vec4(0.2, 0.9, 0.2, 0.8) # 绿色抛物线
self.invalid_arc_color = Vec4(0.9, 0.2, 0.2, 0.8) # 红色抛物线
self.target_color = Vec4(0.2, 0.7, 1.0, 0.9) # 蓝色落点
print("✓ VR传送系统初始化完成")
def initialize(self):
"""初始化传送系统"""
try:
print("🔧 正在初始化VR传送系统...")
# 创建可视化元素
self._create_teleport_visuals()
# 设置地面检测
self._setup_ground_detection()
print("✅ VR传送系统初始化成功")
return True
except Exception as e:
print(f"❌ VR传送系统初始化失败: {e}")
import traceback
traceback.print_exc()
return False
def _create_teleport_visuals(self):
"""创建传送可视化元素"""
if not self.world or not hasattr(self.world, 'render'):
print("⚠️ 无法创建传送可视化 - 缺少世界渲染节点")
return
# 创建抛物线节点
self.teleport_arc_node = self.world.render.attachNewNode("teleport_arc")
self.teleport_arc_node.hide() # 初始隐藏
# 创建落点标记
self._create_target_marker()
# 创建无效位置标记
self._create_invalid_marker()
print("✓ 传送可视化元素已创建")
def _create_target_marker(self):
"""创建传送目标标记"""
try:
# 创建一个圆形平台作为落点标记
from panda3d.core import CardMaker
cm = CardMaker("teleport_target")
cm.setFrame(-1, 1, -1, 1) # 2x2的平面
self.teleport_target_node = self.world.render.attachNewNode(cm.generate())
self.teleport_target_node.setScale(1.0) # 2米直径的圆形
self.teleport_target_node.setP(-90) # 平放在地面
self.teleport_target_node.setColor(self.target_color)
self.teleport_target_node.setTransparency(TransparencyAttrib.MAlpha)
self.teleport_target_node.hide()
# 添加PBR材质以兼容RenderPipeline
material = Material()
material.setBaseColor(self.target_color)
material.setRoughness(0.8)
material.setMetallic(0.0)
material.setRefractiveIndex(1.5)
self.teleport_target_node.setMaterial(material, 1)
except Exception as e:
print(f"⚠️ 创建传送目标标记失败: {e}")
def _create_invalid_marker(self):
"""创建无效位置标记"""
try:
from panda3d.core import CardMaker
cm = CardMaker("teleport_invalid")
cm.setFrame(-0.8, 0.8, -0.8, 0.8) # 稍小的红色叉号
self.teleport_invalid_node = self.world.render.attachNewNode(cm.generate())
self.teleport_invalid_node.setScale(1.0)
self.teleport_invalid_node.setP(-90)
self.teleport_invalid_node.setColor(self.invalid_arc_color)
self.teleport_invalid_node.setTransparency(TransparencyAttrib.MAlpha)
self.teleport_invalid_node.hide()
# 添加PBR材质
material = Material()
material.setBaseColor(self.invalid_arc_color)
material.setRoughness(0.8)
material.setMetallic(0.0)
material.setRefractiveIndex(1.5)
self.teleport_invalid_node.setMaterial(material, 1)
except Exception as e:
print(f"⚠️ 创建无效位置标记失败: {e}")
def _setup_ground_detection(self):
"""设置地面检测"""
try:
# 假设地面在Z=0平面使用正确的CollisionPlane API
from panda3d.core import Plane
ground_plane = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0)))
ground_node = CollisionNode("ground_detection")
ground_node.addSolid(ground_plane)
ground_node.setIntoCollideMask(BitMask32.bit(1)) # 地面碰撞掩码
# 附加到渲染节点
if self.world and hasattr(self.world, 'render'):
ground_node_path = self.world.render.attachNewNode(ground_node)
print("✓ 地面检测设置成功")
except Exception as e:
print(f"⚠️ 设置地面检测失败: {e}")
# 继续运行,不让这个错误阻止传送系统
def start_teleport_preview(self, controller, direction):
"""开始传送预览
Args:
controller: VR控制器实例
direction: 传送方向向量摇杆输入转换的世界方向
"""
if not controller or not controller.anchor_node:
return False
self.is_teleport_active = True
self.active_controller = controller
# 计算抛物线轨迹
start_pos = controller.get_world_position()
self._calculate_teleport_trajectory(start_pos, direction)
# 显示可视化
self._show_teleport_visuals()
return True
def _calculate_teleport_trajectory(self, start_pos, direction):
"""计算传送抛物线轨迹
Args:
start_pos: 起始位置
direction: 方向向量
"""
try:
# 确保方向向量已标准化
direction = direction.normalized()
# 计算抛物线点
trajectory_points = []
hit_ground = False
self.teleport_valid = False
# 初始速度向量
velocity = direction * self.initial_velocity
velocity.z += 2.0 # 给一些向上的初始速度
dt = 0.05 # 时间步长
current_pos = Vec3(start_pos)
current_velocity = Vec3(velocity)
for i in range(self.arc_resolution):
trajectory_points.append(Point3(current_pos))
# 更新位置和速度
current_pos += current_velocity * dt
current_velocity.z += self.gravity * dt # 应用重力
# 检查是否碰撞地面
if current_pos.z <= 0.1: # 假设地面在z=0
# 精确计算落地点
ground_pos = self._calculate_ground_intersection(
trajectory_points[-2] if len(trajectory_points) > 1 else start_pos,
current_pos
)
trajectory_points.append(ground_pos)
# 检查传送有效性
distance = (ground_pos - start_pos).length()
if (distance >= self.min_teleport_distance and
distance <= self.teleport_range):
self.teleport_target_pos = ground_pos
self.teleport_valid = True
else:
self.teleport_target_pos = ground_pos
self.teleport_valid = False
hit_ground = True
break
# 超出范围
if (current_pos - start_pos).length() > self.teleport_range:
break
# 如果没有碰撞地面,传送无效
if not hit_ground:
if trajectory_points:
self.teleport_target_pos = trajectory_points[-1]
self.teleport_valid = False
# 创建抛物线几何体
self._create_arc_geometry(trajectory_points)
except Exception as e:
print(f"⚠️ 计算传送轨迹失败: {e}")
self.teleport_valid = False
def _calculate_ground_intersection(self, p1, p2):
"""计算与地面的精确交点"""
if p1.z == p2.z:
return Point3(p2)
# 线性插值找到z=0的点
t = -p1.z / (p2.z - p1.z)
t = max(0, min(1, t)) # 限制在0-1范围内
intersection = p1 + (p2 - p1) * t
intersection.z = 0.1 # 稍微高于地面
return Point3(intersection)
def _create_arc_geometry(self, points):
"""创建抛物线几何体"""
if not points or len(points) < 2:
return
try:
# 创建线段
line_segs = LineSegs()
line_segs.setThickness(3)
# 根据有效性设置颜色
color = self.valid_arc_color if self.teleport_valid else self.invalid_arc_color
line_segs.setColor(color)
# 添加线段点
line_segs.moveTo(points[0])
for point in points[1:]:
line_segs.drawTo(point)
# 清除旧的几何体
self.teleport_arc_node.removeNode()
self.teleport_arc_node = self.world.render.attachNewNode("teleport_arc")
# 创建新的几何体
geom_node = line_segs.create()
arc_node_path = self.teleport_arc_node.attachNewNode(geom_node)
# 设置材质
material = Material()
material.setBaseColor(color)
material.setRoughness(0.1)
material.setMetallic(0.0)
material.setRefractiveIndex(1.5)
arc_node_path.setMaterial(material, 1)
# 设置透明度
arc_node_path.setTransparency(TransparencyAttrib.MAlpha)
except Exception as e:
print(f"⚠️ 创建抛物线几何体失败: {e}")
def _show_teleport_visuals(self):
"""显示传送可视化"""
if self.teleport_arc_node:
self.teleport_arc_node.show()
if self.teleport_target_pos:
if self.teleport_valid and self.teleport_target_node:
# 显示有效的传送目标
self.teleport_target_node.setPos(self.teleport_target_pos)
self.teleport_target_node.show()
self.teleport_invalid_node.hide()
elif self.teleport_invalid_node:
# 显示无效的传送位置
self.teleport_invalid_node.setPos(self.teleport_target_pos)
self.teleport_invalid_node.show()
self.teleport_target_node.hide()
def stop_teleport_preview(self):
"""停止传送预览"""
self.is_teleport_active = False
self.active_controller = None
# 隐藏可视化
if self.teleport_arc_node:
self.teleport_arc_node.hide()
if self.teleport_target_node:
self.teleport_target_node.hide()
if self.teleport_invalid_node:
self.teleport_invalid_node.hide()
def execute_teleport(self):
"""执行传送"""
if not self.teleport_valid or not self.teleport_target_pos:
print("⚠️ 传送位置无效,无法执行传送")
return False
try:
# 计算传送偏移
if self.vr_manager.tracking_space:
current_pos = self.vr_manager.tracking_space.getPos()
target_offset = self.teleport_target_pos - self.active_controller.get_world_position()
new_pos = current_pos + target_offset
# 执行传送
self.vr_manager.tracking_space.setPos(new_pos)
print(f"✅ 传送成功: {current_pos}{new_pos}")
# 停止预览
self.stop_teleport_preview()
return True
else:
print("⚠️ 无法获取VR跟踪空间传送失败")
return False
except Exception as e:
print(f"❌ 执行传送失败: {e}")
return False
def update_teleport_preview(self, controller, direction):
"""更新传送预览(摇杆移动时调用)"""
if self.is_teleport_active and controller == self.active_controller:
start_pos = controller.get_world_position()
self._calculate_teleport_trajectory(start_pos, direction)
self._show_teleport_visuals()
def cleanup(self):
"""清理传送系统资源"""
try:
self.stop_teleport_preview()
if self.teleport_arc_node:
self.teleport_arc_node.removeNode()
self.teleport_arc_node = None
if self.teleport_target_node:
self.teleport_target_node.removeNode()
self.teleport_target_node = None
if self.teleport_invalid_node:
self.teleport_invalid_node.removeNode()
self.teleport_invalid_node = None
self.ignoreAll()
print("🧹 VR传送系统已清理")
except Exception as e:
print(f"⚠️ 清理传送系统失败: {e}")

729
core/vr_visualization.py Normal file
View File

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

View File

@ -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文件。

View File

@ -1,253 +0,0 @@
# VR测试说明
## 📋 测试条件说明
### 🎮 VR测试的两种模式
#### 1. 模拟模式(推荐用于开发和调试)
- **无需VR硬件**
- **无需SteamVR**
- **无需ALVR服务器**
- 自动启用当真实VR不可用时
- 模拟头盔和控制器追踪数据
- 立体渲染显示到窗口
- 适用于功能测试和开发
#### 2. 真实VR模式需要完整VR设备
- **需要VR头盔**
- **需要SteamVR运行**
- **需要ALVR服务器Quest无线**
- 真实的6DOF追踪
- 立体渲染到VR头盔
- 完整的VR交互体验
## 🔧 软件要求
### 必需依赖
```bash
# 安装VR相关依赖
pip install -r requirements/vr-requirements.txt
# 主要包含:
# - openvr>=1.26.7
# - psutil>=5.9.0
# - numpy>=1.21.0
# - Pillow>=9.0.1
```
### 系统要求
- Python 3.8+
- Panda3D
- PyQt5
- OpenGL支持的显卡
## 🎮 硬件要求完整VR模式
### 最低硬件要求
- **显卡**: GTX 1060 / RX 580 或更好
- **内存**: 8GB RAM
- **处理器**: Intel i5-4590 / AMD FX 8350 或更好
- **USB**: 至少1个USB 3.0端口
### 支持的VR头盔
- **Quest 2/3**: 有线USB-C或无线ALVR
- **Valve Index**: DisplayPort + USB 3.0
- **HTC Vive**: HDMI + USB 3.0
- **其他OpenVR兼容设备**
## 🌐 连接方式
### 有线连接
1. **Quest 2/3**: USB-C数据线连接PC
2. **Valve Index**: DisplayPort + USB 3.0
3. **HTC Vive**: HDMI + USB 3.0
### 无线连接推荐Quest
1. **下载ALVR服务器**: [GitHub](https://github.com/alvr-org/ALVR)
2. **在PC上运行ALVR服务器**
3. **在Quest上安装ALVR客户端**
4. **连接到同一Wi-Fi网络5GHz推荐**
## 🔄 启动顺序
### 完整VR模式启动顺序
1. **连接VR头盔**到PC
2. **启动SteamVR**
3. **可选启动ALVR服务器**Quest无线
4. **运行VR测试脚本**
### 模拟模式启动顺序
1. **直接运行VR测试脚本**
2. **系统会自动切换到模拟模式**
## 📊 测试项目说明
### 1. 基本VR功能测试
- VR系统初始化
- 控制器检测
- 追踪数据获取
- 立体渲染测试
### 2. VR模拟模式测试
- 强制启用模拟模式
- 模拟数据更新
- 控制器输入模拟
- 立体渲染显示
### 3. ALVR串流测试
- ALVR服务器连接
- 串流质量设置
- 触觉反馈测试
- 串流开关控制
### 4. VR GUI控制面板
- 图形界面控制
- 实时状态监控
- 参数调整
- 系统开关
### 5. VR交互功能测试
- 控制器射线显示
- 手势识别
- 对象交互
- 输入处理
## 🚀 快速开始
### 开发和调试(推荐)
```bash
# 直接运行,会自动使用模拟模式
python vr_test.py
# 选择 "2. VR模拟模式测试"
```
### 完整VR测试
```bash
# 确保VR设备已连接并启动SteamVR
# 然后运行
python vr_test.py
# 选择 "1. 基本VR功能测试"
```
## 💡 故障排除
### 常见问题
#### 1. OpenVR初始化失败
**现象**: 显示"OpenVR初始化失败"
**解决方案**:
- 检查SteamVR是否运行
- 确认VR头盔被系统识别
- 重启SteamVR和头盔
- 系统会自动切换到模拟模式
#### 2. 控制器未检测到
**现象**: 控制器显示"未连接"
**解决方案**:
- 确保控制器已配对
- 检查控制器电量
- 在SteamVR中重新配对
- 模拟模式下会显示模拟控制器
#### 3. ALVR连接失败
**现象**: ALVR串流测试失败
**解决方案**:
- 确保ALVR服务器正在运行
- 检查Quest上的ALVR客户端
- 确认PC和Quest在同一网络
- 使用5GHz Wi-Fi网络
#### 4. 性能问题
**现象**: 帧率低或卡顿
**解决方案**:
- 降低渲染分辨率
- 关闭不必要的后台程序
- 检查显卡驱动更新
- 使用模拟模式进行开发
### 日志分析
测试过程中的详细日志可以帮助诊断问题:
- `✓` 表示成功
- `⚠` 表示警告(非致命)
- `✗` 表示失败
- `🔧` 表示配置或调试信息
## 📈 性能优化建议
### 开发阶段
1. **使用模拟模式**进行功能开发
2. **降低渲染分辨率**提高帧率
3. **关闭不必要的特效**
4. **使用性能分析工具**
### 生产部署
1. **使用真实VR模式**
2. **根据硬件配置调整质量**
3. **启用异步时间扭曲**
4. **优化渲染管线**
## 🔍 调试技巧
### 1. 检查VR系统状态
```python
# 在代码中添加调试信息
vr_info = world.getVRInfo()
print(f"VR模式: {vr_info['mode']}")
print(f"模拟模式: {vr_info['simulation_mode']}")
```
### 2. 监控性能
```python
# 获取VR系统状态
status = world.getVRStatus()
print(f"VR启用: {status['vr_enabled']}")
print(f"控制器数量: {len(status['controllers'])}")
```
### 3. 测试特定功能
```python
# 强制启用模拟模式
world.vr_manager.enable_simulation_mode()
# 更新模拟数据
world.vr_manager.update_simulation_data('head_pose', new_data)
```
## 📚 API参考
### VR系统控制
- `world.initializeVR()` - 初始化VR系统
- `world.shutdownVR()` - 关闭VR系统
- `world.isVREnabled()` - 检查VR状态
- `world.getVRInfo()` - 获取VR信息
### 模拟模式
- `world.vr_manager.enable_simulation_mode()` - 启用模拟模式
- `world.vr_manager.get_simulation_data()` - 获取模拟数据
- `world.vr_manager.update_simulation_data()` - 更新模拟数据
### ALVR串流
- `world.initializeALVR()` - 初始化ALVR
- `world.startALVRStreaming()` - 开始串流
- `world.stopALVRStreaming()` - 停止串流
- `world.setALVRStreamQuality()` - 设置质量
### VR输入
- `world.startVRInput()` - 启动输入处理
- `world.showControllerRays()` - 显示控制器射线
- `world.getAllControllers()` - 获取所有控制器
- `world.getControllerState()` - 获取控制器状态
## 🎯 总结
现在的VR测试系统具有以下优势
1. **自动适应**: 自动在真实VR和模拟模式之间切换
2. **开发友好**: 无需VR硬件即可开发和测试
3. **完整功能**: 支持所有VR功能的测试
4. **详细反馈**: 提供清晰的状态信息和错误提示
5. **灵活配置**: 可根据需要调整各种参数
无论您是否拥有VR设备都可以使用这个测试系统来验证VR功能的正确性。

155
main.py
View File

@ -21,11 +21,6 @@ from core.selection import SelectionSystem
from core.event_handler import EventHandler
from core.tool_manager import ToolManager
from core.script_system import ScriptManager
from core.vr_manager import VRManager
from core.vr_input_handler import VRInputHandler
from core.alvr_streamer import ALVRStreamer
from core.patrol_system import PatrolSystem
from core.Command_System import CommandManager
from gui.gui_manager import GUIManager
from core.terrain_manager import TerrainManager
from scene.scene_manager import SceneManager
@ -96,10 +91,6 @@ class MyWorld(CoreWorld):
# 初始化界面管理系统
self.interface_manager = InterfaceManager(self)
# 初始化VR系统
self.vr_manager = VRManager(self)
self.vr_input_handler = VRInputHandler(self, self.vr_manager)
self.alvr_streamer = ALVRStreamer(self, self.vr_manager)
# 启动脚本系统
self.script_manager.start_system()
@ -119,6 +110,15 @@ class MyWorld(CoreWorld):
from core.collision_manager import CollisionManager
self.collision_manager = CollisionManager(self)
# 初始化VR管理器
try:
from core.vr_manager import VRManager
self.vr_manager = VRManager(self)
print("✓ VR管理器初始化完成")
except Exception as e:
print(f"⚠ VR管理器初始化失败: {e}")
self.vr_manager = None
# 调试选项
self.debug_collision = True # 是否显示碰撞体
@ -621,143 +621,6 @@ class MyWorld(CoreWorld):
def listAllScripts(self):
"""列出所有脚本信息"""
return self.script_manager.list_all_scripts()
# ==================== VR系统功能代理 ====================
# VR系统控制方法 - 代理到vr_manager
def initializeVR(self):
"""初始化VR系统"""
return self.vr_manager.initialize_vr()
def shutdownVR(self):
"""关闭VR系统"""
return self.vr_manager.shutdown_vr()
def isVREnabled(self):
"""检查VR是否启用"""
return self.vr_manager.is_vr_enabled()
def getVRInfo(self):
"""获取VR系统信息"""
return self.vr_manager.get_vr_info()
# VR输入处理方法 - 代理到vr_input_handler
def startVRInput(self):
"""启动VR输入处理"""
return self.vr_input_handler.start_input_handling()
def stopVRInput(self):
"""停止VR输入处理"""
return self.vr_input_handler.stop_input_handling()
def showControllerRays(self, show=True):
"""显示/隐藏控制器射线"""
return self.vr_input_handler.show_controller_rays(show)
def getControllerState(self, controller_id):
"""获取控制器状态"""
return self.vr_input_handler.get_controller_state(controller_id)
def getAllControllers(self):
"""获取所有控制器"""
return self.vr_input_handler.get_all_controllers()
def setVRGestureEnabled(self, enabled):
"""设置VR手势识别启用状态"""
return self.vr_input_handler.set_gesture_enabled(enabled)
def setVRInteractionEnabled(self, enabled):
"""设置VR交互启用状态"""
return self.vr_input_handler.set_interaction_enabled(enabled)
# ALVR串流方法 - 代理到alvr_streamer
def initializeALVR(self):
"""初始化ALVR串流"""
return self.alvr_streamer.initialize()
def startALVRStreaming(self):
"""开始ALVR串流"""
return self.alvr_streamer.start_streaming()
def stopALVRStreaming(self):
"""停止ALVR串流"""
return self.alvr_streamer.stop_streaming()
def getALVRStreamingStatus(self):
"""获取ALVR串流状态"""
return self.alvr_streamer.get_streaming_status()
def setALVRStreamQuality(self, width, height, fps, bitrate):
"""设置ALVR串流质量"""
return self.alvr_streamer.set_stream_quality(width, height, fps, bitrate)
def sendHapticFeedback(self, controller_id, duration, intensity):
"""发送触觉反馈"""
return self.alvr_streamer.send_haptic_feedback(controller_id, duration, intensity)
def shutdownALVR(self):
"""关闭ALVR串流"""
return self.alvr_streamer.shutdown()
def isALVRConnected(self):
"""检查ALVR是否连接"""
return self.alvr_streamer.is_connected()
def isALVRStreaming(self):
"""检查ALVR是否在串流"""
return self.alvr_streamer.is_streaming()
# 便捷方法
def enableVRMode(self):
"""启用VR模式一键启动"""
print("启用VR模式...")
# 1. 初始化VR系统
if not self.initializeVR():
print("VR系统初始化失败")
return False
# 2. 启动VR输入处理
if not self.startVRInput():
print("VR输入处理启动失败")
return False
# 3. 初始化ALVR串流
if self.initializeALVR():
print("✓ ALVR串流已启用")
# 自动开始串流
self.startALVRStreaming()
else:
print("⚠ ALVR串流启用失败但VR系统仍可用")
print("✓ VR模式已启用")
return True
def disableVRMode(self):
"""禁用VR模式一键关闭"""
print("禁用VR模式...")
# 1. 停止ALVR串流
self.stopALVRStreaming()
self.shutdownALVR()
# 2. 停止VR输入处理
self.stopVRInput()
# 3. 关闭VR系统
self.shutdownVR()
print("✓ VR模式已禁用")
def getVRStatus(self):
"""获取VR系统总体状态"""
return {
"vr_enabled": self.isVREnabled(),
"vr_info": self.getVRInfo(),
"controllers": self.getAllControllers(),
"alvr_connected": self.isALVRConnected(),
"alvr_streaming": self.isALVRStreaming(),
"streaming_status": self.getALVRStreamingStatus() if self.isALVRConnected() else None
}
def loadCesiumTileset(self,tileset_url,position=(0,0,0)):
return self.scene_manager.load_cesium_tileset(tileset_url,position)

View File

@ -98,3 +98,4 @@ webencodings==0.5.1
xdg==5
xkit==0.0.0
zipp==1.0.0
openvr==2.2.0

View File

@ -1,18 +0,0 @@
# VR功能依赖包
# 用于支持VR显示和ALVR串流功能
# OpenVR Python库 - VR系统核心
openvr>=1.26.7
# 网络和通信
psutil>=5.9.0
# 数学和科学计算
numpy>=1.21.0
# 图像处理
Pillow>=9.0.1
# 现有核心依赖
Panda3D>=1.10.15
PyQt5>=5.15.9

123
run_vr_test.sh Executable file
View File

@ -0,0 +1,123 @@
#!/bin/bash
# VR性能测试启动脚本
# 使用方法: ./run_vr_test.sh
echo "🚀 VR性能测试启动脚本"
echo "======================"
# 检查是否在正确的目录
if [ ! -f "vr_performance_test.py" ]; then
echo "❌ 错误找不到vr_performance_test.py文件"
echo "请在EG项目根目录运行此脚本"
exit 1
fi
# 检查Python是否可用
if ! command -v python3 &> /dev/null; then
if ! command -v python &> /dev/null; then
echo "❌ 错误找不到Python解释器"
exit 1
else
PYTHON_CMD="python"
fi
else
PYTHON_CMD="python3"
fi
echo "✓ 使用Python解释器: $PYTHON_CMD"
# 检查核心模块是否存在
if [ ! -d "core" ]; then
echo "❌ 错误找不到core目录"
echo "请确保在EG项目根目录中运行"
exit 1
fi
if [ ! -f "core/vr_manager.py" ]; then
echo "❌ 错误找不到core/vr_manager.py文件"
echo "VR管理器模块不存在"
exit 1
fi
echo "✓ 核心模块检查通过"
# 检查VR相关依赖
echo "🔍 检查VR依赖..."
$PYTHON_CMD -c "import openvr" 2>/dev/null
if [ $? -eq 0 ]; then
echo "✓ OpenVR Python绑定可用"
else
echo "⚠️ 警告OpenVR Python绑定不可用"
echo " 如果需要VR功能请安装: pip install openvr"
fi
$PYTHON_CMD -c "from panda3d.core import Vec3" 2>/dev/null
if [ $? -eq 0 ]; then
echo "✓ Panda3D可用"
else
echo "❌ 错误Panda3D不可用"
echo "请安装Panda3D: pip install panda3d"
exit 1
fi
# 检查可选的性能监控依赖
echo "🔍 检查性能监控依赖..."
$PYTHON_CMD -c "import psutil" 2>/dev/null
if [ $? -eq 0 ]; then
echo "✓ psutil可用CPU/内存监控)"
else
echo "⚠️ 建议安装psutil以获得完整性能监控: pip install psutil"
fi
$PYTHON_CMD -c "import GPUtil" 2>/dev/null
if [ $? -eq 0 ]; then
echo "✓ GPUtil可用GPU监控"
else
echo "⚠️ 建议安装GPUtil以获得GPU监控: pip install GPUtil"
fi
echo ""
echo "🎮 启动前检查清单:"
echo "□ VR头显已连接并开机"
echo "□ SteamVR正在运行"
echo "□ VR头显已完成初始设置"
echo "□ VR跟踪系统正常工作"
echo ""
# 询问用户是否继续
echo "是否继续启动VR性能测试"
echo "按Enter继续或按Ctrl+C取消..."
read -r
echo ""
echo "🚀 正在启动VR性能测试..."
echo "控制说明:"
echo " ESC - 退出测试"
echo " 1-9 - 设置场景复杂度"
echo " R - 重置性能计数器"
echo " P - 手动输出性能报告"
echo " D - 切换调试模式"
echo "VR分辨率控制"
echo " Q - 切换质量预设 (性能/平衡/质量)"
echo " [ - 降低分辨率缩放"
echo " ] - 提高分辨率缩放"
echo " I - 显示分辨率信息"
echo ""
# 启动测试
$PYTHON_CMD vr_performance_test.py
# 检查退出状态
if [ $? -eq 0 ]; then
echo ""
echo "✅ VR性能测试正常结束"
else
echo ""
echo "❌ VR性能测试异常退出错误代码$?"
fi
echo "📋 检查当前目录中的CSV文件获取详细性能数据"
echo "📖 查看VR_PERFORMANCE_TEST_README.md获取详细使用说明"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -767,6 +767,105 @@ class MainWindow(QMainWindow):
self.refreshAssetsAction = self.assetsMenu.addAction('刷新资源')
self.refreshAssetsAction.triggered.connect(self.refreshAssetsView)
# VR菜单
self.vrMenu = menubar.addMenu('VR')
self.enterVRAction = self.vrMenu.addAction('进入VR模式')
self.exitVRAction = self.vrMenu.addAction('退出VR模式')
self.vrMenu.addSeparator()
self.vrStatusAction = self.vrMenu.addAction('VR状态')
self.vrSettingsAction = self.vrMenu.addAction('VR设置')
self.vrMenu.addSeparator()
# VR调试子菜单
self.vrDebugMenu = self.vrMenu.addMenu('VR调试')
self.vrDebugToggleAction = self.vrDebugMenu.addAction('启用调试输出')
self.vrDebugToggleAction.setCheckable(True)
self.vrDebugToggleAction.setChecked(False) # 默认关闭(节省资源)
self.vrShowPerformanceAction = self.vrDebugMenu.addAction('立即显示性能报告')
self.vrDebugMenu.addSeparator()
# 调试模式切换
self.vrDebugModeMenu = self.vrDebugMenu.addMenu('输出模式')
self.vrDebugBriefAction = self.vrDebugModeMenu.addAction('简短模式')
self.vrDebugDetailedAction = self.vrDebugModeMenu.addAction('详细模式')
self.vrDebugBriefAction.setCheckable(True)
self.vrDebugDetailedAction.setCheckable(True)
self.vrDebugDetailedAction.setChecked(True) # 默认详细模式
# 创建调试模式动作组(单选)
from PyQt5.QtWidgets import QActionGroup
self.vrDebugModeGroup = QActionGroup(self)
self.vrDebugModeGroup.addAction(self.vrDebugBriefAction)
self.vrDebugModeGroup.addAction(self.vrDebugDetailedAction)
self.vrDebugMenu.addSeparator()
# 性能监控选项
self.vrPerformanceMonitorAction = self.vrDebugMenu.addAction('启用性能监控')
self.vrPerformanceMonitorAction.setCheckable(True)
self.vrPerformanceMonitorAction.setChecked(False) # 默认关闭(节省资源)
self.vrGpuTimingAction = self.vrDebugMenu.addAction('启用GPU时间监控')
self.vrGpuTimingAction.setCheckable(True)
self.vrGpuTimingAction.setChecked(False) # 默认关闭(节省资源)
# 管线监控选项
self.vrPipelineMonitorAction = self.vrDebugMenu.addAction('启用管线监控')
self.vrPipelineMonitorAction.setCheckable(True)
self.vrPipelineMonitorAction.setChecked(False) # 默认关闭(节省资源)
self.vrDebugMenu.addSeparator()
# 姿态策略选项
self.vrPoseStrategyMenu = self.vrDebugMenu.addMenu('姿态策略')
self.vrPoseRenderCallbackAction = self.vrPoseStrategyMenu.addAction('渲染回调策略')
self.vrPoseUpdateTaskAction = self.vrPoseStrategyMenu.addAction('更新任务策略')
self.vrPoseRenderCallbackAction.setCheckable(True)
self.vrPoseUpdateTaskAction.setCheckable(True)
self.vrPoseRenderCallbackAction.setChecked(True) # 默认策略
# 创建姿态策略动作组(单选)
self.vrPoseStrategyGroup = QActionGroup(self)
self.vrPoseStrategyGroup.addAction(self.vrPoseRenderCallbackAction)
self.vrPoseStrategyGroup.addAction(self.vrPoseUpdateTaskAction)
self.vrDebugMenu.addSeparator()
# 测试功能
self.vrTestPipelineAction = self.vrDebugMenu.addAction('测试管线监控')
# VR测试模式
self.vrTestModeAction = self.vrDebugMenu.addAction('VR测试模式')
self.vrTestModeAction.setCheckable(True)
self.vrTestModeAction.setChecked(False) # 默认关闭
# VR测试模式调试子菜单
self.vrTestDebugMenu = self.vrDebugMenu.addMenu('测试模式调试')
# 渐进式功能开关
self.vrTestSubmitTextureAction = self.vrTestDebugMenu.addAction('启用纹理提交')
self.vrTestSubmitTextureAction.setCheckable(True)
self.vrTestSubmitTextureAction.setChecked(False) # 默认关闭
self.vrTestWaitPosesAction = self.vrTestDebugMenu.addAction('启用姿态等待')
self.vrTestWaitPosesAction.setCheckable(True)
self.vrTestWaitPosesAction.setChecked(False) # 默认关闭
self.vrTestDebugMenu.addSeparator()
# 快捷测试预设
self.vrTestStep1Action = self.vrTestDebugMenu.addAction('步骤1: 只启用纹理提交')
self.vrTestStep2Action = self.vrTestDebugMenu.addAction('步骤2: 只启用姿态等待')
self.vrTestStep3Action = self.vrTestDebugMenu.addAction('步骤3: 同时启用两者')
self.vrTestResetAction = self.vrTestDebugMenu.addAction('重置: 禁用所有功能')
self.vrDebugSettingsAction = self.vrDebugMenu.addAction('调试设置...')
# 初始状态下禁用退出VR选项
self.exitVRAction.setEnabled(False)
# 帮助菜单
self.helpMenu = menubar.addMenu('帮助')
self.aboutAction = self.helpMenu.addAction('关于')
@ -1857,6 +1956,35 @@ class MainWindow(QMainWindow):
# self.toggleHotReloadAction.triggered.connect(self.onToggleHotReload)
# self.openScriptsManagerAction.triggered.connect(self.onOpenScriptsManager)
# 连接VR菜单事件
self.enterVRAction.triggered.connect(self.onEnterVR)
self.exitVRAction.triggered.connect(self.onExitVR)
self.vrStatusAction.triggered.connect(self.onShowVRStatus)
self.vrSettingsAction.triggered.connect(self.onShowVRSettings)
# 连接VR调试菜单事件
self.vrDebugToggleAction.triggered.connect(self.onToggleVRDebug)
self.vrShowPerformanceAction.triggered.connect(self.onShowVRPerformance)
self.vrDebugBriefAction.triggered.connect(lambda: self.onSetVRDebugMode('brief'))
self.vrDebugDetailedAction.triggered.connect(lambda: self.onSetVRDebugMode('detailed'))
self.vrPerformanceMonitorAction.triggered.connect(self.onToggleVRPerformanceMonitor)
self.vrGpuTimingAction.triggered.connect(self.onToggleVRGpuTiming)
self.vrPipelineMonitorAction.triggered.connect(self.onToggleVRPipelineMonitor)
self.vrPoseRenderCallbackAction.triggered.connect(lambda: self.onSetVRPoseStrategy('render_callback'))
self.vrPoseUpdateTaskAction.triggered.connect(lambda: self.onSetVRPoseStrategy('update_task'))
self.vrTestPipelineAction.triggered.connect(self.onTestVRPipeline)
self.vrTestModeAction.triggered.connect(self.onToggleVRTestMode)
# 连接VR测试模式调试菜单事件
self.vrTestSubmitTextureAction.triggered.connect(self.onToggleVRTestSubmitTexture)
self.vrTestWaitPosesAction.triggered.connect(self.onToggleVRTestWaitPoses)
self.vrTestStep1Action.triggered.connect(lambda: self.onSetVRTestStep(1))
self.vrTestStep2Action.triggered.connect(lambda: self.onSetVRTestStep(2))
self.vrTestStep3Action.triggered.connect(lambda: self.onSetVRTestStep(3))
self.vrTestResetAction.triggered.connect(lambda: self.onSetVRTestStep(0))
self.vrDebugSettingsAction.triggered.connect(self.onShowVRDebugSettings)
def onCopy(self):
"""复制操作"""
try:
@ -3114,13 +3242,21 @@ class MainWindow(QMainWindow):
# 清理工具管理器中的进程
if hasattr(self.world, 'tool_manager') and self.world.tool_manager:
print("🧹 清理工具管理器进程...")
self.world.tool_manager.cleanup_processes()
if hasattr(self.world.tool_manager, 'cleanup_processes'):
self.world.tool_manager.cleanup_processes()
else:
print("✓ 工具管理器无需清理进程")
# 停止更新定时器
if hasattr(self, 'updateTimer') and self.updateTimer:
self.updateTimer.stop()
print("⏹️ 更新定时器已停止")
# 清理VR资源
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
print("🧹 清理VR资源...")
self.world.vr_manager.cleanup()
# 清理Panda3D资源
if hasattr(self, 'pandaWidget') and self.pandaWidget:
print("🧹 清理Panda3D资源...")
@ -3308,6 +3444,545 @@ class MainWindow(QMainWindow):
else:
QMessageBox.warning(self, "错误", "高度图地形创建失败!")
# ==================== VR事件处理 ====================
def onEnterVR(self):
"""进入VR模式"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
success = self.world.vr_manager.enable_vr()
if success:
# 更新菜单状态
self.enterVRAction.setEnabled(False)
self.exitVRAction.setEnabled(True)
QMessageBox.information(self, "成功", "VR模式已启用\n请确保您的VR头显已正确连接。")
else:
QMessageBox.warning(self, "错误", "无法启用VR模式\n请检查:\n1. SteamVR是否正在运行\n2. VR头显是否已连接\n3. OpenVR库是否已正确安装")
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
except Exception as e:
QMessageBox.critical(self, "错误", f"启用VR模式时发生错误\n{str(e)}")
def onExitVR(self):
"""退出VR模式"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
self.world.vr_manager.disable_vr()
# 更新菜单状态
self.enterVRAction.setEnabled(True)
self.exitVRAction.setEnabled(False)
QMessageBox.information(self, "成功", "已退出VR模式")
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
except Exception as e:
QMessageBox.critical(self, "错误", f"退出VR模式时发生错误\n{str(e)}")
def onShowVRStatus(self):
"""显示VR状态"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
status = self.world.vr_manager.get_vr_status()
status_text = f"""VR系统状态
可用性: {'✅ 可用' if status['available'] else '❌ 不可用'}
初始化: {'✅ 已初始化' if status['initialized'] else '❌ 未初始化'}
启用状态: {'✅ 已启用' if status['enabled'] else '❌ 未启用'}
渲染分辨率: {status['eye_resolution'][0]}x{status['eye_resolution'][1]}
追踪设备数: {status['device_count']}
提示
- 如果VR不可用请确保已安装SteamVR并连接VR头显
- 如果OpenVR库未安装请运行pip install openvr
"""
QMessageBox.information(self, "VR状态", status_text)
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
except Exception as e:
QMessageBox.critical(self, "错误", f"获取VR状态时发生错误\n{str(e)}")
def onShowVRSettings(self):
"""显示VR设置对话框"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
dialog = self.createVRSettingsDialog()
dialog.exec_()
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
except Exception as e:
QMessageBox.critical(self, "错误", f"打开VR设置时发生错误\n{str(e)}")
def createVRSettingsDialog(self):
"""创建VR设置对话框"""
dialog = QDialog(self)
dialog.setWindowTitle("VR设置")
dialog.setModal(True)
dialog.resize(400, 300)
layout = QVBoxLayout(dialog)
# VR状态显示
status_group = QGroupBox("VR状态")
status_layout = QVBoxLayout()
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
status = self.world.vr_manager.get_vr_status()
available_label = QLabel(f"VR可用性: {'' if status['available'] else ''}")
available_label.setStyleSheet(f"color: {'green' if status['available'] else 'red'};")
status_layout.addWidget(available_label)
enabled_label = QLabel(f"VR状态: {'已启用' if status['enabled'] else '未启用'}")
enabled_label.setStyleSheet(f"color: {'green' if status['enabled'] else 'gray'};")
status_layout.addWidget(enabled_label)
resolution_label = QLabel(f"渲染分辨率: {status['eye_resolution'][0]}x{status['eye_resolution'][1]}")
status_layout.addWidget(resolution_label)
status_group.setLayout(status_layout)
layout.addWidget(status_group)
# 渲染设置
render_group = QGroupBox("渲染设置")
render_layout = QFormLayout()
# 渲染质量
quality_combo = QComboBox()
quality_combo.addItems(["", "", "", "超高"])
quality_combo.setCurrentText("")
render_layout.addRow("渲染质量:", quality_combo)
# 抗锯齿
aa_combo = QComboBox()
aa_combo.addItems(["", "2x", "4x", "8x"])
aa_combo.setCurrentText("4x")
render_layout.addRow("抗锯齿:", aa_combo)
render_group.setLayout(render_layout)
layout.addWidget(render_group)
# 性能设置
perf_group = QGroupBox("性能设置")
perf_layout = QFormLayout()
# 刷新率
refresh_combo = QComboBox()
refresh_combo.addItems(["72Hz", "90Hz", "120Hz", "144Hz"])
refresh_combo.setCurrentText("90Hz")
perf_layout.addRow("刷新率:", refresh_combo)
# 异步重投影
async_check = QCheckBox("启用异步重投影")
async_check.setChecked(True)
perf_layout.addRow("", async_check)
perf_group.setLayout(perf_layout)
layout.addWidget(perf_group)
# 按钮
button_layout = QHBoxLayout()
apply_button = QPushButton("应用")
ok_button = QPushButton("确定")
cancel_button = QPushButton("取消")
button_layout.addWidget(apply_button)
button_layout.addStretch()
button_layout.addWidget(ok_button)
button_layout.addWidget(cancel_button)
layout.addLayout(button_layout)
# 连接信号
apply_button.clicked.connect(lambda: self.applyVRSettings(dialog))
ok_button.clicked.connect(dialog.accept)
cancel_button.clicked.connect(dialog.reject)
return dialog
def applyVRSettings(self, dialog):
"""应用VR设置"""
try:
# 这里可以实现设置的保存和应用逻辑
QMessageBox.information(dialog, "成功", "VR设置已应用")
except Exception as e:
QMessageBox.critical(dialog, "错误", f"应用VR设置时发生错误\n{str(e)}")
# ==================== VR调试事件处理 ====================
def onToggleVRDebug(self):
"""切换VR调试输出"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
enabled = self.world.vr_manager.toggle_debug_output()
self.vrDebugToggleAction.setChecked(enabled)
status = "启用" if enabled else "禁用"
QMessageBox.information(self, "VR调试", f"VR调试输出已{status}")
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
except Exception as e:
QMessageBox.critical(self, "错误", f"切换VR调试时发生错误\n{str(e)}")
def onShowVRPerformance(self):
"""立即显示VR性能报告"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
if self.world.vr_manager.vr_enabled:
self.world.vr_manager.force_performance_report()
QMessageBox.information(self, "VR性能", "性能报告已输出到控制台")
else:
QMessageBox.warning(self, "提示", "请先启用VR模式")
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
except Exception as e:
QMessageBox.critical(self, "错误", f"显示VR性能报告时发生错误\n{str(e)}")
def onSetVRDebugMode(self, mode):
"""设置VR调试模式"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
self.world.vr_manager.set_debug_mode(mode)
# 更新菜单状态
if mode == 'brief':
self.vrDebugBriefAction.setChecked(True)
self.vrDebugDetailedAction.setChecked(False)
else:
self.vrDebugBriefAction.setChecked(False)
self.vrDebugDetailedAction.setChecked(True)
mode_name = "简短" if mode == 'brief' else "详细"
QMessageBox.information(self, "VR调试", f"调试模式已设置为:{mode_name}")
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
except Exception as e:
QMessageBox.critical(self, "错误", f"设置VR调试模式时发生错误\n{str(e)}")
def onToggleVRPerformanceMonitor(self):
"""切换VR性能监控"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
enabled = self.vrPerformanceMonitorAction.isChecked()
if enabled:
self.world.vr_manager.enable_performance_monitoring()
else:
self.world.vr_manager.disable_performance_monitoring()
status = "启用" if enabled else "禁用"
QMessageBox.information(self, "VR性能监控", f"VR性能监控已{status}")
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
self.vrPerformanceMonitorAction.setChecked(False)
except Exception as e:
QMessageBox.critical(self, "错误", f"切换VR性能监控时发生错误\n{str(e)}")
def onToggleVRGpuTiming(self):
"""切换VR GPU时间监控"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
enabled = self.vrGpuTimingAction.isChecked()
if enabled:
self.world.vr_manager.enable_gpu_timing_monitoring()
else:
self.world.vr_manager.disable_gpu_timing_monitoring()
status = "启用" if enabled else "禁用"
QMessageBox.information(self, "VR GPU监控", f"VR GPU时间监控已{status}")
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
self.vrGpuTimingAction.setChecked(False)
except Exception as e:
QMessageBox.critical(self, "错误", f"切换VR GPU时间监控时发生错误\n{str(e)}")
def onToggleVRPipelineMonitor(self):
"""切换VR管线监控"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
enabled = self.vrPipelineMonitorAction.isChecked()
self.world.vr_manager.enable_pipeline_monitoring = enabled
status = "启用" if enabled else "禁用"
print(f"✓ VR管线监控已{status}")
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
self.vrPipelineMonitorAction.setChecked(False)
except Exception as e:
QMessageBox.critical(self, "错误", f"切换VR管线监控时发生错误\n{str(e)}")
def onSetVRPoseStrategy(self, strategy):
"""设置VR姿态策略"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
success = self.world.vr_manager.set_pose_strategy(strategy)
if success:
strategy_names = {
'render_callback': '渲染回调策略',
'update_task': '更新任务策略'
}
QMessageBox.information(self, "VR姿态策略",
f"姿态策略已切换为:{strategy_names.get(strategy, strategy)}")
else:
QMessageBox.warning(self, "错误", f"无效的姿态策略:{strategy}")
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
except Exception as e:
QMessageBox.critical(self, "错误", f"设置VR姿态策略时发生错误\n{str(e)}")
def onTestVRPipeline(self):
"""测试VR管线监控功能"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
self.world.vr_manager.test_pipeline_monitoring()
QMessageBox.information(self, "VR管线测试", "管线监控测试已完成,请查看控制台输出。")
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
except Exception as e:
QMessageBox.critical(self, "错误", f"测试VR管线监控时发生错误\n{str(e)}")
def onShowVRDebugSettings(self):
"""显示VR调试设置对话框"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
dialog = self.createVRDebugSettingsDialog()
dialog.exec_()
else:
QMessageBox.warning(self, "错误", "VR管理器不可用")
except Exception as e:
QMessageBox.critical(self, "错误", f"打开VR调试设置时发生错误\n{str(e)}")
def createVRDebugSettingsDialog(self):
"""创建VR调试设置对话框"""
from PyQt5.QtWidgets import QCheckBox, QSlider
from PyQt5.QtCore import Qt
dialog = QDialog(self)
dialog.setWindowTitle("VR调试设置")
dialog.setModal(True)
dialog.resize(450, 400)
layout = QVBoxLayout(dialog)
# 获取当前设置
vr_manager = self.world.vr_manager
debug_status = vr_manager.get_debug_status()
perf_config = vr_manager.get_performance_monitoring_config()
# 调试状态显示
status_group = QGroupBox("调试状态")
status_layout = QVBoxLayout()
debug_enabled_label = QLabel(f"调试输出: {'启用' if debug_status['debug_enabled'] else '禁用'}")
debug_enabled_label.setStyleSheet(f"color: {'green' if debug_status['debug_enabled'] else 'red'};")
status_layout.addWidget(debug_enabled_label)
debug_mode_label = QLabel(f"调试模式: {debug_status['debug_mode']}")
status_layout.addWidget(debug_mode_label)
performance_label = QLabel(f"性能监控: {'启用' if debug_status['performance_monitoring'] else '禁用'}")
performance_label.setStyleSheet(f"color: {'green' if debug_status['performance_monitoring'] else 'red'};")
status_layout.addWidget(performance_label)
status_group.setLayout(status_layout)
layout.addWidget(status_group)
# 报告设置
report_group = QGroupBox("报告设置")
report_layout = QFormLayout()
# 报告间隔滑块 (5-120秒)
interval_slider = QSlider(Qt.Horizontal)
interval_slider.setMinimum(5)
interval_slider.setMaximum(120)
interval_slider.setValue(int(debug_status['report_interval_seconds']))
interval_slider.setTickPosition(QSlider.TicksBelow)
interval_slider.setTickInterval(15)
interval_label = QLabel(f"{int(debug_status['report_interval_seconds'])}")
interval_slider.valueChanged.connect(lambda v: interval_label.setText(f"{v}"))
interval_layout = QHBoxLayout()
interval_layout.addWidget(interval_slider)
interval_layout.addWidget(interval_label)
report_layout.addRow("报告间隔:", interval_layout)
# 性能检查间隔
check_interval_combo = QComboBox()
check_interval_combo.addItems(["0.1秒", "0.5秒", "1.0秒", "2.0秒"])
current_check_interval = perf_config['check_interval']
if current_check_interval == 0.1:
check_interval_combo.setCurrentIndex(0)
elif current_check_interval == 0.5:
check_interval_combo.setCurrentIndex(1)
elif current_check_interval == 1.0:
check_interval_combo.setCurrentIndex(2)
else:
check_interval_combo.setCurrentIndex(3)
report_layout.addRow("性能检查间隔:", check_interval_combo)
# 帧历史大小
frame_history_spin = QSpinBox()
frame_history_spin.setMinimum(10)
frame_history_spin.setMaximum(1000)
frame_history_spin.setValue(perf_config['frame_history_size'])
frame_history_spin.setSuffix("")
report_layout.addRow("帧时间历史:", frame_history_spin)
report_group.setLayout(report_layout)
layout.addWidget(report_group)
# 监控项目
monitor_group = QGroupBox("监控项目")
monitor_layout = QVBoxLayout()
cpu_check = QCheckBox("CPU使用率")
cpu_check.setChecked(perf_config['psutil_available'])
cpu_check.setEnabled(perf_config['psutil_available'])
monitor_layout.addWidget(cpu_check)
memory_check = QCheckBox("内存使用率")
memory_check.setChecked(perf_config['psutil_available'])
memory_check.setEnabled(perf_config['psutil_available'])
monitor_layout.addWidget(memory_check)
gpu_check = QCheckBox("GPU使用率")
gpu_check.setChecked(perf_config['gputil_available'] or perf_config['nvidia_ml_available'])
gpu_check.setEnabled(perf_config['gputil_available'] or perf_config['nvidia_ml_available'])
monitor_layout.addWidget(gpu_check)
frame_time_check = QCheckBox("帧时间统计")
frame_time_check.setChecked(True)
monitor_layout.addWidget(frame_time_check)
monitor_group.setLayout(monitor_layout)
layout.addWidget(monitor_group)
# 按钮
button_layout = QHBoxLayout()
apply_button = QPushButton("应用")
reset_button = QPushButton("重置计数器")
ok_button = QPushButton("确定")
cancel_button = QPushButton("取消")
button_layout.addWidget(apply_button)
button_layout.addWidget(reset_button)
button_layout.addStretch()
button_layout.addWidget(ok_button)
button_layout.addWidget(cancel_button)
layout.addLayout(button_layout)
# 连接信号
def apply_settings():
try:
# 应用报告间隔
new_interval_seconds = interval_slider.value()
new_interval_frames = int(new_interval_seconds * 60) # 假设60fps
vr_manager.set_performance_report_interval(new_interval_frames)
# 应用性能检查间隔
check_intervals = [0.1, 0.5, 1.0, 2.0]
new_check_interval = check_intervals[check_interval_combo.currentIndex()]
vr_manager.set_performance_check_interval(new_check_interval)
# 应用帧历史大小
vr_manager.set_frame_time_history_size(frame_history_spin.value())
QMessageBox.information(dialog, "成功", "VR调试设置已应用")
except Exception as e:
QMessageBox.critical(dialog, "错误", f"应用设置时发生错误:\n{str(e)}")
def reset_counters():
try:
vr_manager.reset_performance_counters()
QMessageBox.information(dialog, "成功", "性能计数器已重置!")
except Exception as e:
QMessageBox.critical(dialog, "错误", f"重置计数器时发生错误:\n{str(e)}")
apply_button.clicked.connect(apply_settings)
reset_button.clicked.connect(reset_counters)
ok_button.clicked.connect(lambda: (apply_settings(), dialog.accept()))
cancel_button.clicked.connect(dialog.reject)
return dialog
# ==================== VR测试模式事件处理 ====================
def onToggleVRTestMode(self):
"""切换VR测试模式"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
if self.vrTestModeAction.isChecked():
# 启用VR测试模式
success = self.world.vr_manager.enable_vr_test_mode(display_mode='stereo')
if success:
QMessageBox.information(self, "VR测试模式",
"VR测试模式已启用\n\n现在VR渲染内容将直接显示在PC屏幕上无需VR头显。\n\n特点:\n- 显示VR左右眼视图\n- 实时性能监控HUD\n- 复用完整VR渲染管线\n- 可测量纯渲染性能")
print("✅ VR测试模式已启用")
# 可选:自动开启性能测试
self.world.vr_manager.run_vr_performance_test(duration_seconds=10)
else:
self.vrTestModeAction.setChecked(False)
QMessageBox.warning(self, "错误", "启用VR测试模式失败")
else:
# 禁用VR测试模式
self.world.vr_manager.disable_vr_test_mode()
QMessageBox.information(self, "VR测试模式", "VR测试模式已禁用")
print("✅ VR测试模式已禁用")
else:
self.vrTestModeAction.setChecked(False)
QMessageBox.warning(self, "错误", "VR管理器不可用")
except Exception as e:
self.vrTestModeAction.setChecked(False)
QMessageBox.critical(self, "错误", f"切换VR测试模式时发生错误\n{str(e)}")
def onToggleVRTestSubmitTexture(self):
"""切换VR测试模式纹理提交功能"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
enabled = self.vrTestSubmitTextureAction.isChecked()
self.world.vr_manager.set_test_mode_features(submit_texture=enabled)
except Exception as e:
QMessageBox.critical(self, "错误", f"设置纹理提交功能时发生错误:\n{str(e)}")
def onToggleVRTestWaitPoses(self):
"""切换VR测试模式姿态等待功能"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
enabled = self.vrTestWaitPosesAction.isChecked()
self.world.vr_manager.set_test_mode_features(wait_poses=enabled)
except Exception as e:
QMessageBox.critical(self, "错误", f"设置姿态等待功能时发生错误:\n{str(e)}")
def onSetVRTestStep(self, step):
"""设置VR测试步骤"""
try:
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
if step == 0: # 重置
self.world.vr_manager.set_test_mode_features(submit_texture=False, wait_poses=False)
self.vrTestSubmitTextureAction.setChecked(False)
self.vrTestWaitPosesAction.setChecked(False)
QMessageBox.information(self, "VR测试", "已重置为基线状态:两个功能都禁用")
elif step == 1: # 只启用纹理提交
self.world.vr_manager.set_test_mode_features(submit_texture=True, wait_poses=False)
self.vrTestSubmitTextureAction.setChecked(True)
self.vrTestWaitPosesAction.setChecked(False)
QMessageBox.information(self, "VR测试", "步骤1只启用纹理提交\n观察FPS变化来判断submit_texture是否影响性能")
elif step == 2: # 只启用姿态等待
self.world.vr_manager.set_test_mode_features(submit_texture=False, wait_poses=True)
self.vrTestSubmitTextureAction.setChecked(False)
self.vrTestWaitPosesAction.setChecked(True)
QMessageBox.information(self, "VR测试", "步骤2只启用姿态等待\n观察FPS变化来判断waitGetPoses是否影响性能")
elif step == 3: # 同时启用两者
self.world.vr_manager.set_test_mode_features(submit_texture=True, wait_poses=True)
self.vrTestSubmitTextureAction.setChecked(True)
self.vrTestWaitPosesAction.setChecked(True)
QMessageBox.information(self, "VR测试", "步骤3同时启用两者\n这应该完全复现普通VR模式的36FPS问题")
except Exception as e:
QMessageBox.critical(self, "错误", f"设置VR测试步骤时发生错误\n{str(e)}")
def setup_main_window(world,path = None):
"""设置主窗口的便利函数"""
app = QApplication.instance()

View File

@ -1,412 +0,0 @@
"""
VR控制面板
提供VR系统的GUI控制界面
包括VR启用/禁用ALVR串流控制性能监控等功能
"""
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox,
QPushButton, QLabel, QProgressBar, QSlider,
QSpinBox, QCheckBox, QComboBox, QTextEdit,
QGridLayout, QFrame, QSizePolicy)
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
from PyQt5.QtGui import QFont, QColor, QPalette
class VRControlPanel(QWidget):
"""VR控制面板"""
# 信号定义
vr_enabled = pyqtSignal(bool)
alvr_streaming_changed = pyqtSignal(bool)
def __init__(self, world, parent=None):
super().__init__(parent)
self.world = world
self.setupUI()
self.connectSignals()
# 设置更新定时器
self.update_timer = QTimer()
self.update_timer.timeout.connect(self.updateStatus)
self.update_timer.start(1000) # 每秒更新一次
def setupUI(self):
"""设置界面"""
self.setWindowTitle("VR控制面板")
self.setMinimumSize(400, 600)
# 主布局
main_layout = QVBoxLayout(self)
# VR系统控制组
vr_control_group = self.createVRControlGroup()
main_layout.addWidget(vr_control_group)
# ALVR串流控制组
alvr_control_group = self.createALVRControlGroup()
main_layout.addWidget(alvr_control_group)
# 性能监控组
performance_group = self.createPerformanceGroup()
main_layout.addWidget(performance_group)
# 控制器状态组
controller_group = self.createControllerGroup()
main_layout.addWidget(controller_group)
# 日志输出组
log_group = self.createLogGroup()
main_layout.addWidget(log_group)
# 添加弹性空间
main_layout.addStretch()
def createVRControlGroup(self):
"""创建VR系统控制组"""
group = QGroupBox("VR系统控制")
layout = QVBoxLayout(group)
# 状态指示器
self.vr_status_label = QLabel("VR状态: 未启用")
self.vr_status_label.setStyleSheet("color: red; font-weight: bold;")
layout.addWidget(self.vr_status_label)
# 启用/禁用VR按钮
button_layout = QHBoxLayout()
self.enable_vr_button = QPushButton("启用VR")
self.enable_vr_button.clicked.connect(self.enableVR)
button_layout.addWidget(self.enable_vr_button)
self.disable_vr_button = QPushButton("禁用VR")
self.disable_vr_button.clicked.connect(self.disableVR)
self.disable_vr_button.setEnabled(False)
button_layout.addWidget(self.disable_vr_button)
layout.addLayout(button_layout)
# VR设备信息
self.vr_info_label = QLabel("VR设备: 未连接")
layout.addWidget(self.vr_info_label)
# VR选项
options_layout = QGridLayout()
# 控制器射线显示
self.show_rays_checkbox = QCheckBox("显示控制器射线")
self.show_rays_checkbox.stateChanged.connect(self.toggleControllerRays)
options_layout.addWidget(self.show_rays_checkbox, 0, 0)
# 手势识别
self.gesture_checkbox = QCheckBox("启用手势识别")
self.gesture_checkbox.stateChanged.connect(self.toggleGestureRecognition)
options_layout.addWidget(self.gesture_checkbox, 0, 1)
# VR交互
self.interaction_checkbox = QCheckBox("启用VR交互")
self.interaction_checkbox.setChecked(True)
self.interaction_checkbox.stateChanged.connect(self.toggleVRInteraction)
options_layout.addWidget(self.interaction_checkbox, 1, 0)
layout.addLayout(options_layout)
return group
def createALVRControlGroup(self):
"""创建ALVR串流控制组"""
group = QGroupBox("ALVR串流控制")
layout = QVBoxLayout(group)
# 连接状态
self.alvr_status_label = QLabel("ALVR状态: 未连接")
self.alvr_status_label.setStyleSheet("color: red; font-weight: bold;")
layout.addWidget(self.alvr_status_label)
# 串流控制按钮
button_layout = QHBoxLayout()
self.start_streaming_button = QPushButton("开始串流")
self.start_streaming_button.clicked.connect(self.startStreaming)
self.start_streaming_button.setEnabled(False)
button_layout.addWidget(self.start_streaming_button)
self.stop_streaming_button = QPushButton("停止串流")
self.stop_streaming_button.clicked.connect(self.stopStreaming)
self.stop_streaming_button.setEnabled(False)
button_layout.addWidget(self.stop_streaming_button)
layout.addLayout(button_layout)
# 串流质量设置
quality_layout = QGridLayout()
# 分辨率
quality_layout.addWidget(QLabel("分辨率宽度:"), 0, 0)
self.width_spinbox = QSpinBox()
self.width_spinbox.setRange(1920, 4096)
self.width_spinbox.setValue(2880)
self.width_spinbox.setSuffix(" px")
quality_layout.addWidget(self.width_spinbox, 0, 1)
quality_layout.addWidget(QLabel("分辨率高度:"), 1, 0)
self.height_spinbox = QSpinBox()
self.height_spinbox.setRange(1080, 2160)
self.height_spinbox.setValue(1700)
self.height_spinbox.setSuffix(" px")
quality_layout.addWidget(self.height_spinbox, 1, 1)
# 帧率
quality_layout.addWidget(QLabel("帧率:"), 2, 0)
self.fps_spinbox = QSpinBox()
self.fps_spinbox.setRange(60, 120)
self.fps_spinbox.setValue(72)
self.fps_spinbox.setSuffix(" fps")
quality_layout.addWidget(self.fps_spinbox, 2, 1)
# 比特率
quality_layout.addWidget(QLabel("比特率:"), 3, 0)
self.bitrate_spinbox = QSpinBox()
self.bitrate_spinbox.setRange(50, 500)
self.bitrate_spinbox.setValue(150)
self.bitrate_spinbox.setSuffix(" Mbps")
quality_layout.addWidget(self.bitrate_spinbox, 3, 1)
layout.addLayout(quality_layout)
# 应用设置按钮
self.apply_quality_button = QPushButton("应用质量设置")
self.apply_quality_button.clicked.connect(self.applyQualitySettings)
layout.addWidget(self.apply_quality_button)
return group
def createPerformanceGroup(self):
"""创建性能监控组"""
group = QGroupBox("性能监控")
layout = QGridLayout(group)
# FPS显示
layout.addWidget(QLabel("渲染FPS:"), 0, 0)
self.fps_label = QLabel("0")
self.fps_label.setStyleSheet("font-weight: bold; color: blue;")
layout.addWidget(self.fps_label, 0, 1)
# 串流FPS
layout.addWidget(QLabel("串流FPS:"), 1, 0)
self.stream_fps_label = QLabel("0")
self.stream_fps_label.setStyleSheet("font-weight: bold; color: green;")
layout.addWidget(self.stream_fps_label, 1, 1)
# 延迟
layout.addWidget(QLabel("延迟:"), 2, 0)
self.latency_label = QLabel("0 ms")
self.latency_label.setStyleSheet("font-weight: bold; color: orange;")
layout.addWidget(self.latency_label, 2, 1)
# 性能进度条
layout.addWidget(QLabel("CPU使用率:"), 3, 0)
self.cpu_progress = QProgressBar()
self.cpu_progress.setRange(0, 100)
layout.addWidget(self.cpu_progress, 3, 1)
layout.addWidget(QLabel("GPU使用率:"), 4, 0)
self.gpu_progress = QProgressBar()
self.gpu_progress.setRange(0, 100)
layout.addWidget(self.gpu_progress, 4, 1)
return group
def createControllerGroup(self):
"""创建控制器状态组"""
group = QGroupBox("控制器状态")
layout = QVBoxLayout(group)
# 控制器列表
self.controller_list = QTextEdit()
self.controller_list.setMaximumHeight(100)
self.controller_list.setReadOnly(True)
layout.addWidget(self.controller_list)
# 触觉反馈测试
haptic_layout = QHBoxLayout()
haptic_layout.addWidget(QLabel("触觉反馈测试:"))
self.haptic_button = QPushButton("发送震动")
self.haptic_button.clicked.connect(self.sendHapticFeedback)
haptic_layout.addWidget(self.haptic_button)
layout.addLayout(haptic_layout)
return group
def createLogGroup(self):
"""创建日志输出组"""
group = QGroupBox("系统日志")
layout = QVBoxLayout(group)
self.log_text = QTextEdit()
self.log_text.setMaximumHeight(150)
self.log_text.setReadOnly(True)
layout.addWidget(self.log_text)
# 清空日志按钮
clear_button = QPushButton("清空日志")
clear_button.clicked.connect(self.clearLog)
layout.addWidget(clear_button)
return group
def connectSignals(self):
"""连接信号"""
# 连接值变化信号
self.width_spinbox.valueChanged.connect(self.onQualityChanged)
self.height_spinbox.valueChanged.connect(self.onQualityChanged)
self.fps_spinbox.valueChanged.connect(self.onQualityChanged)
self.bitrate_spinbox.valueChanged.connect(self.onQualityChanged)
def enableVR(self):
"""启用VR"""
self.addLog("正在启用VR模式...")
if self.world.enableVRMode():
self.addLog("✓ VR模式已启用")
self.vr_enabled.emit(True)
else:
self.addLog("✗ VR模式启用失败")
def disableVR(self):
"""禁用VR"""
self.addLog("正在禁用VR模式...")
self.world.disableVRMode()
self.addLog("✓ VR模式已禁用")
self.vr_enabled.emit(False)
def startStreaming(self):
"""开始串流"""
self.addLog("正在开始ALVR串流...")
if self.world.startALVRStreaming():
self.addLog("✓ ALVR串流已开始")
self.alvr_streaming_changed.emit(True)
else:
self.addLog("✗ ALVR串流开始失败")
def stopStreaming(self):
"""停止串流"""
self.addLog("正在停止ALVR串流...")
self.world.stopALVRStreaming()
self.addLog("✓ ALVR串流已停止")
self.alvr_streaming_changed.emit(False)
def toggleControllerRays(self, checked):
"""切换控制器射线显示"""
self.world.showControllerRays(checked)
self.addLog(f"控制器射线: {'显示' if checked else '隐藏'}")
def toggleGestureRecognition(self, checked):
"""切换手势识别"""
self.world.setVRGestureEnabled(checked)
self.addLog(f"手势识别: {'启用' if checked else '禁用'}")
def toggleVRInteraction(self, checked):
"""切换VR交互"""
self.world.setVRInteractionEnabled(checked)
self.addLog(f"VR交互: {'启用' if checked else '禁用'}")
def applyQualitySettings(self):
"""应用质量设置"""
width = self.width_spinbox.value()
height = self.height_spinbox.value()
fps = self.fps_spinbox.value()
bitrate = self.bitrate_spinbox.value()
self.world.setALVRStreamQuality(width, height, fps, bitrate)
self.addLog(f"串流质量已设置: {width}x{height} @ {fps}fps, {bitrate}Mbps")
def sendHapticFeedback(self):
"""发送触觉反馈"""
controllers = self.world.getAllControllers()
if controllers:
controller_id = controllers[0] # 使用第一个控制器
self.world.sendHapticFeedback(controller_id, 0.5, 0.8)
self.addLog(f"已发送触觉反馈到控制器 {controller_id}")
else:
self.addLog("没有可用的控制器")
def onQualityChanged(self):
"""质量设置变化"""
# 可以在这里实现实时质量调整
pass
def updateStatus(self):
"""更新状态显示"""
# 更新VR状态
vr_status = self.world.getVRStatus()
# VR系统状态
if vr_status['vr_enabled']:
self.vr_status_label.setText("VR状态: 已启用")
self.vr_status_label.setStyleSheet("color: green; font-weight: bold;")
self.enable_vr_button.setEnabled(False)
self.disable_vr_button.setEnabled(True)
# 更新VR设备信息
vr_info = vr_status['vr_info']
if vr_info:
device_info = f"VR设备: {vr_info.get('hmd_manufacturer', 'Unknown')} {vr_info.get('hmd_model', 'Unknown')}"
self.vr_info_label.setText(device_info)
else:
self.vr_status_label.setText("VR状态: 未启用")
self.vr_status_label.setStyleSheet("color: red; font-weight: bold;")
self.enable_vr_button.setEnabled(True)
self.disable_vr_button.setEnabled(False)
self.vr_info_label.setText("VR设备: 未连接")
# ALVR状态
if vr_status['alvr_connected']:
self.alvr_status_label.setText("ALVR状态: 已连接")
self.alvr_status_label.setStyleSheet("color: green; font-weight: bold;")
self.start_streaming_button.setEnabled(not vr_status['alvr_streaming'])
self.stop_streaming_button.setEnabled(vr_status['alvr_streaming'])
else:
self.alvr_status_label.setText("ALVR状态: 未连接")
self.alvr_status_label.setStyleSheet("color: red; font-weight: bold;")
self.start_streaming_button.setEnabled(False)
self.stop_streaming_button.setEnabled(False)
# 性能统计
streaming_status = vr_status['streaming_status']
if streaming_status:
self.stream_fps_label.setText(str(streaming_status.get('fps', 0)))
self.latency_label.setText(f"{streaming_status.get('latency', 0)} ms")
# 控制器状态
controllers = vr_status['controllers']
if controllers:
controller_text = f"已连接 {len(controllers)} 个控制器:\n"
for i, controller_id in enumerate(controllers):
controller_text += f"控制器 {i+1}: ID {controller_id}\n"
self.controller_list.setText(controller_text)
else:
self.controller_list.setText("没有连接的控制器")
def addLog(self, message):
"""添加日志"""
import datetime
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
log_message = f"[{timestamp}] {message}"
self.log_text.append(log_message)
print(log_message) # 同时输出到控制台
def clearLog(self):
"""清空日志"""
self.log_text.clear()
def closeEvent(self, event):
"""关闭事件"""
# 停止更新定时器
if hasattr(self, 'update_timer'):
self.update_timer.stop()
# 如果VR启用先禁用
if self.world.isVREnabled():
self.disableVR()
event.accept()

89
vr_actions/actions.json Normal file
View File

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

View File

@ -0,0 +1,133 @@
{
"controller_type": "knuckles",
"description": "Valve Index\u63a7\u5236\u5668\u7ed1\u5b9a",
"name": "EG VR Editor - Index",
"bindings": {
"/actions/default": {
"sources": [
{
"inputs": {
"click": {
"output": "/actions/default/in/Trigger"
}
},
"mode": "button",
"path": "/user/hand/left/input/trigger"
},
{
"inputs": {
"click": {
"output": "/actions/default/in/Trigger"
}
},
"mode": "button",
"path": "/user/hand/right/input/trigger"
},
{
"inputs": {
"value": {
"output": "/actions/default/in/Squeeze"
}
},
"mode": "trigger",
"path": "/user/hand/left/input/grip"
},
{
"inputs": {
"value": {
"output": "/actions/default/in/Squeeze"
}
},
"mode": "trigger",
"path": "/user/hand/right/input/grip"
},
{
"inputs": {
"click": {
"output": "/actions/default/in/AButton"
}
},
"mode": "button",
"path": "/user/hand/right/input/a"
},
{
"inputs": {
"click": {
"output": "/actions/default/in/BButton"
}
},
"mode": "button",
"path": "/user/hand/right/input/b"
},
{
"inputs": {
"position": {
"output": "/actions/default/in/Trackpad"
},
"click": {
"output": "/actions/default/in/TrackpadClick"
},
"touch": {
"output": "/actions/default/in/TrackpadTouch"
}
},
"mode": "trackpad",
"path": "/user/hand/left/input/trackpad"
},
{
"inputs": {
"position": {
"output": "/actions/default/in/Trackpad"
},
"click": {
"output": "/actions/default/in/TrackpadClick"
},
"touch": {
"output": "/actions/default/in/TrackpadTouch"
}
},
"mode": "trackpad",
"path": "/user/hand/right/input/trackpad"
},
{
"inputs": {
"position": {
"output": "/actions/default/in/Joystick"
}
},
"mode": "joystick",
"path": "/user/hand/left/input/thumbstick"
},
{
"inputs": {
"position": {
"output": "/actions/default/in/Joystick"
}
},
"mode": "joystick",
"path": "/user/hand/right/input/thumbstick"
}
],
"poses": [
{
"output": "/actions/default/in/Pose",
"path": "/user/hand/left/pose/raw"
},
{
"output": "/actions/default/in/Pose",
"path": "/user/hand/right/pose/raw"
}
],
"haptics": [
{
"output": "/actions/default/out/Haptic",
"path": "/user/hand/left/output/haptic"
},
{
"output": "/actions/default/out/Haptic",
"path": "/user/hand/right/output/haptic"
}
]
}
}
}

View File

@ -0,0 +1,118 @@
{
"controller_type": "oculus_touch",
"description": "Oculus Touch\u63a7\u5236\u5668\u7ed1\u5b9a",
"name": "EG VR Editor - Oculus Touch",
"bindings": {
"/actions/default": {
"sources": [
{
"inputs": {
"click": {
"output": "/actions/default/in/Trigger"
}
},
"mode": "button",
"path": "/user/hand/left/input/trigger"
},
{
"inputs": {
"click": {
"output": "/actions/default/in/Trigger"
}
},
"mode": "button",
"path": "/user/hand/right/input/trigger"
},
{
"inputs": {
"value": {
"output": "/actions/default/in/Squeeze"
}
},
"mode": "trigger",
"path": "/user/hand/left/input/grip"
},
{
"inputs": {
"value": {
"output": "/actions/default/in/Squeeze"
}
},
"mode": "trigger",
"path": "/user/hand/right/input/grip"
},
{
"inputs": {
"click": {
"output": "/actions/default/in/AButton"
}
},
"mode": "button",
"path": "/user/hand/right/input/a"
},
{
"inputs": {
"click": {
"output": "/actions/default/in/BButton"
}
},
"mode": "button",
"path": "/user/hand/right/input/b"
},
{
"inputs": {
"position": {
"output": "/actions/default/in/Joystick"
},
"click": {
"output": "/actions/default/in/TrackpadClick"
}
},
"mode": "joystick",
"path": "/user/hand/left/input/thumbstick"
},
{
"inputs": {
"position": {
"output": "/actions/default/in/Joystick"
},
"click": {
"output": "/actions/default/in/TrackpadClick"
}
},
"mode": "joystick",
"path": "/user/hand/right/input/thumbstick"
},
{
"inputs": {
"click": {
"output": "/actions/default/in/Menu"
}
},
"mode": "button",
"path": "/user/hand/left/input/menu"
}
],
"poses": [
{
"output": "/actions/default/in/Pose",
"path": "/user/hand/left/pose/raw"
},
{
"output": "/actions/default/in/Pose",
"path": "/user/hand/right/pose/raw"
}
],
"haptics": [
{
"output": "/actions/default/out/Haptic",
"path": "/user/hand/left/output/haptic"
},
{
"output": "/actions/default/out/Haptic",
"path": "/user/hand/right/output/haptic"
}
]
}
}
}

View File

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

View File

@ -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()