From af0a999191a423cf4b34201665692d307e018375 Mon Sep 17 00:00:00 2001 From: Rowland <975945824@qq.com> Date: Mon, 15 Sep 2025 10:03:47 +0800 Subject: [PATCH] =?UTF-8?q?vr=E7=AC=AC=E4=B8=80=E6=AC=A1=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 112 ++++ .../samples/04-Material-Blending/main.py | 2 +- RenderPipelineFile/samples/06-Car/main.py | 5 - core/alvr_streamer.py | 509 ---------------- core/vr_input_handler.py | 430 ------------- core/vr_manager.py | 573 ------------------ demo/VR_ALVR_实现指南.md | 374 ------------ demo/VR测试说明.md | 253 -------- main.py | 144 ----- requirements/vr-requirements.txt | 18 - test_metallic_gradient.png | Bin 1304 -> 0 bytes test_metallic_stripes.png | Bin 1308 -> 0 bytes test_roughness_checkerboard.png | Bin 1364 -> 0 bytes test_roughness_circle.png | Bin 19408 -> 0 bytes test_roughness_gradient.png | Bin 1304 -> 0 bytes ui/vr_control_panel.py | 412 ------------- vr_test.py | 468 -------------- 17 files changed, 113 insertions(+), 3187 deletions(-) create mode 100644 CLAUDE.md delete mode 100644 core/alvr_streamer.py delete mode 100644 core/vr_input_handler.py delete mode 100644 core/vr_manager.py delete mode 100644 demo/VR_ALVR_实现指南.md delete mode 100644 demo/VR测试说明.md delete mode 100644 requirements/vr-requirements.txt delete mode 100644 test_metallic_gradient.png delete mode 100644 test_metallic_stripes.png delete mode 100644 test_roughness_checkerboard.png delete mode 100644 test_roughness_circle.png delete mode 100644 test_roughness_gradient.png delete mode 100644 ui/vr_control_panel.py delete mode 100644 vr_test.py diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..1fc3378b --- /dev/null +++ b/CLAUDE.md @@ -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支持 +- 某些功能可能需要特定的系统配置 \ No newline at end of file diff --git a/RenderPipelineFile/samples/04-Material-Blending/main.py b/RenderPipelineFile/samples/04-Material-Blending/main.py index 296373b2..a56cf579 100644 --- a/RenderPipelineFile/samples/04-Material-Blending/main.py +++ b/RenderPipelineFile/samples/04-Material-Blending/main.py @@ -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") diff --git a/RenderPipelineFile/samples/06-Car/main.py b/RenderPipelineFile/samples/06-Car/main.py index da6715f8..1720104b 100644 --- a/RenderPipelineFile/samples/06-Car/main.py +++ b/RenderPipelineFile/samples/06-Car/main.py @@ -52,11 +52,6 @@ class MainApp(ShowBase): # Load the scene model = loader.loadModel("scene/scene.bam") # model = loader.loadModel("scene2/Scene.bam") - model_0 = self.loader.loadModel("/home/tiger/下载/Benci/source/s65/s65/s65.fbx") - model_0.reparentTo(self.render) - model_0.setScale(0.01) - model_0.setPos(-8, 42, 0) - model_0.setHpr(0, 90, 0) model.reparent_to(render) self.render_pipeline.prepare_scene(model) diff --git a/core/alvr_streamer.py b/core/alvr_streamer.py deleted file mode 100644 index 2783c5bd..00000000 --- a/core/alvr_streamer.py +++ /dev/null @@ -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('= 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 \ No newline at end of file diff --git a/core/vr_input_handler.py b/core/vr_input_handler.py deleted file mode 100644 index 2438e601..00000000 --- a/core/vr_input_handler.py +++ /dev/null @@ -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 '禁用'}") \ No newline at end of file diff --git a/core/vr_manager.py b/core/vr_manager.py deleted file mode 100644 index c6830b06..00000000 --- a/core/vr_manager.py +++ /dev/null @@ -1,573 +0,0 @@ -import warnings -warnings.filterwarnings("ignore", category=DeprecationWarning) - -from panda3d.core import * -from direct.showbase.DirectObject import DirectObject -from direct.task import Task -import sys - -class VRManager(DirectObject): - """VR管理器 - 处理VR系统初始化、追踪和渲染""" - - def __init__(self, world): - super().__init__() - self.world = world - self.vr_enabled = False - self.vr_system = None - self.vr_compositor = None - self.render_width = 1920 - self.render_height = 1080 - self.alvr_enabled = False - - # 模拟模式设置 - self.simulation_mode = False - self.simulation_data = { - 'head_pose': {'position': [0, 0, 1.6], 'rotation': [0, 0, 0, 1]}, - 'controller_poses': { - 0: {'position': [-0.3, 0, 1.2], 'rotation': [0, 0, 0, 1], 'connected': True}, - 1: {'position': [0.3, 0, 1.2], 'rotation': [0, 0, 0, 1], 'connected': True} - }, - 'render_size': (1920, 1080) - } - - # 立体渲染缓冲区 - self.left_eye_buffer = None - self.right_eye_buffer = None - self.left_eye_camera = None - self.right_eye_camera = None - - # 控制器相关 - self.controller_nodes = {} - self.controller_poses = {} - - print("✓ VR管理器初始化完成") - - def initialize_vr(self, force_simulation=False): - """初始化VR系统""" - try: - # 检查是否强制使用模拟模式 - if force_simulation: - print("🔧 强制启用VR模拟模式") - return self._init_simulation_mode() - - # 检查OpenVR支持 - if not self._check_openvr_support(): - print("⚠ OpenVR支持不可用,切换到模拟模式") - return self._init_simulation_mode() - - # 尝试初始化OpenVR - if not self._init_openvr(): - print("⚠ OpenVR初始化失败,切换到模拟模式") - print("提示: 请确保SteamVR正在运行且VR头盔已连接") - return self._init_simulation_mode() - - # 真实VR模式初始化成功 - print("✓ 真实VR模式初始化成功") - return self._init_real_vr_mode() - - except Exception as e: - print(f"VR初始化错误: {str(e)}") - print("⚠ 切换到模拟模式") - return self._init_simulation_mode() - - def _init_simulation_mode(self): - """初始化模拟模式""" - try: - self.simulation_mode = True - - # 使用模拟数据设置渲染尺寸 - self.render_width, self.render_height = self.simulation_data['render_size'] - print(f"🎮 模拟VR渲染尺寸: {self.render_width}x{self.render_height}") - - # 设置模拟立体渲染 - self._setup_stereo_rendering() - - # 启动模拟VR任务 - self._start_simulation_tasks() - - self.vr_enabled = True - print("✓ VR模拟模式初始化完成") - print("ℹ 模拟模式说明:") - print(" - 头盔追踪: 模拟数据") - print(" - 控制器: 模拟两个控制器") - print(" - 渲染: 立体渲染到窗口") - print(" - 交互: 键盘鼠标模拟") - - return True - - except Exception as e: - print(f"模拟模式初始化错误: {str(e)}") - return False - - def _init_real_vr_mode(self): - """初始化真实VR模式""" - try: - self.simulation_mode = False - - # 设置立体渲染 - self._setup_stereo_rendering() - - # 初始化ALVR - if self._init_alvr(): - print("✓ ALVR串流已启用") - self.alvr_enabled = True - - # 启动VR更新任务 - self._start_vr_tasks() - - self.vr_enabled = True - print("✓ 真实VR系统初始化完成") - return True - - except Exception as e: - print(f"真实VR模式初始化错误: {str(e)}") - return False - - def _check_openvr_support(self): - """检查OpenVR支持""" - try: - import openvr - return True - except ImportError: - print("OpenVR库未安装") - return False - - def _init_openvr(self): - """初始化OpenVR""" - try: - import openvr - - # 初始化OpenVR - self.vr_system = openvr.init(openvr.VRApplication_Scene) - if not self.vr_system: - return False - - # 获取合成器 - self.vr_compositor = openvr.VRCompositor() - if not self.vr_compositor: - return False - - # 获取推荐的渲染尺寸 - self.render_width, self.render_height = self.vr_system.getRecommendedRenderTargetSize() - print(f"VR推荐渲染尺寸: {self.render_width}x{self.render_height}") - - return True - - except Exception as e: - print(f"OpenVR初始化错误: {str(e)}") - return False - - def _setup_stereo_rendering(self): - """设置立体渲染""" - try: - # 创建眼部缓冲区 - self._create_eye_buffers() - - # 创建眼部摄像机 - self._create_eye_cameras() - - # 设置渲染目标 - self._setup_render_targets() - - print("✓ 立体渲染设置完成") - - except Exception as e: - print(f"立体渲染设置错误: {str(e)}") - - def _create_eye_buffers(self): - """创建眼部渲染缓冲区""" - try: - # 创建左眼缓冲区 - self.left_eye_buffer = self.world.win.makeTextureBuffer( - "left_eye", self.render_width, self.render_height - ) - self.left_eye_buffer.setSort(-100) - - # 创建右眼缓冲区 - self.right_eye_buffer = self.world.win.makeTextureBuffer( - "right_eye", self.render_width, self.render_height - ) - self.right_eye_buffer.setSort(-99) - - # 获取纹理 - self.left_eye_texture = self.left_eye_buffer.getTexture() - self.right_eye_texture = self.right_eye_buffer.getTexture() - - print("✓ 眼部渲染缓冲区创建完成") - - except Exception as e: - print(f"眼部缓冲区创建错误: {str(e)}") - - def _create_eye_cameras(self): - """创建眼部摄像机""" - try: - # 创建左眼摄像机 - self.left_eye_camera = self.world.makeCamera(self.left_eye_buffer) - self.left_eye_camera.setPos(-0.032, 0, 0) # 瞳距的一半 - - # 创建右眼摄像机 - self.right_eye_camera = self.world.makeCamera(self.right_eye_buffer) - self.right_eye_camera.setPos(0.032, 0, 0) # 瞳距的一半 - - # 设置投影矩阵 - if not self.simulation_mode: - self._update_eye_projection(0, self.left_eye_camera.node().getLens()) - self._update_eye_projection(1, self.right_eye_camera.node().getLens()) - else: - # 模拟模式使用标准透视投影 - lens = PerspectiveLens() - lens.setFov(110) # 模拟VR FOV - lens.setNearFar(0.1, 1000) - self.left_eye_camera.node().setLens(lens) - self.right_eye_camera.node().setLens(lens) - - print("✓ 眼部摄像机创建完成") - - except Exception as e: - print(f"眼部摄像机创建错误: {str(e)}") - - def _update_eye_projection(self, eye, lens): - """更新眼部投影矩阵""" - try: - if self.simulation_mode: - # 模拟模式使用标准投影 - perspective_lens = PerspectiveLens() - perspective_lens.setFov(110) - perspective_lens.setNearFar(0.1, 1000) - lens.copyFrom(perspective_lens) - return - - import openvr - - # 获取投影矩阵 - projection_matrix = self.vr_system.getProjectionMatrix(eye, 0.1, 1000.0) - - # 转换为Panda3D矩阵 - panda_matrix = self._convert_openvr_matrix(projection_matrix) - - # 设置自定义投影矩阵 - lens.setCustomProjectionMatrix(panda_matrix) - - except Exception as e: - print(f"眼部投影更新错误: {str(e)}") - - def _setup_render_targets(self): - """设置渲染目标""" - try: - # 在模拟模式下,可以选择将渲染结果显示到主窗口 - if self.simulation_mode: - # 创建并排显示的立体视图 - self._setup_simulation_display() - - print("✓ 渲染目标设置完成") - - except Exception as e: - print(f"渲染目标设置错误: {str(e)}") - - def _setup_simulation_display(self): - """设置模拟显示""" - try: - # 创建卡片来显示眼部纹理 - cm = CardMaker("stereo_display") - - # 左眼显示区域 - cm.setFrame(-1, 0, -1, 1) - left_card = self.world.render2d.attachNewNode(cm.generate()) - left_card.setTexture(self.left_eye_texture) - - # 右眼显示区域 - cm.setFrame(0, 1, -1, 1) - right_card = self.world.render2d.attachNewNode(cm.generate()) - right_card.setTexture(self.right_eye_texture) - - print("✓ 模拟立体显示设置完成") - - except Exception as e: - print(f"模拟显示设置错误: {str(e)}") - - def _init_alvr(self): - """初始化ALVR(仅在真实VR模式下)""" - if self.simulation_mode: - print("ℹ 模拟模式: ALVR串流已跳过") - return False - - try: - # ALVR初始化逻辑 - # 这里应该连接到ALVR服务器 - print("✓ ALVR初始化完成") - return True - except Exception as e: - print(f"ALVR初始化错误: {str(e)}") - return False - - def _start_vr_tasks(self): - """启动VR更新任务(真实VR模式)""" - if not self.simulation_mode: - taskMgr.add(self._update_vr_tracking, "vr_tracking") - taskMgr.add(self._update_vr_rendering, "vr_rendering") - - def _start_simulation_tasks(self): - """启动模拟VR任务""" - taskMgr.add(self._update_simulation_tracking, "simulation_tracking") - taskMgr.add(self._update_simulation_rendering, "simulation_rendering") - - def _update_simulation_tracking(self, task): - """更新模拟追踪数据""" - try: - # 模拟头部追踪(可以添加一些变化) - import math - time_factor = task.time * 0.5 - - # 模拟轻微的头部摆动 - head_pos = self.simulation_data['head_pose']['position'] - head_pos[1] = math.sin(time_factor) * 0.05 # 前后轻微摆动 - - # 更新主摄像机位置 - if hasattr(self.world, 'camera'): - self.world.camera.setPos(head_pos[0], head_pos[1], head_pos[2]) - - # 更新控制器位置(模拟手部动作) - for controller_id, pose in self.simulation_data['controller_poses'].items(): - if pose['connected']: - # 模拟控制器轻微移动 - pose['position'][1] = math.sin(time_factor + controller_id) * 0.1 - - # 更新控制器节点位置 - if controller_id in self.controller_nodes: - node = self.controller_nodes[controller_id] - node.setPos(pose['position'][0], pose['position'][1], pose['position'][2]) - - return task.cont - - except Exception as e: - print(f"模拟追踪更新错误: {str(e)}") - return task.cont - - def _update_simulation_rendering(self, task): - """更新模拟渲染""" - try: - # 在模拟模式下,渲染已经由Panda3D自动处理 - # 这里可以添加任何特殊的渲染逻辑 - return task.cont - - except Exception as e: - print(f"模拟渲染更新错误: {str(e)}") - return task.cont - - def _update_vr_tracking(self, task): - """更新VR追踪数据(真实VR模式)""" - try: - if not self.vr_system or self.simulation_mode: - return task.cont - - import openvr - - # 获取设备姿态 - poses, game_poses = self.vr_compositor.waitGetPoses(None, None) - - # 更新头显位置 - if poses[openvr.k_unTrackedDeviceIndex_Hmd].bPoseIsValid: - self._update_main_camera_pose() - - # 更新眼部摄像机 - self._update_eye_cameras() - - # 更新控制器姿态 - self._update_controller_poses(poses) - - return task.cont - - except Exception as e: - print(f"VR追踪更新错误: {str(e)}") - return task.cont - - def _update_vr_rendering(self, task): - """更新VR渲染(真实VR模式)""" - try: - if not self.vr_compositor or self.simulation_mode: - return task.cont - - # 提交帧到合成器 - self._submit_frames_to_compositor() - - return task.cont - - except Exception as e: - print(f"VR渲染更新错误: {str(e)}") - return task.cont - - def _convert_openvr_matrix(self, openvr_matrix): - """转换OpenVR矩阵为Panda3D矩阵""" - # 实现矩阵转换逻辑 - mat = Mat4() - # 这里需要实现具体的矩阵转换 - return mat - - def _update_main_camera_pose(self): - """更新主摄像机姿态""" - try: - if self.simulation_mode: - return - - # 从VR系统获取头显姿态并应用到主摄像机 - pass - - except Exception as e: - print(f"主摄像机姿态更新错误: {str(e)}") - - def _update_eye_cameras(self): - """更新眼部摄像机""" - try: - if self.simulation_mode: - return - - # 更新左右眼摄像机位置 - pass - - except Exception as e: - print(f"眼部摄像机更新错误: {str(e)}") - - def _update_controller_poses(self, poses): - """更新控制器姿态""" - try: - if self.simulation_mode: - return - - # 更新控制器位置和姿态 - pass - - except Exception as e: - print(f"控制器姿态更新错误: {str(e)}") - - def _submit_frames_to_compositor(self): - """提交帧到合成器""" - try: - if not self.vr_compositor or self.simulation_mode: - return - - import openvr - - # 提交左眼纹理 - left_eye_texture = openvr.Texture_t() - left_eye_texture.handle = self.left_eye_texture.getTextureId() - left_eye_texture.eType = openvr.TextureType_OpenGL - left_eye_texture.eColorSpace = openvr.ColorSpace_Gamma - self.vr_compositor.submit(openvr.Eye_Left, left_eye_texture) - - # 提交右眼纹理 - right_eye_texture = openvr.Texture_t() - right_eye_texture.handle = self.right_eye_texture.getTextureId() - right_eye_texture.eType = openvr.TextureType_OpenGL - right_eye_texture.eColorSpace = openvr.ColorSpace_Gamma - self.vr_compositor.submit(openvr.Eye_Right, right_eye_texture) - - except Exception as e: - print(f"帧提交错误: {str(e)}") - - def get_controller_input(self, controller_id): - """获取控制器输入""" - try: - if self.simulation_mode: - # 返回模拟的控制器输入 - return { - 'trigger': 0.0, - 'grip': 0.0, - 'touchpad': {'x': 0.0, 'y': 0.0, 'pressed': False}, - 'menu': False, - 'connected': controller_id in self.simulation_data['controller_poses'] - } - - if not self.vr_system: - return None - - import openvr - - # 获取控制器状态 - result, controller_state = self.vr_system.getControllerState(controller_id) - if not result: - return None - - # 解析控制器输入 - return { - 'trigger': controller_state.rAxis[1].x, - 'grip': controller_state.rAxis[2].x, - 'touchpad': { - 'x': controller_state.rAxis[0].x, - 'y': controller_state.rAxis[0].y, - 'pressed': controller_state.rAxis[0].x != 0 or controller_state.rAxis[0].y != 0 - }, - 'menu': controller_state.ulButtonPressed & (1 << openvr.k_EButton_ApplicationMenu) != 0, - 'connected': True - } - - except Exception as e: - print(f"控制器输入获取错误: {str(e)}") - return None - - def shutdown_vr(self): - """关闭VR系统""" - try: - # 停止任务 - if self.simulation_mode: - taskMgr.remove("simulation_tracking") - taskMgr.remove("simulation_rendering") - else: - taskMgr.remove("vr_tracking") - taskMgr.remove("vr_rendering") - - # 关闭OpenVR - if self.vr_system and not self.simulation_mode: - import openvr - openvr.shutdown() - - # 清理资源 - self.left_eye_buffer = None - self.right_eye_buffer = None - self.left_eye_camera = None - self.right_eye_camera = None - - self.vr_enabled = False - self.simulation_mode = False - - print("✓ VR系统已关闭") - - except Exception as e: - print(f"VR关闭错误: {str(e)}") - - def is_vr_enabled(self): - """检查VR是否启用""" - return self.vr_enabled - - def get_vr_info(self): - """获取VR系统信息""" - info = { - 'enabled': self.vr_enabled, - 'simulation_mode': self.simulation_mode, - 'render_size': (self.render_width, self.render_height), - 'alvr_enabled': self.alvr_enabled - } - - if self.simulation_mode: - info['mode'] = 'simulation' - info['controllers'] = len(self.simulation_data['controller_poses']) - else: - info['mode'] = 'real_vr' - info['openvr_connected'] = self.vr_system is not None - - return info - - # 便捷方法 - def enable_simulation_mode(self): - """启用模拟模式(调试用)""" - return self.initialize_vr(force_simulation=True) - - def get_simulation_data(self): - """获取模拟数据""" - return self.simulation_data if self.simulation_mode else None - - def update_simulation_data(self, key, value): - """更新模拟数据""" - if self.simulation_mode and key in self.simulation_data: - self.simulation_data[key] = value - return True - return False \ No newline at end of file diff --git a/demo/VR_ALVR_实现指南.md b/demo/VR_ALVR_实现指南.md deleted file mode 100644 index b788f31e..00000000 --- a/demo/VR_ALVR_实现指南.md +++ /dev/null @@ -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文件。 \ No newline at end of file diff --git a/demo/VR测试说明.md b/demo/VR测试说明.md deleted file mode 100644 index 7303f308..00000000 --- a/demo/VR测试说明.md +++ /dev/null @@ -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功能的正确性。 \ No newline at end of file diff --git a/main.py b/main.py index 9bca08bc..de7d3872 100644 --- a/main.py +++ b/main.py @@ -19,9 +19,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 gui.gui_manager import GUIManager from core.terrain_manager import TerrainManager from scene.scene_manager import SceneManager @@ -83,10 +80,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() @@ -606,143 +599,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) diff --git a/requirements/vr-requirements.txt b/requirements/vr-requirements.txt deleted file mode 100644 index 57cbb876..00000000 --- a/requirements/vr-requirements.txt +++ /dev/null @@ -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 \ No newline at end of file diff --git a/test_metallic_gradient.png b/test_metallic_gradient.png deleted file mode 100644 index 11dbcc812cdc6a5d0115a5c14248d152a038fbd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1304 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w3=!$sk4H3kNj`<^b2Ar*7pUNU54FyJ}7 zq3n+%zZ-v~;)KK2#@_G$9C&-LC5&MQ(*aS2bqocp4cZJbj1RaN!bgQhLt->djAn&F cUMSpQ-!zHkpQ7K@8K5H1)78&qol`;+0Db^kO#lD@ diff --git a/test_metallic_stripes.png b/test_metallic_stripes.png deleted file mode 100644 index 956c8661f3d20aa537e1744bd66b374588bb187d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1308 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w3=!$sk4H3kNj$DS^ZAr*7pUfL*lz<`I@ z@bvHZb9xq7RW5kC@wa7n`px72FaO{D=Fc34M=S#B4Hk??I2HUD3Yj{D9Y%#lLt->d fjAn&FUnuAYUgrK^?WMK~RP1@W`njxgN@xNAmdRkl diff --git a/test_roughness_checkerboard.png b/test_roughness_checkerboard.png deleted file mode 100644 index 3ee2bd65e7ccda1e9fb3c3f4e3e4020febf2ee37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1364 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w3=!$sk4H3kM&X-^l&kcv5P?{4gMHRND8 zc>L@A&FeXszHBw#+1f0s`B``G)8B^wOa9&eWNYuoP{`CF>@bJn5sQF&g9YOePKALM z+Hv#V{P|C-Kj%NcJ@_04458ff=j-49V1D-g#-McG*>C^;=-i)OZ+)L_kVPywb>&sp z{i{AMZ%}`Ha5`_>_vh(9@BO*`(`-;>3Rup({rU5K>wgVDtp}&`Ht)5!KmGmL{d3zH h2Ty?l3p=Kc-|Vd?l)rZR<*|W^OHWrnmvv4FO#rtdjrafn diff --git a/test_roughness_circle.png b/test_roughness_circle.png deleted file mode 100644 index 5ab3d4d699503e94f433c5c8fe1b13b5aa170a87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19408 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

WavE?*4tH2w{K0;j`Z5%%(K11=>=P9mB2 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 xlh!MsD^`}djAn&F cUMSpQ-!zHkpQ7K@8K5H1)78&qol`;+0Db^kO#lD@ diff --git a/ui/vr_control_panel.py b/ui/vr_control_panel.py deleted file mode 100644 index fbb75c1b..00000000 --- a/ui/vr_control_panel.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/vr_test.py b/vr_test.py deleted file mode 100644 index b35a6ee3..00000000 --- a/vr_test.py +++ /dev/null @@ -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() \ No newline at end of file