From 72819341efa0db52d5bb4fbffb67b5abc46d348e Mon Sep 17 00:00:00 2001 From: Rowland <975945824@qq.com> Date: Tue, 29 Jul 2025 17:46:08 +0800 Subject: [PATCH] =?UTF-8?q?docs(README):=20=E6=B7=BB=E5=8A=A0=E9=A1=B9?= =?UTF-8?q?=E7=9B=AEREADME=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建详细的README文档,介绍项目功能、架构、使用方法等 - 新增.pyc文件和目录,用于缓存编译后的Python代码 - 添加.lingma规则文件,用于代码格式检查 - 删除ALVR串流处理器代码,准备替换为OpenXR输入处理器 - 新增OpenXR输入处理器代码,处理VR控制器输入 --- .lingma/rules/panda3d.md | 4 + .../__pycache__/Panda3DWorld.cpython-313.pyc | Bin 0 -> 5204 bytes .../QPanda3DWidget.cpython-313.pyc | Bin 0 -> 10435 bytes ...anda3D_Buttons_Translation.cpython-313.pyc | Bin 0 -> 2570 bytes .../QPanda3D_Keys_Translation.cpython-313.pyc | Bin 0 -> 29217 bytes ...da3D_Modifiers_Translation.cpython-313.pyc | Bin 0 -> 937 bytes QPanda3D/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 235 bytes README.md | 151 ++++ __pycache__/main.cpython-310.pyc | Bin 25562 -> 25852 bytes __pycache__/main.cpython-313.pyc | Bin 0 -> 36784 bytes core/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 787 bytes .../openxr_input_handler.cpython-310.pyc | Bin 0 -> 7733 bytes .../openxr_manager.cpython-310.pyc | Bin 0 -> 8816 bytes .../openxr_streamer.cpython-310.pyc | Bin 0 -> 7065 bytes core/__pycache__/world.cpython-313.pyc | Bin 0 -> 20782 bytes core/alvr_streamer.py | 683 ------------------ core/openxr_input_handler.py | 261 +++++++ core/openxr_manager.py | 342 +++++++++ core/openxr_streamer.py | 257 +++++++ core/vr_input_handler.py | 430 ----------- core/vr_manager.py | 666 ----------------- demo/OPENXR_IMPLEMENTATION_GUIDE.md | 346 +++++++++ demo/VR_ALVR_实现指南.md | 374 ---------- demo/VR测试说明.md | 253 ------- direct_vr_streaming_test.py | 197 ----- main.py | 305 ++++---- openxr_test.py | 480 ++++++++++++ requirements/openxr-requirements.txt | 23 + test_openxr_simple.py | 279 +++++++ ui/vr_control_panel.py | 412 ----------- vr/__init__.py | 11 - vr/devices/__init__.py | 8 - vr/vr_display.py | 379 ---------- vr/vr_input.py | 363 ---------- vr/vr_system.py | 380 ---------- vr_display_demo.py | 500 ------------- vr_display_optimizer.py | 316 -------- vr_quick_start.py | 263 ------- vr_streaming_test.py | 264 ------- vr_test.py | 468 ------------ 参考文档.md | 1 + 41 files changed, 2297 insertions(+), 6119 deletions(-) create mode 100644 .lingma/rules/panda3d.md create mode 100644 QPanda3D/__pycache__/Panda3DWorld.cpython-313.pyc create mode 100644 QPanda3D/__pycache__/QPanda3DWidget.cpython-313.pyc create mode 100644 QPanda3D/__pycache__/QPanda3D_Buttons_Translation.cpython-313.pyc create mode 100644 QPanda3D/__pycache__/QPanda3D_Keys_Translation.cpython-313.pyc create mode 100644 QPanda3D/__pycache__/QPanda3D_Modifiers_Translation.cpython-313.pyc create mode 100644 QPanda3D/__pycache__/__init__.cpython-313.pyc create mode 100644 __pycache__/main.cpython-313.pyc create mode 100644 core/__pycache__/__init__.cpython-313.pyc create mode 100644 core/__pycache__/openxr_input_handler.cpython-310.pyc create mode 100644 core/__pycache__/openxr_manager.cpython-310.pyc create mode 100644 core/__pycache__/openxr_streamer.cpython-310.pyc create mode 100644 core/__pycache__/world.cpython-313.pyc delete mode 100644 core/alvr_streamer.py create mode 100644 core/openxr_input_handler.py create mode 100644 core/openxr_manager.py create mode 100644 core/openxr_streamer.py delete mode 100644 core/vr_input_handler.py delete mode 100644 core/vr_manager.py create mode 100644 demo/OPENXR_IMPLEMENTATION_GUIDE.md delete mode 100644 demo/VR_ALVR_实现指南.md delete mode 100644 demo/VR测试说明.md delete mode 100644 direct_vr_streaming_test.py create mode 100755 openxr_test.py create mode 100644 requirements/openxr-requirements.txt create mode 100644 test_openxr_simple.py delete mode 100644 ui/vr_control_panel.py delete mode 100644 vr/__init__.py delete mode 100644 vr/devices/__init__.py delete mode 100644 vr/vr_display.py delete mode 100644 vr/vr_input.py delete mode 100644 vr/vr_system.py delete mode 100644 vr_display_demo.py delete mode 100644 vr_display_optimizer.py delete mode 100644 vr_quick_start.py delete mode 100644 vr_streaming_test.py delete mode 100644 vr_test.py create mode 100644 参考文档.md diff --git a/.lingma/rules/panda3d.md b/.lingma/rules/panda3d.md new file mode 100644 index 00000000..baebd605 --- /dev/null +++ b/.lingma/rules/panda3d.md @@ -0,0 +1,4 @@ +--- +trigger: always_on +--- +在进行panda3d项目的开发时,必须参考api文档,@https://docs.panda3d.org/1.10/python/reference/index \ No newline at end of file diff --git a/QPanda3D/__pycache__/Panda3DWorld.cpython-313.pyc b/QPanda3D/__pycache__/Panda3DWorld.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ae61dd84eeeffe8f4f1d7acda05afcc115e615d GIT binary patch literal 5204 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 literal 0 HcmV?d00001 diff --git a/QPanda3D/__pycache__/QPanda3DWidget.cpython-313.pyc b/QPanda3D/__pycache__/QPanda3DWidget.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba08dc7ee009a42be3377904d1a22ad961576c4b GIT binary patch literal 10435 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 literal 0 HcmV?d00001 diff --git a/QPanda3D/__pycache__/QPanda3D_Buttons_Translation.cpython-313.pyc b/QPanda3D/__pycache__/QPanda3D_Buttons_Translation.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51ede85df57f7ba3ed67cd2624ea9a254ccbd0d6 GIT binary patch literal 2570 zcmY+F-%s0C6vwZVkOU_I)^%MeEpt0Y3+lcMz*sy z(;k+mvA4eLUzoK2We=eo@Gz>{ zNJ9#f2{PDe=_t8|OjXN7$#pW=Y1t^bflO5!jgp&Wu;a-sWU73E+hnlg$uu%mo+xCn z%9AP??0B+- zOqC}qWU%AODl%1`tdYTvC+o;mdGdq|c08#eQ{~AP8SHqngG`ktUy{L&Cto8|<;hbr z*zx25nJP~X$zTV`HoRR&XsBxGa+o>ur^z|_hu`FVyVYb)u*=ig+ddIbN z&$K&@TG#XJjw^3;y_W5Ss}9UFOLvuh<9nlXrnk@KEyF!=%yU?+tjbDwZQ31AH#@GP zD^2qR0^R8;UQ72Bj~{KgO8r=A+g;aC;3UsgJX<-B253fy#IvIOAB;aI`O^G>KRFz9 z&*-`FX?^Brx}CF*{k-F+qXQOrRe7z)xjA7=8|Ew`=<2 z2;Z2^Q^Rxpq}y|0r*2u{&P3hwvj_Iz=J@i4W%2vu%TJB(JnnNx=4lIIbl+@ppAXxn zW#I0besae9?6X0$;ODnrg!c-muwy&Vbq5c?WvZ}D_2pyT#iQZ}RD^X^fffZ?5@=bV z6@ec6h4_eN5f?;MMXZQe6>(9-B@vfJToF;6N)@M4#i>+rDpj0Ht@=~L2W{{>H1O#S zpXlG&6+Dvua;asvjZ(|7EW5P5Tf!~F2H&9gb6Py_`PsF0^x~~M@Mlj7KkknS#)70I zNtda#l=^>yNmFmx%Fi~`HR`F{T5S&#$H1CRH|UuJ^>@EGnlgK^++jAZm}Uj^?0 zU&HmI!TZ41L;jmhykZkCCxZ{b+@KvF0^g(^lfbut2NO>L-=_Qy@HFv9zzXqQ;Cn;9 z{1|v<$iXMTv&8p-9}v$0&xbsm_#v1_aq}sdV%(I#l;dUrj2br;Fx9wO1hW)3%V1XG x<}sMnxcLmsTHJgNW<74c0P{qOs|^WUO>mo$+lsr(?a1v2ZWr7uHs!#-=64_dOdkLM literal 0 HcmV?d00001 diff --git a/QPanda3D/__pycache__/QPanda3D_Keys_Translation.cpython-313.pyc b/QPanda3D/__pycache__/QPanda3D_Keys_Translation.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eddd21a474582cd81ff7e18484081fd5df7a78be GIT binary patch literal 29217 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 DSrdUz literal 0 HcmV?d00001 diff --git a/QPanda3D/__pycache__/QPanda3D_Modifiers_Translation.cpython-313.pyc b/QPanda3D/__pycache__/QPanda3D_Modifiers_Translation.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..271a60ef0329fae8740879bd46a8b4435dbe02d8 GIT binary patch literal 937 zcmZ`%O>fgM7`BtH?b5aB3M!1L$kz_kA|Zq}A*5+TJyfk$GKtHi$TZ#(HI0MgFohF6 zZQOd{M{wb{P;p=~J0Nl5R#bifPHD0U$}1&#-sgG0{5rE@Q3KN6eID9J3IN|@C$Z83 z%)UwR9vFZD0~$&jK332lq@+k)Ahn%g86(q#Y~?8XJgJaWlwU)YeEAx&fJPi&S;A)} ze{%_+lYDgvpO^gY|L}K~@egvWbfVT*-+a_M)CygKc2MsCdlr7UZyqx21uo_e^-QTc z#|~kEwi|MrG8xx(1}+XzhrA}<01tg_p9Fp8_;Ebi(U7z~>T&FN0m8^~`mumngt(14 z;)~)WK)pjWAkj;TnT6tFJV2Zxe?EmZuk)AO(0;^+sY3y`y5ep+a607m(Zxz&JI7q) z`f?aXU7=!EdZ9P)=&4uNMb0!W+BZ$Hb`bUWqehSKQAR`|@mirH^u&4LSSN%BLJguI z&cv=OGnF0}T9+pMMbU^`rmi0ELAFkb!mfLzu5}2{IUe1DM6@5!+3h4oTA{u1lNGTFoRW1y{FvePInh68R^?`S0+QZOTeeKEW&b znyL`OpP4Mo{3-*uc?mWyLG3HhE(-TPY4_jerda^hv*M_DZe0N73n))Od3V-N=hn4pZ$0zk%8hG2#w27QKL#z4j*#&jl4=35+r0f~7j ziN-EKHXn=~o|%%KT5^jADixkzl#_Bx5+)n(omyEOA5xT$|$^!;H|6kI&4@EQycTE2zB1VUwGm mQks)$SHum}!wAI1{6OLZGb1D84OadJmRnq&jqF97KrsMCwLuF2 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index e69de29b..d8395633 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,151 @@ +# EG - 3D编辑器与游戏引擎 + +EG是一个功能强大的3D编辑器和游戏引擎,基于Panda3D和PyQt5构建,提供直观的可视化编辑功能和VR支持。该引擎适用于游戏开发、3D可视化和交互式应用程序创建。 + +## 功能特性 + +### 核心功能 +- **3D场景编辑与管理**:完整的场景编辑工具集 +- **模型导入与处理**:支持多种3D模型格式导入 +- **材质系统**:高级材质编辑和管理 +- **光照系统**:环境光、定向光、聚光灯和点光源 +- **相机控制系统**:灵活的相机操作和视角控制 +- **场景保存与加载**:BAM格式场景保存和恢复 + +### 编辑器功能 +- **对象选择与变换**:精确的对象选择和变换工具 +- **坐标轴拖拽控制**:直观的3D变换控制 +- **属性面板编辑**:实时编辑对象属性 +- **场景树管理**:层次化场景结构管理 +- **材质编辑器**:可视化材质编辑 +- **日夜循环系统**:动态光照和环境变化 + +### GUI系统 +- **2D GUI元素**:按钮、标签、输入框、滑块等 +- **3D空间GUI**:3D文本和虚拟屏幕 +- **GUI编辑模式**:可视化GUI布局编辑 +- **事件处理系统**:GUI交互事件响应 + +### VR支持 +- **VR系统集成**:完整的VR环境支持 +- **VR控制器输入处理**:精确的控制器输入识别 +- **ALVR串流支持**:无线VR体验 +- **触觉反馈**:增强沉浸感的触觉反馈 +- **VR手势识别**:自然的手势交互 + +### 脚本系统 +- **Python脚本热重载**:实时脚本更新无需重启 +- **脚本模板创建**:快速创建新脚本 +- **对象脚本管理**:为游戏对象添加行为脚本 +- **脚本调试支持**:内置调试功能 + +### 项目管理 +- **项目创建与保存**:完整的项目管理功能 +- **资源管理**:统一的资源导入和管理 +- **项目打包**:一键打包发布项目 + +## 系统架构 + +### 核心模块 +- `core/world.py` - 核心世界管理,集成所有子系统 +- `core/event_handler.py` - 输入事件处理系统 +- `core/selection.py` - 对象选择和变换系统 +- `core/tool_manager.py` - 编辑工具管理 +- `core/script_system.py` - Python脚本系统 +- `core/vr_manager.py` - VR系统管理 +- `core/vr_input_handler.py` - VR输入处理 +- `core/alvr_streamer.py` - ALVR串流支持 + +### 子系统 +- `scene/scene_manager.py` - 场景管理系统 +- `gui/gui_manager.py` - GUI系统管理 +- `ui/` - 用户界面组件 + - `ui/main_window.py` - 主窗口设置 + - `ui/property_panel.py` - 属性面板 + - `ui/widgets.py` - 自定义UI组件 + - `ui/interface_manager.py` - 界面管理 +- `project/project_manager.py` - 项目管理系统 + +## 快速开始 + +### 系统要求 +- Python 3.7+ +- Panda3D +- PyQt5 +- 支持OpenGL的显卡 + +### 依赖安装 +```bash +pip install -r requirements.txt +``` + +### 运行编辑器 +```bash +python main.py +``` + +### 基本操作 +- **鼠标左键**:选择对象 +- **鼠标右键**:旋转视角 +- **鼠标中键**:平移视角 +- **滚轮**:缩放视角 +- **WASD**:在场景中移动 +- **数字键1-0**:切换不同时间点 +- **F键**:聚焦选中对象 +- **Delete键**:删除选中对象 + +## 编辑器使用指南 + +### 导入模型 +1. 点击"文件" > "导入模型" +2. 选择支持的3D模型文件 +3. 调整模型位置和属性 + +### 场景编辑 +1. 使用选择工具选中对象 +2. 通过属性面板修改对象属性 +3. 使用变换工具(移动、旋转、缩放)调整对象 + +### GUI创建 +1. 进入GUI编辑模式 +2. 选择GUI元素类型 +3. 点击场景放置GUI元素 +4. 通过属性面板调整GUI属性 + +### VR模式 +1. 连接VR设备 +2. 点击"VR" > "启用VR模式" +3. 使用VR控制器进行交互 + +### 脚本编辑 +1. 点击"脚本" > "创建新脚本" +2. 编写Python脚本代码 +3. 将脚本附加到场景对象 + +## 开发指南 + +### 架构概述 +EG采用模块化设计,核心功能通过`MyWorld`类集成各个子系统。所有子系统都通过代理方法暴露功能,使API保持一致性。 + +### 添加新功能 +1. 在相应目录下创建新模块 +2. 在`MyWorld`类中添加代理方法 +3. 通过子系统管理器集成功能 + +### 扩展VR功能 +1. 修改`vr_manager.py`添加新功能 +2. 更新`vr_input_handler.py`处理新输入 +3. 在`alvr_streamer.py`中添加串流支持 + +### 创建自定义工具 +1. 在`tool_manager.py`中添加新工具类型 +2. 实现工具的交互逻辑 +3. 在UI中添加工具选择按钮 + +## 贡献 + +欢迎提交Pull Request或Issue报告问题。贡献前请先查看贡献指南。 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/__pycache__/main.cpython-310.pyc b/__pycache__/main.cpython-310.pyc index 4d7ef861f7b4caf8c59e5c282197305ca41b5264..feabe58d61beca0659268ad247e25f8144c81efc 100644 GIT binary patch delta 3137 zcmZ`*4^)%a73V#`QUntI1d_tQf2S7ISP-#Q@E?MpAP^~(P)JkWCuRo|&X;7HoVJLg z)>a*KS36ISJ9aHrR;Q^3ixaeeXVqEvr=7N83hfA>_7t^StF3GI-uLo-NpKA(zx(Ch z`~BX1_uYGWo)6HSGbqLu9UU1$KbbqXu0Jmvj=|w`qRW~E>!XHJv(?-vi0Q}^#}&oa zW~ZabY$Z*SHb&CA=Y>1MTVE#!+Cz+M$REf-D$B?r;-a#gyg?3ASwX%fM~J%}84aX| z9HpAIbf6}LQz*PKVe;-8gJuPXll= zs~cT{#(BNyGNfhqqCvQr?LtHF?0i6n8iSsEIYck6Gx`?q+P60yiw)FfjI~%T4vV?T(ju6o&BFD< z#CdAJmcR5aqMJKPUue;mZmhP6O+-Jz1u|DogU-D02cB_X`OU%qx0K4h-svE=pIiM4 z&5eGKumsK@Mz@Ia(UV$g7)1_U_{^jqG-LJ74p5x<+?)`?qEHt#W5&YMjbq z=dJ}-^Ak-1Ig7w-h!2-^kXw|lVw7XQ>h*T;96@uDMy+ZT9GpWmS*+`9aHCM8kETja zRZLaqm2fq3xIuQP$ejwNY2lHbF7NZ*e7TNd4MZ1b=+$Wc89H54v3OmR7jHKh!hXFQM?@wAZT6X#)Am5SHFz^h2X;KeANuL z{X-{5PaNXRGfL*Ge~(2AReSCcc+Q}{WCk8Z&|XrIm>TSF7(U)LeEiptS&{(bB|7v2 z+*>*eUqg^nS{!$G1wu#Z++5v+@-eJ+h=O^8#oBmX*}_zL3ln@HcI|X%ULC>8EwXb&JQ)EiNTM%17df!|#@49_!)d>xFiw#fWmo7g%jaYP#}ie)c#<)6U9;T?<>lJ!M7vy*oVUVB+u5-( z1c!v!={F<}x1`FvvMcN^|0%u`0$~*kQ5Y<#D3EgLZ(VqyV$MTJLA~eCKm=vw!yrxJ z^|dxX2;ccgfd_OdXblT;V&z`Uw^^-1gF_%u3Y|`-yUT}lk}8>QCXmi$SU6juQ*x^Q zQ961$&$Y5z=d*&h%}Dp}y!{>H{ac5RZMw4a5H-E&Rn6byfTynp-fi7kOa4EOEucCZmRftl}5VTLCw?~VNrWfyNV2xX-ghF~Z& zDNfBhos*W@h@gSK%IOKpGzu`omzBnHmdVlA+USz}m5G^@s!7h|-Ht4iK}mq^kLc9l zETG8@zGd-!4X!+0F0t6z-1bMNr)O-Bq7D%_A~XWgZ)g;5QOYOKpN5(U+NxD5X=nw^ z_zhJ_%jNrkZ^juX+xY!}&3ohI2AkC(+M4K9$!=8pEL6v<)~A8vRa26`VWHk)@I8Yl z7Jf8?Nm6EG*}oDVJa72LpsP+!QYLh;70Pq(hn;x^xCFyM9^i5e&5PfgDgMBygRdJN z6%V|~<}e6NHZ!TjAS`(c*I~GSX%{wQIJdM13m7&pb6^dETg%e01%qyR2mAH<@)T^v zaC-SuI2pmp{4^|LSf9TIrNDT8D*hP;)ru#XX~&8b+=Ai2iv741LuG*j>kynRn1X+a z;fsRZ_*o2F3e%vdx)moN_nGQhsNw<(_9BB%8F&~BFu26vGJ`<|LkvD+FwDTqV1&UK zgDVWKGPnk1HCp{m_V^V8se->|PyXCX1ua$0cg*l#XsekCM`}`$3eMGN)rsstk{DOC3MDLvE!-`6gRXV0Ci7f=_RuWv>kSYp~i@k3Jz#j7S)L41Qv6!g9tl5d~s zl+CCMv;<#4+3nPr;J3rak1s{DKx3|=m(eEkHgq2gOni_*7K3aC^BK&5*oL3bwdXb@ aQC!*Zv_^;s=YNs0lTZ|Gm$=ZdZ~g~po0%s7 delta 3095 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 RMuMb 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 MEv~|CQwQ|_1OH36U;qFB literal 0 HcmV?d00001 diff --git a/core/__pycache__/openxr_streamer.cpython-310.pyc b/core/__pycache__/openxr_streamer.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a61ad430cf39536c0b0ad5815866a62b966e2d0 GIT binary patch literal 7065 zcmbVRTW}lI8Qz;#D_NEu$GHbE2??kKhj1y=Kxt^fDV@QzBvU|r*l~2$u^d@)&+f*t z#xThcNC0d=YH3ZB zdRiBykv2pbNk>F!rcF^s(@~Uq&KioPV=Ap-6W2HI-x3?6&7LUUWJ)X>;*mD z%Cs#?D(Y{?C*w~JJNajxy8g*W<=^fpo!UEfGdo&QwlF2c^0emhLH3Jmm#t zLY-7pWlSAYY;8;fg*bp3jOr1lgPP0$jj{-+#Z1tcpmC<4-Xds%MNw~M7HE>iK-*Xx zw4JqpE)Zin1YIcRcCrN87kP^(7EjVDOT_z9L6>>US?dcbgiXO|lfA{*hI=09z8ilV zvVUHyDA~V&q$4A^ZkGKk>OI*_EoeSvD$2|cajuy+K+?v*J>R~MtEl>SfP8WM^{%Eo zD(yc}I`c~D;2Wh64wm;HNv#MXu0QPXz_RUZKI_?b5E&`(91D^KNc1jl5B23CO*|cQ z9lq1yc6K=35>|%`+)F22aWUEac3Zr+WeWK`gfd&m+d~EBq~l`mqBDqvoem3P)p~OrGz1m}kEeY9Tk7SuQfZ^hc_JEV!KHvrM@U?%~4Wiq!9`Ey% z&x{6-A8Vf|y9*Jldc zSs$`l9>U?>!=pTgxr_0*_khGzlm7H7m9r8Iq66kCcOU z`U5=fQIO(YW6FdwskogUP?JOw?^^h-(fiX1)3;Eoj@DZ+%9zt?2<1{HB2=O`684%~ zpv;~*`nak*hgFL0=!<&MF?{1~WGqx33abZZLLiE-@>LP zE|!mvmkvwet!`?M ze{*$!#NX2X59`MGw_r$vZdFtY`cyh_a;tw62Gwq~y8D@d5i6~Y-oC$;rww*&rXCj?6ymXPA^2C>I>Wt?GB z&_KuM&uN@a9;gn7WmP0uMJ;hcglu0fTXY`AyL%gmrdgV$(x0hXdPsFkGu5Qlp(a&p zxAvdnvN{2rU1yF6k~jj!qzHDRQq&=0>K2F({Xry9Pia1FtNiZn^2zt=?xXz63#B8c z<&F1iE>JPxlDXYd+@G zZ>BH42hlsH3kl<7C3uPmsZl+@=%zDxZnT5YmE(HV>h(78t+}wy%QxPQRZ|gvDT;Yj zZJr~C=fLPUZWr zUBCJ|oJhO|Eigh1#@K~w3adXuy5x6&%!-Mey0I%zcYUdD3UpYp-jpdUQ_!~E@$7=M z@Y#HSfzw6v1w;sdIIWbDz`!?9!(3?Ka72_5JT93KQ%|aK{H@iBtDEq!9-8KGk@ii_ zyWryYQ4t;)bZ8Jh*`RRO$H_Y*ju{oxBKm~OF8bh&4Mt@jnU9nq7$KEIY6E=gsk5(a zg`;c&S8?{hWPQ$1ArC0#OYqeoCee}H0CTJK6c$#nmRy;EzC6o0oRjwnpNY^JU_6W7 zm433@{4OGO9J?KDd+ZC;wesRnM#%|1P|N`T3N` zih@t~7Z;#ZIlOgpaNw-y69@>=7FnUx@Xj1vU*%mCytDpK-bophQK97B6t;rjKffpV zSKB|!znJ7-E5?zeLE-J4S>_aM;ibBQv7GF(P}cv2x8KEJiWXeDG~z8xxY`M15Bd95 zBLp@G_#E~|uuj#7{3Bk9j|&Dr=n*r?ENc8=stsxrI@2aq0Al4$#~VPAKQgBAN5_Bx zgMh^c5VP5a7a?VSP9H-+HfSydOzw#A&8W-JjDj+Q`fL;K0-*AnDU`q(b#Re}@I7Pj zJ>z5WTe!x)w%x$UIsIM`;S`Ao zh2Y=A92tlau2kpAWoB=;MdEODpwI2|JkB4%;6Mj5ak7f?a+gz$+}66wO+|#0lTOgb zD|<<|icM5rgG41%p}>XTNvqT$(k8S`;0gV@4k#M@H!Z1Iz$NnR5^CZ`!Z6LG*(DUP ziFnjYz;<#e=u%)&~eG~m4m1ch1&wM82+^LP~YSQj@sedu!O@Eh_1rw&{y z@7}}j#agMTycuzn`_aHxf&_8yxP_cgQ3_v3t%O-H*5Ve$SfLm+w#IjSk~rbP5+?pbwFK#_o3ECQ`CeZqj!iyzM%J1-TWXNIf{vf@#)bp_v>7dXYF-c($=RusNVhu+G% zHiUSm6BvVqY@Q1*p6(Ti#9RTH8S(_A@n{CLmOJ2ktT2)mVeMJirt4yu9 zq7MFR$IZJN{tPB#ALOFc?xTGE-O};1@-na+0nhX8ri#B`V-YS?yvbgwqf$#KHJzut ztR2K{N-6s}swZp#JYi4Nwpk%#+ah<$*U{3eiICU9*AStIl~WMLDa7F93-e7xHWLw| z@FS`{LF6Yyo+k1vk)MGCtzjxRgY+#vGz*B76y&Rj5PA_>x_5wBih(P)?yy!`F)J?q z5~8*Q^@x=aEsNR>F)NkiHs%ConDI8j06*jL?@*&L=)ww$Kq~I~+lF}|H75~gqLT#HLz2-C VlOPI-HWVu=6MvE>U?@BF{{l6C5i9@z literal 0 HcmV?d00001 diff --git a/core/__pycache__/world.cpython-313.pyc b/core/__pycache__/world.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b53c1cc6a12321c0d53f7af3583db8ac961a1af7 GIT binary patch literal 20782 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 1.0: - self.current_fps = self.frame_count - self.frame_count = 0 - self.last_fps_time = current_time - - def start_streaming(self): - """开始串流""" - # 如果是直连模式,直接返回成功 - if self._is_steamvr_active(): - self.streaming = True - print("✓ 启用直连模式,SteamVR将直接处理渲染输出") - return True - - if not self.connected: - print("未连接到ALVR服务器") - return False - - start_message = {"type": "start_streaming"} - self._send_message(start_message) - return True - - def stop_streaming(self): - """停止串流""" - if not self.connected: - return - - stop_message = {"type": "stop_streaming"} - self._send_message(stop_message) - self.streaming = False - - def send_haptic_feedback(self, controller_id, duration, intensity): - """发送触觉反馈""" - if not self.connected: - return - - haptic_message = { - "type": "haptic_feedback", - "controller_id": controller_id, - "duration": duration, - "intensity": intensity - } - - self._send_message(haptic_message) - - def get_streaming_status(self): - """获取串流状态""" - # 如果是直连模式,返回特殊的直连状态 - if self._is_steamvr_active(): - return { - "connected": True, - "streaming": self.streaming, - "fps": self.current_fps, - "latency": self.latency, - "resolution": f"{self.stream_width}x{self.stream_height}", - "bitrate": self.bitrate, - "mode": "direct" # 直连模式 - } - - return { - "connected": self.connected, - "streaming": self.streaming, - "fps": self.current_fps, - "latency": self.latency, - "resolution": f"{self.stream_width}x{self.stream_height}", - "bitrate": self.bitrate, - "mode": "alvr" # ALVR模式 - } - - def set_stream_quality(self, width, height, fps, bitrate): - """设置串流质量""" - self.stream_width = width - self.stream_height = height - self.stream_fps = fps - self.bitrate = bitrate - - # 如果正在串流,重新配置 - if self.streaming: - self._configure_streaming() - - def shutdown(self): - """关闭串流""" - print("关闭ALVR串流...") - - self.running = False - self.streaming = False - - # 发送断开消息 - if self.connected: - disconnect_message = {"type": "disconnect"} - self._send_message(disconnect_message) - - # 关闭连接 - if self.server_socket: - self.server_socket.close() - - if self.streaming_socket: - self.streaming_socket.close() - - # 等待线程结束 - if self.connection_thread: - self.connection_thread.join(timeout=2) - - if self.streaming_thread: - self.streaming_thread.join(timeout=2) - - self.connected = False - print("✓ ALVR串流已关闭") - - def is_connected(self): - """检查是否连接""" - return self.connected - - def is_streaming(self): - """检查是否在串流""" - return self.streaming \ No newline at end of file diff --git a/core/openxr_input_handler.py b/core/openxr_input_handler.py new file mode 100644 index 00000000..93dad247 --- /dev/null +++ b/core/openxr_input_handler.py @@ -0,0 +1,261 @@ +""" +OpenXR输入处理器 + +处理OpenXR控制器输入和手势识别 +支持多种OpenXR控制器和输入方式 +""" + +from direct.showbase.DirectObject import DirectObject +from panda3d.core import Vec3, Point3, CollisionRay, CollisionNode, CollisionHandlerQueue, BitMask32 +from direct.task import Task +import time + + +class OpenXRInputHandler(DirectObject): + """OpenXR输入处理器""" + + def __init__(self, world, openxr_manager): + super().__init__() + self.world = world + self.openxr_manager = openxr_manager + + # 控制器状态 + self.controllers = {} + self.controller_nodes = {} + self.controller_rays = {} + + # 手势识别 + self.gesture_enabled = True + self.gesture_history = [] + self.gesture_threshold = 0.1 + + # 交互系统 + self.interaction_enabled = True + self.selected_object = None + self.grab_offset = Vec3(0, 0, 0) + + # 输入映射 + self.input_mappings = { + 'trigger': self._handle_trigger, + 'grip': self._handle_grip, + 'thumbstick': self._handle_thumbstick, + 'touchpad': self._handle_touchpad, + 'menu': self._handle_menu, + 'system': self._handle_system + } + + print("✓ OpenXR输入处理器初始化完成") + + def start_input_handling(self): + """启动输入处理""" + if not self.openxr_manager.is_openxr_enabled(): + print("OpenXR未启用,无法启动输入处理") + return False + + # 启动输入更新任务 + self.world.taskMgr.add(self._update_input, "openxr_input_update") + + # 设置控制器可视化 + self._setup_controller_visualization() + + print("✓ OpenXR输入处理已启动") + return True + + def stop_input_handling(self): + """停止输入处理""" + self.world.taskMgr.remove("openxr_input_update") + self._cleanup_controller_visualization() + print("✓ OpenXR输入处理已停止") + + def _update_input(self, task): + """更新输入处理""" + if not self.openxr_manager.is_openxr_enabled(): + return Task.cont + + try: + # 更新所有控制器 + self._update_controllers() + + # 处理手势识别 + if self.gesture_enabled: + self._process_gestures() + + # 处理交互 + if self.interaction_enabled: + self._process_interactions() + + except Exception as e: + print(f"OpenXR输入更新错误: {str(e)}") + + return Task.cont + + def _update_controllers(self): + """更新控制器状态""" + if self.openxr_manager.simulation_mode: + # 在模拟模式下使用模拟数据 + sim_data = self.openxr_manager.get_simulation_data('controller_poses') + for controller_id, pose_data in sim_data.items(): + if pose_data['connected']: + self._update_controller_pose(controller_id, pose_data) + else: + # TODO: 实际OpenXR控制器更新 + pass + + def _update_controller_pose(self, controller_id, pose_data): + """更新控制器姿态""" + # 更新控制器位置和旋转 + if controller_id not in self.controller_nodes: + self._create_controller_node(controller_id) + + controller_node = self.controller_nodes[controller_id] + position = pose_data['position'] + rotation = pose_data['rotation'] + + controller_node.setPos(*position) + controller_node.setQuat(Quat(*rotation)) + + def _create_controller_node(self, controller_id): + """创建控制器节点""" + # 创建一个简单的控制器可视化模型 + from panda3d.core import CardMaker + cm = CardMaker(f'controller_{controller_id}') + cm.setFrame(-0.05, 0.05, -0.05, 0.05) + controller_node = self.world.render.attachNewNode(cm.generate()) + controller_node.setColor(0, 1, 0, 1) # 绿色 + + self.controller_nodes[controller_id] = controller_node + + # 创建控制器射线 + self._create_controller_ray(controller_id) + + def _create_controller_ray(self, controller_id): + """创建控制器射线""" + # 创建碰撞射线用于交互检测 + ray_node = CollisionNode(f'controller_ray_{controller_id}') + ray = CollisionRay() + ray.setOrigin(Point3(0, 0, 0)) + ray.setDirection(Vec3(0, 1, 0)) # 默认向前 + ray_node.addSolid(ray) + ray_node.setFromCollideMask(BitMask32.bit(0)) + + ray_np = self.controller_nodes[controller_id].attachNewNode(ray_node) + queue = CollisionHandlerQueue() + + self.controller_rays[controller_id] = { + 'node': ray_np, + 'queue': queue + } + + def _setup_controller_visualization(self): + """设置控制器可视化""" + if self.openxr_manager.simulation_mode: + # 在模拟模式下创建控制器可视化 + sim_data = self.openxr_manager.get_simulation_data('controller_poses') + for controller_id in sim_data.keys(): + self._create_controller_node(controller_id) + + def _cleanup_controller_visualization(self): + """清理控制器可视化""" + # 移除所有控制器节点 + for controller_node in self.controller_nodes.values(): + controller_node.removeNode() + + self.controller_nodes.clear() + self.controller_rays.clear() + + def _process_gestures(self): + """处理手势识别""" + # TODO: 实现手势识别逻辑 + pass + + def _process_interactions(self): + """处理交互""" + # TODO: 实现交互逻辑 + pass + + def _handle_trigger(self, controller_id, value): + """处理触发器输入""" + # TODO: 实现触发器处理逻辑 + pass + + def _handle_grip(self, controller_id, value): + """处理抓握输入""" + # TODO: 实现抓握处理逻辑 + pass + + def _handle_thumbstick(self, controller_id, x, y): + """处理摇杆输入""" + # TODO: 实现摇杆处理逻辑 + pass + + def _handle_touchpad(self, controller_id, x, y, pressed): + """处理触摸板输入""" + # TODO: 实现触摸板处理逻辑 + pass + + def _handle_menu(self, controller_id, pressed): + """处理菜单按钮""" + # TODO: 实现菜单按钮处理逻辑 + pass + + def _handle_system(self, controller_id, pressed): + """处理系统按钮""" + # TODO: 实现系统按钮处理逻辑 + pass + + def get_all_controllers(self): + """获取所有控制器""" + if self.openxr_manager.simulation_mode: + sim_data = self.openxr_manager.get_simulation_data('controller_poses') + return list(sim_data.keys()) + else: + # TODO: 返回实际连接的控制器 + return [] + + def get_controller_state(self, controller_id): + """获取控制器状态""" + if self.openxr_manager.simulation_mode: + sim_data = self.openxr_manager.get_simulation_data('controller_poses') + if controller_id in sim_data: + # 返回模拟控制器状态 + return { + 'trigger': 0.0, + 'grip': 0.0, + 'thumbstick': {'x': 0.0, 'y': 0.0}, + 'touchpad': {'x': 0.0, 'y': 0.0, 'pressed': False}, + 'menu': False, + 'system': False, + 'connected': sim_data[controller_id]['connected'] + } + + # 默认返回空状态 + return { + 'trigger': 0.0, + 'grip': 0.0, + 'thumbstick': {'x': 0.0, 'y': 0.0}, + 'touchpad': {'x': 0.0, 'y': 0.0, 'pressed': False}, + 'menu': False, + 'system': False, + 'connected': False + } + + def set_gesture_enabled(self, enabled): + """设置手势识别启用状态""" + self.gesture_enabled = enabled + print(f"{'✓' if enabled else '✗'} OpenXR手势识别已{'启用' if enabled else '禁用'}") + + def set_interaction_enabled(self, enabled): + """设置交互启用状态""" + self.interaction_enabled = enabled + print(f"{'✓' if enabled else '✗'} OpenXR交互已{'启用' if enabled else '禁用'}") + + def show_controller_rays(self, show=True): + """显示/隐藏控制器射线""" + for ray_data in self.controller_rays.values(): + ray_node = ray_data['node'] + if show: + ray_node.show() + else: + ray_node.hide() + + print(f"{'✓' if show else '✗'} 控制器射线已{'显示' if show else '隐藏'}") \ No newline at end of file diff --git a/core/openxr_manager.py b/core/openxr_manager.py new file mode 100644 index 00000000..df754978 --- /dev/null +++ b/core/openxr_manager.py @@ -0,0 +1,342 @@ +""" +OpenXR管理器 + +负责OpenXR系统初始化、追踪和渲染 +支持有线VR串流功能 +""" + +import warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) + +from panda3d.core import * +from panda3d.core import Mat4, PerspectiveLens, CardMaker +# 修复taskMgr导入错误,应该从TaskManagerGlobal导入而不是ShowBaseGlobal +from direct.task.TaskManagerGlobal import taskMgr +from direct.showbase.DirectObject import DirectObject +from direct.task import Task +import sys + + +class OpenXRManager(DirectObject): + """OpenXR管理器 - 处理OpenXR系统初始化、追踪和渲染""" + + def __init__(self, world): + super().__init__() + self.world = world + self.openxr_enabled = False + self.xr_instance = None + self.xr_system = None + self.xr_session = None + + # 渲染配置 + self.render_width = 1920 + self.render_height = 1080 + + # 有线连接设置 + self.wired_connection = True + self.connection_ip = "127.0.0.1" + self.connection_port = 9943 + + # 模拟模式设置(用于开发测试) + self.simulation_mode = False + self.simulation_data = { + 'head_pose': {'position': [0, 0, 1.6], 'rotation': [0, 0, 0, 1]}, + 'controller_poses': { + 0: {'position': [-0.3, 0, 1.2], 'rotation': [0, 0, 0, 1], 'connected': True}, + 1: {'position': [0.3, 0, 1.2], 'rotation': [0, 0, 0, 1], 'connected': True} + }, + 'render_size': (1920, 1080) + } + + # 立体渲染缓冲区 + self.left_eye_buffer = None + self.right_eye_buffer = None + self.left_eye_camera = None + self.right_eye_camera = None + + # 眼部纹理(用于串流) + self.left_eye_texture = None + self.right_eye_texture = None + self.eye_textures = {} + + # 控制器相关 + self.controller_nodes = {} + self.controller_poses = {} + + print("✓ OpenXR管理器初始化完成") + + def initialize_openxr(self, force_simulation=False): + """初始化OpenXR系统""" + try: + # 检查是否强制使用模拟模式 + if force_simulation: + print("🔧 强制启用OpenXR模拟模式") + return self._init_simulation_mode() + + # 检查OpenXR支持 + if not self._check_openxr_support(): + print("⚠ OpenXR支持不可用,切换到模拟模式") + return self._init_simulation_mode() + + # 尝试初始化OpenXR + if not self._init_openxr_native(): + print("⚠ OpenXR初始化失败,切换到模拟模式") + print("提示: 请确保OpenXR运行时已安装且VR头盔已连接") + return self._init_simulation_mode() + + # 真实OpenXR模式初始化成功 + print("✓ 真实OpenXR模式初始化成功") + return self._init_real_openxr_mode() + + except Exception as e: + print(f"OpenXR初始化错误: {str(e)}") + print("⚠ 切换到模拟模式") + return self._init_simulation_mode() + + def _check_openxr_support(self): + """检查OpenXR支持""" + try: + # TODO: 实际检查OpenXR运行时支持 + # 这里暂时返回False以使用模拟模式进行开发 + return False + except: + return False + + def _init_openxr_native(self): + """初始化原生OpenXR""" + # TODO: 实现OpenXR初始化逻辑 + print("ℹ OpenXR原生初始化占位符") + return False + + def _init_real_openxr_mode(self): + """初始化真实OpenXR模式""" + self.openxr_enabled = True + self.simulation_mode = False + + # TODO: 实现实际的OpenXR设置 + # 1. 创建OpenXR实例 + # 2. 获取系统信息 + # 3. 创建会话 + # 4. 设置渲染目标 + # 5. 初始化控制器 + + return True + + def _init_simulation_mode(self): + """初始化模拟模式""" + self.openxr_enabled = True + self.simulation_mode = True + + # 设置模拟渲染尺寸 + self.render_width = self.simulation_data['render_size'][0] + self.render_height = self.simulation_data['render_size'][1] + + # 初始化模拟立体渲染 + self._setup_simulation_rendering() + + print("ℹ OpenXR模拟模式已启用") + print(f" 渲染尺寸: {self.render_width}x{self.render_height}") + print(" 头部追踪: 模拟摆动") + print(" 控制器: 模拟位置") + + return True + + def _setup_simulation_rendering(self): + """设置模拟渲染""" + print("🔧 OpenXR模拟模式: 开始设置渲染") + + # 创建左右眼渲染缓冲区 + self.left_eye_buffer = self._create_eye_buffer("left_eye", self.render_width//2, self.render_height) + self.right_eye_buffer = self._create_eye_buffer("right_eye", self.render_width//2, self.render_height) + + print(f" 创建眼部缓冲区: 左眼={self.left_eye_buffer is not None}, 右眼={self.right_eye_buffer is not None}") + + # 创建左右眼相机 + self.left_eye_camera = self._create_eye_camera("left_eye_camera", (-0.03, 0, 0)) # 瞳距的一半 + self.right_eye_camera = self._create_eye_camera("right_eye_camera", (0.03, 0, 0)) # 瞳距的一半) + + print(f" 创建眼部相机: 左眼={self.left_eye_camera is not None}, 右眼={self.right_eye_camera is not None}") + + # 创建用于显示的左右眼纹理,格式与主程序保持一致 + self.left_eye_texture = Texture("left_eye_texture") + self.right_eye_texture = Texture("right_eye_texture") + self.left_eye_texture.setFormat(Texture.F_rgba8) + self.right_eye_texture.setFormat(Texture.F_rgba8) + self.left_eye_texture.setComponentType(Texture.T_unsigned_byte) + self.right_eye_texture.setComponentType(Texture.T_unsigned_byte) + + print(f" 创建眼部纹理: 左眼={self.left_eye_texture is not None}, 右眼={self.right_eye_texture is not None}") + print(" 设置纹理格式为F_rgba8和T_unsigned_byte") + + # 设置自动纹理缩放为None + self.left_eye_texture.setAutoTextureScale(ATSNone) + self.right_eye_texture.setAutoTextureScale(ATSNone) + + print(" 设置自动纹理缩放为ATSNone") + + # 将纹理附加到渲染缓冲区,使用RTMCopyRam模式确保数据能被复制到RAM + left_result = self.left_eye_buffer.addRenderTexture(self.left_eye_texture, GraphicsOutput.RTMCopyRam) + right_result = self.right_eye_buffer.addRenderTexture(self.right_eye_texture, GraphicsOutput.RTMCopyRam) + + print(f" 添加渲染纹理结果: 左眼={left_result}, 右眼={right_result}") + + # 设置相机渲染目标 + if self.left_eye_buffer and self.left_eye_camera: + self.left_eye_camera.node().setScene(self.world.render) + print(" 设置左眼相机渲染场景") + + if self.right_eye_buffer and self.right_eye_camera: + self.right_eye_camera.node().setScene(self.world.render) + print(" 设置右眼相机渲染场景") + + self.eye_textures['left'] = self.left_eye_texture + self.eye_textures['right'] = self.right_eye_texture + + print(" 初始化完成,开始渲染帧") + + # 强制渲染多帧以便初始化纹理数据 + # 参考主程序的实现方式,渲染足够多的帧以确保数据填充 + for i in range(5): # 渲染5帧以确保纹理数据被正确填充 + self.world.graphicsEngine.renderFrame() + print(f" 渲染第{i+1}帧") + + # 提取纹理数据到RAM + gsg = self.world.win.getGsg() + print(f" 获取GSG: {gsg is not None}") + + if gsg: + self.world.graphicsEngine.extractTextureData(self.left_eye_texture, gsg) + self.world.graphicsEngine.extractTextureData(self.right_eye_texture, gsg) + print(" 提取纹理数据到RAM") + + # 检查纹理数据是否成功提取 + left_has_ram = self.left_eye_texture.hasRamImage() + right_has_ram = self.right_eye_texture.hasRamImage() + print(f" 纹理RAM图像检查: 左眼={left_has_ram}, 右眼={right_has_ram}") + + if left_has_ram: + left_data_size = len(self.left_eye_texture.getRamImage().getData()) + print(f" 左眼纹理数据大小: {left_data_size} 字节") + + if right_has_ram: + right_data_size = len(self.right_eye_texture.getRamImage().getData()) + print(f" 右眼纹理数据大小: {right_data_size} 字节") + + def get_left_eye_texture(self): + """获取左眼纹理用于显示""" + return self.left_eye_texture + + def get_right_eye_texture(self): + """获取右眼纹理用于显示""" + return self.right_eye_texture + + def _create_eye_buffer(self, name, width, height): + """创建眼部渲染缓冲区""" + # 创建帧缓冲区属性 + fb_prop = FrameBufferProperties() + fb_prop.setRgbColor(True) + fb_prop.setRgbaBits(8, 8, 8, 8) + fb_prop.setDepthBits(24) + + # 创建窗口属性 + win_prop = WindowProperties() + win_prop.setSize(width, height) + + # 创建缓冲区 + buffer = self.world.graphicsEngine.makeOutput( + self.world.pipe, + name, + -1000, # 低优先级 + fb_prop, + win_prop, + GraphicsPipe.BFRefuseWindow + ) + + return buffer + + def _create_eye_camera(self, name, position_offset): + """创建眼部相机""" + # 创建相机节点 + camera_node = Camera(name) + lens = PerspectiveLens() + lens.setFov(100) # VR镜头视野 + lens.setNear(0.1) + lens.setFar(1000.0) + camera_node.setLens(lens) + + # 创建相机节点路径 + camera_np = self.world.render.attachNewNode(camera_node) + camera_np.setPos(*position_offset) + + return camera_np + + def shutdown_openxr(self): + """关闭OpenXR系统""" + if not self.openxr_enabled: + return True + + try: + # 清理渲染缓冲区 + if self.left_eye_buffer: + self.world.graphicsEngine.removeWindow(self.left_eye_buffer) + self.left_eye_buffer = None + + if self.right_eye_buffer: + self.world.graphicsEngine.removeWindow(self.right_eye_buffer) + self.right_eye_buffer = None + + # 清理相机 + if self.left_eye_camera: + self.left_eye_camera.removeNode() + self.left_eye_camera = None + + if self.right_eye_camera: + self.right_eye_camera.removeNode() + self.right_eye_camera = None + + # 重置状态 + self.openxr_enabled = False + self.xr_instance = None + self.xr_system = None + self.xr_session = None + + print("✓ OpenXR系统已关闭") + return True + + except Exception as e: + print(f"OpenXR关闭错误: {str(e)}") + return False + + def is_openxr_enabled(self): + """检查OpenXR是否启用""" + return self.openxr_enabled + + def get_openxr_info(self): + """获取OpenXR系统信息""" + return { + "enabled": self.openxr_enabled, + "mode": "simulation" if self.simulation_mode else "real", + "render_size": (self.render_width, self.render_height), + "simulation_mode": self.simulation_mode, + "wired_connection": self.wired_connection + } + + def update_simulation_data(self, data_type, data): + """更新模拟数据""" + if data_type in self.simulation_data: + self.simulation_data[data_type] = data + return True + return False + + def get_simulation_data(self, data_type=None): + """获取模拟数据""" + if data_type: + return self.simulation_data.get(data_type, None) + return self.simulation_data + + def enable_simulation_mode(self): + """启用模拟模式""" + if not self.simulation_mode: + self.shutdown_openxr() + self.simulation_mode = True + self.initialize_openxr(force_simulation=True) \ No newline at end of file diff --git a/core/openxr_streamer.py b/core/openxr_streamer.py new file mode 100644 index 00000000..0db16833 --- /dev/null +++ b/core/openxr_streamer.py @@ -0,0 +1,257 @@ +""" +OpenXR串流处理器 + +负责与VR设备通信和视频流传输 +支持有线连接的低延迟串流 +""" + +import socket +import struct +import threading +import json +import time +import subprocess +import psutil +from direct.showbase.DirectObject import DirectObject +from panda3d.core import Texture, PNMImage + + +class OpenXRStreamer(DirectObject): + """OpenXR串流处理器""" + + def __init__(self, world, openxr_manager): + super().__init__() + self.world = world + self.openxr_manager = openxr_manager + + # 串流服务器配置 + self.server_ip = "127.0.0.1" + self.server_port = 9943 + self.streaming_port = 9944 + + # 有线连接设置 + self.connection_mode = "wired" # wired, wireless + self.wired_port = 9945 # 有线连接端口 + + # 连接状态 + self.connected = False + self.streaming = False + self.server_socket = None + self.streaming_socket = None + + # 流媒体配置 + self.stream_width = 2880 # 推荐分辨率 + self.stream_height = 1700 + self.stream_fps = 72 + self.bitrate = 150 # Mbps + self.codec = "h264" + + # 线程管理 + self.connection_thread = None + self.streaming_thread = None + self.running = False + + # 性能统计 + self.frame_count = 0 + self.last_fps_time = time.time() + self.current_fps = 0 + self.latency = 0 + + print("✓ OpenXR串流处理器初始化完成") + + def initialize(self): + """初始化OpenXR串流""" + try: + # 检查串流服务器是否运行 + if not self._check_streaming_server(): + print("串流服务器未运行,尝试启动...") + if not self._start_streaming_server(): + print("无法启动串流服务器") + return False + + # 连接到串流服务器 + if not self._connect_to_server(): + print("无法连接到串流服务器") + return False + + # 配置流媒体设置 + self._configure_streaming() + + # 启动串流线程 + self._start_streaming_threads() + + print("✓ OpenXR串流初始化成功") + return True + + except Exception as e: + print(f"OpenXR串流初始化错误: {str(e)}") + return False + + def _check_streaming_server(self): + """检查串流服务器是否运行""" + try: + # TODO: 实际检查串流服务器状态 + # 这里暂时返回False以便在开发阶段使用模拟模式 + return False + except: + return False + + def _start_streaming_server(self): + """启动串流服务器""" + try: + # TODO: 实际启动串流服务器 + print("ℹ 串流服务器启动占位符") + return False + except Exception as e: + print(f"串流服务器启动失败: {str(e)}") + return False + + def _connect_to_server(self): + """连接到串流服务器""" + try: + # TODO: 实际连接到串流服务器 + print("ℹ 串流服务器连接占位符") + return False + except Exception as e: + print(f"串流服务器连接失败: {str(e)}") + return False + + def _configure_streaming(self): + """配置流媒体设置""" + # 根据OpenXR管理器设置配置串流参数 + if self.openxr_manager: + render_size = self.openxr_manager.get_openxr_info()['render_size'] + self.stream_width = render_size[0] + self.stream_height = render_size[1] + + print(f"ℹ 串流配置: {self.stream_width}x{self.stream_height}@{self.stream_fps}fps") + + def _start_streaming_threads(self): + """启动串流线程""" + self.running = True + + # 启动连接线程 + self.connection_thread = threading.Thread(target=self._connection_handler, daemon=True) + self.connection_thread.start() + + # 启动串流线程 + self.streaming_thread = threading.Thread(target=self._streaming_handler, daemon=True) + self.streaming_thread.start() + + def _connection_handler(self): + """连接处理线程""" + while self.running: + try: + # TODO: 实现连接处理逻辑 + time.sleep(1) + except Exception as e: + print(f"连接处理错误: {str(e)}") + + def _streaming_handler(self): + """串流处理线程""" + while self.running: + try: + if self.streaming: + self._send_video_frames() + time.sleep(1.0 / self.stream_fps) + except Exception as e: + print(f"串流处理错误: {str(e)}") + + def _send_video_frames(self): + """发送视频帧""" + try: + # 获取左右眼纹理 + if self.openxr_manager and hasattr(self.openxr_manager, 'eye_textures'): + left_texture = self.openxr_manager.eye_textures.get('left') + right_texture = self.openxr_manager.eye_textures.get('right') + + if left_texture and right_texture: + # TODO: 实现视频编码和发送逻辑 + self.frame_count += 1 + + # 更新FPS统计 + current_time = time.time() + if current_time - self.last_fps_time >= 1.0: + self.current_fps = self.frame_count + self.frame_count = 0 + self.last_fps_time = current_time + # print(f"串流FPS: {self.current_fps}") + + except Exception as e: + print(f"视频帧发送错误: {str(e)}") + + def start_streaming(self): + """开始OpenXR串流""" + if not self.connected: + print("串流服务器未连接") + return False + + self.streaming = True + print("✓ OpenXR串流已开始") + return True + + def stop_streaming(self): + """停止OpenXR串流""" + self.streaming = False + print("✓ OpenXR串流已停止") + + def get_streaming_status(self): + """获取串流状态""" + return { + "connected": self.connected, + "streaming": self.streaming, + "fps": self.current_fps, + "latency": self.latency, + "resolution": f"{self.stream_width}x{self.stream_height}", + "bitrate": self.bitrate, + "codec": self.codec + } + + def set_stream_quality(self, width, height, fps, bitrate): + """设置串流质量""" + self.stream_width = width + self.stream_height = height + self.stream_fps = fps + self.bitrate = bitrate + + print(f"✓ 串流质量已设置: {width}x{height}@{fps}fps, {bitrate}Mbps") + return True + + def send_haptic_feedback(self, controller_id, duration, intensity): + """发送触觉反馈""" + try: + # TODO: 实现触觉反馈发送逻辑 + print(f"ℹ 触觉反馈发送占位符: 控制器{controller_id}, 持续时间{duration}s, 强度{intensity}") + return True + except Exception as e: + print(f"触觉反馈发送失败: {str(e)}") + return False + + def shutdown(self): + """关闭串流""" + self.running = False + self.stop_streaming() + + # 等待线程结束 + if self.connection_thread and self.connection_thread.is_alive(): + self.connection_thread.join(timeout=2) + + if self.streaming_thread and self.streaming_thread.is_alive(): + self.streaming_thread.join(timeout=2) + + # 关闭套接字 + if self.server_socket: + self.server_socket.close() + + if self.streaming_socket: + self.streaming_socket.close() + + print("✓ OpenXR串流已关闭") + + def is_connected(self): + """检查是否连接""" + return self.connected + + def is_streaming(self): + """检查是否正在串流""" + return self.streaming \ 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 f4cb0832..00000000 --- a/core/vr_manager.py +++ /dev/null @@ -1,666 +0,0 @@ -import warnings -warnings.filterwarnings("ignore", category=DeprecationWarning) - -from panda3d.core import * -from panda3d.core import Mat4, PerspectiveLens, CardMaker -# 修复taskMgr导入错误,应该从TaskManagerGlobal导入而不是ShowBaseGlobal -from direct.task.TaskManagerGlobal import taskMgr -from direct.showbase.DirectObject import DirectObject -from direct.task import Task -import sys - -class VRManager(DirectObject): - """VR管理器 - 处理VR系统初始化、追踪和渲染""" - - def __init__(self, world): - super().__init__() - self.world = world - self.vr_enabled = False - self.vr_system = None - self.vr_compositor = None - self.render_width = 1920 - self.render_height = 1080 - self.alvr_enabled = False - - # 模拟模式设置 - self.simulation_mode = False - self.simulation_data = { - 'head_pose': {'position': [0, 0, 1.6], 'rotation': [0, 0, 0, 1]}, - 'controller_poses': { - 0: {'position': [-0.3, 0, 1.2], 'rotation': [0, 0, 0, 1], 'connected': True}, - 1: {'position': [0.3, 0, 1.2], 'rotation': [0, 0, 0, 1], 'connected': True} - }, - 'render_size': (1920, 1080) - } - - # 立体渲染缓冲区 - self.left_eye_buffer = None - self.right_eye_buffer = None - self.left_eye_camera = None - self.right_eye_camera = None - - # 眼部纹理(用于ALVR串流) - self.left_eye_texture = None - self.right_eye_texture = None - self.eye_textures = {} - - # 控制器相关 - self.controller_nodes = {} - self.controller_poses = {} - - print("✓ VR管理器初始化完成") - - def initialize_vr(self, force_simulation=False): - """初始化VR系统""" - try: - # 检查是否强制使用模拟模式 - if force_simulation: - print("🔧 强制启用VR模拟模式") - return self._init_simulation_mode() - - # 检查OpenVR支持 - if not self._check_openvr_support(): - print("⚠ OpenVR支持不可用,切换到模拟模式") - return self._init_simulation_mode() - - # 尝试初始化OpenVR - if not self._init_openvr(): - print("⚠ OpenVR初始化失败,切换到模拟模式") - print("提示: 请确保SteamVR正在运行且VR头盔已连接") - return self._init_simulation_mode() - - # 真实VR模式初始化成功 - print("✓ 真实VR模式初始化成功") - return self._init_real_vr_mode() - - except Exception as e: - print(f"VR初始化错误: {str(e)}") - print("⚠ 切换到模拟模式") - return self._init_simulation_mode() - - def _init_simulation_mode(self): - """初始化模拟模式""" - try: - self.simulation_mode = True - - # 使用模拟数据设置渲染尺寸 - self.render_width, self.render_height = self.simulation_data['render_size'] - print(f"🎮 模拟VR渲染尺寸: {self.render_width}x{self.render_height}") - - # 设置模拟立体渲染 - self._setup_stereo_rendering() - - # 启动模拟VR任务 - self._start_simulation_tasks() - - self.vr_enabled = True - print("✓ VR模拟模式初始化完成") - print("ℹ 模拟模式说明:") - print(" - 头盔追踪: 模拟数据") - print(" - 控制器: 模拟两个控制器") - print(" - 渲染: 立体渲染到窗口") - print(" - 交互: 键盘鼠标模拟") - - return True - - except Exception as e: - print(f"模拟模式初始化错误: {str(e)}") - return False - - def _init_real_vr_mode(self): - """初始化真实VR模式""" - try: - self.simulation_mode = False - - # 设置立体渲染 - self._setup_stereo_rendering() - - # 初始化ALVR - if self._init_alvr(): - print("✓ ALVR串流已启用") - self.alvr_enabled = True - - # 启动VR更新任务 - self._start_vr_tasks() - - self.vr_enabled = True - print("✓ 真实VR系统初始化完成") - return True - - except Exception as e: - print(f"真实VR模式初始化错误: {str(e)}") - return False - - def _check_openvr_support(self): - """检查OpenVR支持""" - try: - import openvr - return True - except ImportError: - print("OpenVR库未安装") - return False - - def _init_openvr(self): - """初始化OpenVR""" - try: - import openvr - - # 初始化OpenVR - self.vr_system = openvr.init(openvr.VRApplication_Scene) - if not self.vr_system: - return False - - # 获取合成器 - self.vr_compositor = openvr.VRCompositor() - if not self.vr_compositor: - return False - - # 获取推荐的渲染尺寸 - self.render_width, self.render_height = self.vr_system.getRecommendedRenderTargetSize() - print(f"VR推荐渲染尺寸: {self.render_width}x{self.render_height}") - - return True - - except Exception as e: - print(f"OpenVR初始化错误: {str(e)}") - return False - - def _setup_stereo_rendering(self): - """设置立体渲染""" - try: - # 创建眼部缓冲区 - self._create_eye_buffers() - - # 创建眼部摄像机 - self._create_eye_cameras() - - # 设置渲染目标 - self._setup_render_targets() - - print("✓ 立体渲染设置完成") - - except Exception as e: - print(f"立体渲染设置错误: {str(e)}") - - def _create_eye_buffers(self): - """创建眼部渲染缓冲区""" - try: - # 创建左眼缓冲区 - self.left_eye_buffer = self.world.win.makeTextureBuffer( - "left_eye", self.render_width, self.render_height - ) - self.left_eye_buffer.setSort(-100) - - # 创建右眼缓冲区 - self.right_eye_buffer = self.world.win.makeTextureBuffer( - "right_eye", self.render_width, self.render_height - ) - self.right_eye_buffer.setSort(-99) - - # 获取纹理并确保它们可以被访问 - self.left_eye_texture = self.left_eye_buffer.getTexture() - self.right_eye_texture = self.right_eye_buffer.getTexture() - - # 设置纹理参数以确保它们可以被ALVR访问 - if self.left_eye_texture: - self.left_eye_texture.set_format(Texture.F_rgba8) - self.left_eye_texture.set_component_type(Texture.T_unsigned_byte) - self.left_eye_texture.set_wrap_u(Texture.WM_clamp) - self.left_eye_texture.set_wrap_v(Texture.WM_clamp) - # 确保纹理数据可以被读取到RAM(使用正确的方法) - try: - self.left_eye_texture.set_ram_image_compression(Texture.CM_off) - except AttributeError: - # 如果方法不存在,使用替代方法 - pass - - if self.right_eye_texture: - self.right_eye_texture.set_format(Texture.F_rgba8) - self.right_eye_texture.set_component_type(Texture.T_unsigned_byte) - self.right_eye_texture.set_wrap_u(Texture.WM_clamp) - self.right_eye_texture.set_wrap_v(Texture.WM_clamp) - # 确保纹理数据可以被读取到RAM(使用正确的方法) - try: - self.right_eye_texture.set_ram_image_compression(Texture.CM_off) - except AttributeError: - # 如果方法不存在,使用替代方法 - pass - - # 存储到eye_textures字典中供ALVR使用 - self.eye_textures['left'] = self.left_eye_texture - self.eye_textures['right'] = self.right_eye_texture - - print("✓ 眼部渲染缓冲区创建完成") - print(f" 左眼纹理: {self.left_eye_texture.getXSize()}x{self.left_eye_texture.getYSize()}") - print(f" 右眼纹理: {self.right_eye_texture.getXSize()}x{self.right_eye_texture.getYSize()}") - - except Exception as e: - print(f"眼部缓冲区创建错误: {str(e)}") - import traceback - traceback.print_exc() - - def _create_eye_cameras(self): - """创建眼部摄像机""" - try: - # 创建左眼摄像机 - self.left_eye_camera = self.world.makeCamera(self.left_eye_buffer) - self.left_eye_camera.setPos(-0.032, 0, 0) # 瞳距的一半 - - # 创建右眼摄像机 - self.right_eye_camera = self.world.makeCamera(self.right_eye_buffer) - self.right_eye_camera.setPos(0.032, 0, 0) # 瞳距的一半 - - # 设置投影矩阵 - if not self.simulation_mode: - self._update_eye_projection(0, self.left_eye_camera.node().getLens()) - self._update_eye_projection(1, self.right_eye_camera.node().getLens()) - else: - # 模拟模式使用标准透视投影 - lens = PerspectiveLens() - lens.setFov(110) # 模拟VR FOV - lens.setNearFar(0.1, 1000) - self.left_eye_camera.node().setLens(lens) - self.right_eye_camera.node().setLens(lens) - - print("✓ 眼部摄像机创建完成") - - except Exception as e: - print(f"眼部摄像机创建错误: {str(e)}") - - def _update_eye_projection(self, eye, lens): - """更新眼部投影矩阵""" - try: - if self.simulation_mode: - # 模拟模式使用标准投影 - perspective_lens = PerspectiveLens() - perspective_lens.setFov(110) - perspective_lens.setNearFar(0.1, 1000) - lens.copyFrom(perspective_lens) - return - import openvr - # 获取投影矩阵 - projection_matrix = self.vr_system.getProjectionMatrix(eye, 0.1, 1000.0) - # 转换为 Panda3D 矩阵 - panda_matrix = self._convert_openvr_matrix(projection_matrix) - # 使用正确的方法设置自定义投影矩阵 - try: - lens.set_user_mat(panda_matrix) - except AttributeError: - # 如果方法不存在,尝试替代方法 - try: - lens.set_projection_mat(panda_matrix) - except AttributeError: - # 兜底:设置标准 FOV - lens.setFov(110) - lens.setNearFar(0.1, 1000) - except Exception as e: - print(f"眼部投影更新错误: {str(e)}") - - def _setup_render_targets(self): - """设置渲染目标""" - try: - # 在模拟模式下,可以选择将渲染结果显示到主窗口 - if self.simulation_mode: - # 创建并排显示的立体视图 - self._setup_simulation_display() - - print("✓ 渲染目标设置完成") - - except Exception as e: - print(f"渲染目标设置错误: {str(e)}") - - def _setup_simulation_display(self): - """设置模拟显示""" - try: - # 创建卡片来显示眼部纹理 - cm = CardMaker("stereo_display") - - # 左眼显示区域 - cm.setFrame(-1, 0, -1, 1) - left_card = self.world.render2d.attachNewNode(cm.generate()) - left_card.setTexture(self.left_eye_texture) - - # 右眼显示区域 - cm.setFrame(0, 1, -1, 1) - right_card = self.world.render2d.attachNewNode(cm.generate()) - right_card.setTexture(self.right_eye_texture) - - print("✓ 模拟立体显示设置完成") - - except Exception as e: - print(f"模拟显示设置错误: {str(e)}") - - def _init_alvr(self): - """初始化ALVR(仅在真实VR模式下)""" - if self.simulation_mode: - print("ℹ 模拟模式: ALVR串流已跳过") - return False - - try: - # ALVR初始化逻辑 - # 这里应该连接到ALVR服务器 - print("✓ ALVR初始化完成") - return True - except Exception as e: - print(f"ALVR初始化错误: {str(e)}") - return False - - def _start_vr_tasks(self): - """启动VR更新任务(真实VR模式)""" - if not self.simulation_mode: - self.world.taskMgr.add(self._update_vr_tracking, "vr_tracking") - self.world.taskMgr.add(self._update_vr_rendering, "vr_rendering") - - def _start_simulation_tasks(self): - """启动模拟VR任务""" - self.world.taskMgr.add(self._update_simulation_tracking, "simulation_tracking") - self.world.taskMgr.add(self._update_simulation_rendering, "simulation_rendering") - - def _update_simulation_tracking(self, task): - """更新模拟追踪数据""" - try: - # 模拟头部追踪(可以添加一些变化) - import math - time_factor = task.time * 0.5 - - # 模拟轻微的头部摆动 - head_pos = self.simulation_data['head_pose']['position'] - head_pos[1] = math.sin(time_factor) * 0.05 # 前后轻微摆动 - - # 更新主摄像机位置 - if hasattr(self.world, 'camera'): - self.world.camera.setPos(head_pos[0], head_pos[1], head_pos[2]) - - # 更新控制器位置(模拟手部动作) - for controller_id, pose in self.simulation_data['controller_poses'].items(): - if pose['connected']: - # 模拟控制器轻微移动 - pose['position'][1] = math.sin(time_factor + controller_id) * 0.1 - - # 更新控制器节点位置 - if controller_id in self.controller_nodes: - node = self.controller_nodes[controller_id] - node.setPos(pose['position'][0], pose['position'][1], pose['position'][2]) - - return task.cont - - except Exception as e: - print(f"模拟追踪更新错误: {str(e)}") - return task.cont - - def _update_simulation_rendering(self, task): - """更新模拟渲染""" - try: - # 在模拟模式下,渲染已经由Panda3D自动处理 - # 这里可以添加任何特殊的渲染逻辑 - return task.cont - - except Exception as e: - print(f"模拟渲染更新错误: {str(e)}") - return task.cont - - def _update_vr_tracking(self, task): - """更新VR追踪数据(真实VR模式)""" - try: - if not self.vr_system or self.simulation_mode: - return task.cont - - import openvr - - # 获取设备姿态 - poses, game_poses = self.vr_compositor.waitGetPoses(None, None) - - # 检查poses是否有效 - if poses is None: - print("VR追踪更新错误: 无法获取设备姿态数据") - return task.cont - - # 更新头显位置 - if poses[openvr.k_unTrackedDeviceIndex_Hmd].bPoseIsValid: - self._update_main_camera_pose() - - # 更新眼部摄像机 - self._update_eye_cameras() - - # 更新控制器姿态 - self._update_controller_poses(poses) - - return task.cont - - except Exception as e: - print(f"VR追踪更新错误: {str(e)}") - return task.cont - - def _update_vr_rendering(self, task): - """更新VR渲染(真实VR模式)""" - try: - if not self.vr_compositor or self.simulation_mode: - return task.cont - - # 确保渲染完成后再提交帧 - if hasattr(self.world, 'graphicsEngine'): - self.world.graphicsEngine.render_frame() - - # 提交帧到合成器 - self._submit_frames_to_compositor() - - return task.cont - - except Exception as e: - print(f"VR渲染更新错误: {str(e)}") - return task.cont - - def _convert_openvr_matrix(self, openvr_matrix): - """转换OpenVR矩阵为Panda3D矩阵""" - # 实现矩阵转换逻辑 - mat = Mat4() - # 这里需要实现具体的矩阵转换 - return mat - - def _update_main_camera_pose(self): - """更新主摄像机姿态""" - try: - if self.simulation_mode: - return - - # 从VR系统获取头显姿态并应用到主摄像机 - pass - - except Exception as e: - print(f"主摄像机姿态更新错误: {str(e)}") - - def _update_eye_cameras(self): - """更新眼部摄像机""" - try: - if self.simulation_mode: - return - - # 更新左右眼摄像机位置 - pass - - except Exception as e: - print(f"眼部摄像机更新错误: {str(e)}") - - def _update_controller_poses(self, poses): - """更新控制器姿态""" - try: - if self.simulation_mode: - return - - # 更新控制器位置和姿态 - pass - - except Exception as e: - print(f"控制器姿态更新错误: {str(e)}") - - def _submit_frames_to_compositor(self): - """提交帧到合成器""" - try: - if not self.vr_compositor or self.simulation_mode: - return - - import openvr - - # 确保纹理已正确创建并有效 - if not self.left_eye_texture or not self.right_eye_texture: - print("⚠ 眼部纹理未正确创建,无法提交帧") - return - - # 检查纹理是否准备好 - if not self.left_eye_texture.has_ram_image() or not self.right_eye_texture.has_ram_image(): - # 强制更新纹理数据 - try: - self.world.graphicsEngine.extract_texture_data(self.left_eye_texture, self.world.win.get_gsg()) - self.world.graphicsEngine.extract_texture_data(self.right_eye_texture, self.world.win.get_gsg()) - except Exception as e: - print(f"纹理数据提取失败: {str(e)}") - - # 提交左眼纹理 - left_eye_texture = openvr.Texture_t() - # 使用正确的API获取纹理ID - try: - tex_handle = self.left_eye_texture.get_handle() - except: - # 如果get_handle方法不可用,尝试其他方式 - tex_handle = self.left_eye_texture.get_texture_id() if hasattr(self.left_eye_texture, 'get_texture_id') else 0 - - left_eye_texture.handle = tex_handle - left_eye_texture.eType = openvr.TextureType_OpenGL - left_eye_texture.eColorSpace = openvr.ColorSpace_Gamma - - # 检查纹理是否有效后再提交 - if tex_handle: - self.vr_compositor.submit(openvr.Eye_Left, left_eye_texture) - - # 提交右眼纹理 - right_eye_texture = openvr.Texture_t() - # 使用正确的API获取纹理ID - try: - tex_handle = self.right_eye_texture.get_handle() - except: - # 如果get_handle方法不可用,尝试其他方式 - tex_handle = self.right_eye_texture.get_texture_id() if hasattr(self.right_eye_texture, 'get_texture_id') else 0 - - right_eye_texture.handle = tex_handle - right_eye_texture.eType = openvr.TextureType_OpenGL - right_eye_texture.eColorSpace = openvr.ColorSpace_Gamma - - # 检查纹理是否有效后再提交 - if tex_handle: - self.vr_compositor.submit(openvr.Eye_Right, right_eye_texture) - - # 等待下一帧垂直同步 - self.vr_compositor.waitGetPoses(None, None) - - except Exception as e: - print(f"帧提交错误: {str(e)}") - import traceback - traceback.print_exc() - - def get_controller_input(self, controller_id): - """获取控制器输入""" - try: - if self.simulation_mode: - # 返回模拟的控制器输入 - return { - 'trigger': 0.0, - 'grip': 0.0, - 'touchpad': {'x': 0.0, 'y': 0.0, 'pressed': False}, - 'menu': False, - 'connected': controller_id in self.simulation_data['controller_poses'] - } - - if not self.vr_system: - return None - - import openvr - - # 获取控制器状态 - result, controller_state = self.vr_system.getControllerState(controller_id) - if not result: - return None - - # 解析控制器输入 - return { - 'trigger': controller_state.rAxis[1].x, - 'grip': controller_state.rAxis[2].x, - 'touchpad': { - 'x': controller_state.rAxis[0].x, - 'y': controller_state.rAxis[0].y, - 'pressed': controller_state.rAxis[0].x != 0 or controller_state.rAxis[0].y != 0 - }, - 'menu': controller_state.ulButtonPressed & (1 << openvr.k_EButton_ApplicationMenu) != 0, - 'connected': True - } - - except Exception as e: - print(f"控制器输入获取错误: {str(e)}") - return None - - def shutdown_vr(self): - """关闭VR系统""" - try: - # 停止任务 - if self.simulation_mode: - self.world.taskMgr.remove("simulation_tracking") - self.world.taskMgr.remove("simulation_rendering") - else: - self.world.taskMgr.remove("vr_tracking") - self.world.taskMgr.remove("vr_rendering") - - # 关闭OpenVR - if self.vr_system and not self.simulation_mode: - import openvr - openvr.shutdown() - - # 清理资源 - self.left_eye_buffer = None - self.right_eye_buffer = None - self.left_eye_camera = None - self.right_eye_camera = None - - self.vr_enabled = False - self.simulation_mode = False - - print("✓ VR系统已关闭") - - except Exception as e: - print(f"VR关闭错误: {str(e)}") - - def is_vr_enabled(self): - """检查VR是否启用""" - return self.vr_enabled - - def get_vr_info(self): - """获取VR系统信息""" - info = { - 'enabled': self.vr_enabled, - 'simulation_mode': self.simulation_mode, - 'render_size': (self.render_width, self.render_height), - 'alvr_enabled': self.alvr_enabled - } - - if self.simulation_mode: - info['mode'] = 'simulation' - info['controllers'] = len(self.simulation_data['controller_poses']) - else: - info['mode'] = 'real_vr' - info['openvr_connected'] = self.vr_system is not None - - return info - - # 便捷方法 - def enable_simulation_mode(self): - """启用模拟模式(调试用)""" - return self.initialize_vr(force_simulation=True) - - def get_simulation_data(self): - """获取模拟数据""" - return self.simulation_data if self.simulation_mode else None - - def update_simulation_data(self, key, value): - """更新模拟数据""" - if self.simulation_mode and key in self.simulation_data: - self.simulation_data[key] = value - return True - return False \ No newline at end of file diff --git a/demo/OPENXR_IMPLEMENTATION_GUIDE.md b/demo/OPENXR_IMPLEMENTATION_GUIDE.md new file mode 100644 index 00000000..75cb804b --- /dev/null +++ b/demo/OPENXR_IMPLEMENTATION_GUIDE.md @@ -0,0 +1,346 @@ +# OpenXR 实现指南 + +## 🎯 概述 + +本指南详细介绍了如何在Panda3D引擎中实现OpenXR支持,并通过有线连接串流到VR设备。 + +## 🏗️ 系统架构 + +### 核心组件 + +1. **OpenXRManager** (`core/openxr_manager.py`) + - 负责OpenXR系统初始化 + - 管理立体渲染(左右眼) + - 处理VR设备跟踪 + - 集成OpenXR接口 + +2. **OpenXRInputHandler** (`core/openxr_input_handler.py`) + - 处理OpenXR控制器输入 + - 手势识别系统 + - 交互逻辑处理 + +3. **OpenXRStreamer** (`core/openxr_streamer.py`) + - OpenXR服务器通信 + - 视频流编码和传输 + - 性能监控 + +## 🔧 安装和配置 + +### 1. 系统依赖 + +```bash +# 安装Python依赖 +pip install -r requirements/openxr-requirements.txt + +# 安装OpenXR运行时 +# Windows: +# 1. 下载并安装SteamVR或Oculus运行时 +# 2. 安装OpenXR运行时 + +# Linux: +sudo apt-get install openxr-loader +``` + +### 2. 有线连接设置 + +```bash +# 配置网络以支持有线连接 +# 1. 使用USB网络适配器或以太网连接VR设备 +# 2. 确保PC和VR设备在同一子网 +# 3. 配置防火墙允许OpenXR通信(端口9943-9945) +``` + +### 3. VR设备配置 + +```bash +# 1. 在VR设备上安装OpenXR客户端应用 +# 2. 通过有线连接设备到PC +# 3. 启用开发者模式(如需要) +``` + +## 🚀 使用方法 + +### 快速启动 + +```python +# 在主程序中 +world = MyWorld() + +# 一键启用OpenXR模式 +if world.enableOpenXRMode(): + print("OpenXR模式已启用") + + # 检查OpenXR状态 + status = world.getOpenXRStatus() + print(f"OpenXR设备: {status['openxr_info']}") + print(f"连接状态: {status['openxr_connected']}") + print(f"串流状态: {status['openxr_streaming']}") +``` + +### 详细控制 + +```python +# 分步骤启动OpenXR +world = MyWorld() + +# 1. 初始化OpenXR系统 +if world.initializeOpenXR(): + print("OpenXR系统初始化成功") + + # 2. 启动OpenXR输入处理 + world.startOpenXRInput() + + # 3. 显示控制器射线 + world.showControllerRays(True) + + # 4. 初始化OpenXR串流 + if world.initializeOpenXRStreaming(): + print("OpenXR串流初始化成功") + + # 5. 设置串流质量 + world.setOpenXRStreamQuality( + width=2880, + height=1700, + fps=72, + bitrate=150 + ) + + # 6. 开始串流 + world.startOpenXRStreaming() +``` + +## 🎮 OpenXR交互功能 + +### 控制器输入处理 + +```python +# 获取控制器状态 +controllers = world.getAllControllers() +for controller_id in controllers: + state = world.getControllerState(controller_id) + + # 检查触发器按下 + if state['trigger'] > 0.5: + print(f"控制器 {controller_id} 触发器按下") + + # 检查摇杆输入 + thumbstick_x, thumbstick_y = state['thumbstick']['x'], state['thumbstick']['y'] + if abs(thumbstick_x) > 0.5: + print(f"摇杆水平滑动: {thumbstick_x}") +``` + +### 对象抓取和操作 + +```python +# OpenXR输入处理器自动处理对象抓取 +# 触发器按下时自动检测和抓取对象 +# 触发器释放时自动释放对象 + +# 监听OpenXR事件 +world.event_handler.accept('openxr-object-grabbed', onObjectGrabbed) +world.event_handler.accept('openxr-object-released', onObjectReleased) + +def onObjectGrabbed(object_node, controller_id): + print(f"抓取对象: {object_node.getName()}") + +def onObjectReleased(object_node, controller_id): + print(f"释放对象: {object_node.getName()}") +``` + +### 触觉反馈 + +```python +# 发送触觉反馈 +world.sendHapticFeedback( + controller_id=0, + duration=0.5, # 持续时间(秒) + intensity=0.8 # 强度(0-1) +) +``` + +## 📊 性能优化 + +### 渲染优化 + +```python +# 调整OpenXR渲染质量 +openxr_manager = world.openxr_manager +openxr_manager.render_scale = 1.0 # 渲染缩放 (0.5-2.0) + +# 设置多重采样抗锯齿 +fb_props = FrameBufferProperties() +fb_props.setMultisamples(4) # 4x MSAA +``` + +### 网络优化 + +```python +# 优化OpenXR串流参数 +world.setOpenXRStreamQuality( + width=2160, # 降低分辨率提高性能 + height=1200, + fps=60, # 降低帧率减少延迟 + bitrate=100 # 降低比特率适应网络带宽 +) +``` + +### 系统资源监控 + +```python +# 获取性能状态 +streaming_status = world.getOpenXRStreamingStatus() +print(f"串流FPS: {streaming_status['fps']}") +print(f"延迟: {streaming_status['latency']} ms") +print(f"分辨率: {streaming_status['resolution']}") +``` + +## 🔍 故障排除 + +### 常见问题 + +1. **OpenXR系统初始化失败** + ``` + 错误: OpenXR初始化失败 + 解决: 确保OpenXR运行时已安装并运行 + ``` + +2. **串流连接失败** + ``` + 错误: 无法连接到串流服务器 + 解决: + - 检查串流服务器是否运行 + - 确认防火墙设置 + - 检查网络连接 + ``` + +3. **串流质量问题** + ``` + 问题: 画面卡顿或延迟高 + 解决: + - 降低分辨率和帧率 + - 检查网络带宽 + - 调整比特率设置 + ``` + +### 调试命令 + +```python +# 启用调试模式 +world.openxr_manager.debug_mode = True + +# 获取详细状态信息 +openxr_info = world.getOpenXRInfo() +print(f"OpenXR设备信息: {openxr_info}") + +# 检查网络连接 +if world.isOpenXRConnected(): + print("OpenXR连接正常") +else: + print("OpenXR连接失败") +``` + +## 🎯 高级功能 + +### 自定义OpenXR交互 + +```python +# 创建自定义OpenXR交互逻辑 +class CustomOpenXRInteraction: + def __init__(self, world): + self.world = world + + def handleCustomGesture(self, gesture_data): + # 处理自定义手势 + pass + + def createOpenXRMenu(self, controller_id): + # 在VR中创建3D菜单 + pass +``` + +### 多用户OpenXR支持 + +```python +# 支持多个OpenXR用户 +class MultiUserOpenXRManager: + def __init__(self): + self.openxr_users = {} + + def addOpenXRUser(self, user_id, openxr_manager): + self.openxr_users[user_id] = openxr_manager + + def syncOpenXRUsers(self): + # 同步多用户OpenXR状态 + pass +``` + +## 📋 配置文件 + +### OpenXR配置 (`config/openxr_config.yaml`) + +```yaml +openxr: + enabled: true + render_scale: 1.0 + tracking_space: "stage" + +streaming: + server_ip: "127.0.0.1" + server_port: 9943 + streaming_port: 9944 + + video: + width: 2880 + height: 1700 + fps: 72 + bitrate: 150 + codec: "h264" + + audio: + enabled: true + sample_rate: 48000 + channels: 2 +``` + +## 🚀 部署建议 + +### 开发环境 + +```bash +# 创建OpenXR开发环境 +python -m venv openxr_env +source openxr_env/bin/activate +pip install -r requirements/openxr-requirements.txt + +# 启动开发服务器 +python main.py --openxr-mode +``` + +### 生产环境 + +```bash +# 优化生产部署 +# 1. 使用专用计算机处理OpenXR渲染 +# 2. 配置高性能有线网络连接 +# 3. 启用GPU加速 +# 4. 监控系统性能 +``` + +## 📚 参考资料 + +- [OpenXR规范](https://www.khronos.org/openxr/) +- [Panda3D文档](https://docs.panda3d.org/) +- [OpenXR Cookbook](https://github.com/KhronosGroup/OpenXR-Docs) + +## 🤝 贡献指南 + +欢迎贡献代码和改进建议!请遵循以下步骤: + +1. Fork此仓库 +2. 创建功能分支 +3. 提交代码修改 +4. 创建Pull Request + +## 📄 许可证 + +本项目采用MIT许可证。详见LICENSE文件。 \ 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/direct_vr_streaming_test.py b/direct_vr_streaming_test.py deleted file mode 100644 index 9d510dac..00000000 --- a/direct_vr_streaming_test.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -直接VR串流测试脚本 -测试不通过ALVR,直接使用OpenVR API进行串流的功能 -""" - -import sys -import os -import time -from main import MyWorld - - -def test_direct_vr_streaming(): - """测试直接VR串流功能""" - print("=== 直接VR串流功能测试 ===") - - # 创建世界实例 - world = MyWorld() - - # 初始化VR系统 - print("🥽 初始化VR系统...") - if not world.initializeVR(): - print("✗ VR系统初始化失败") - return False - - print("✓ VR系统初始化成功") - - # 获取VR信息 - vr_info = world.getVRInfo() - print(f"\n📊 VR系统信息:") - print(f" - 模式: {vr_info['mode']}") - print(f" - 启用状态: {vr_info['enabled']}") - print(f" - 模拟模式: {vr_info['simulation_mode']}") - print(f" - 渲染尺寸: {vr_info['render_size']}") - - # 检查是否为真实VR模式 - if not vr_info['enabled'] or vr_info['simulation_mode']: - print("⚠ 未检测到真实VR设备或处于模拟模式") - print(" 直接串流需要真实的VR设备支持") - return False - - # 检查VR纹理是否正确创建 - print("\n🔍 检查VR纹理...") - if hasattr(world.vr_manager, 'left_eye_texture') and world.vr_manager.left_eye_texture: - print(" ✓ 左眼纹理已创建") - print(f" 纹理尺寸: {world.vr_manager.left_eye_texture.getXSize()}x{world.vr_manager.left_eye_texture.getYSize()}") - else: - print(" ✗ 左眼纹理未创建") - return False - - if hasattr(world.vr_manager, 'right_eye_texture') and world.vr_manager.right_eye_texture: - print(" ✓ 右眼纹理已创建") - print(f" 纹理尺寸: {world.vr_manager.right_eye_texture.getXSize()}x{world.vr_manager.right_eye_texture.getYSize()}") - else: - print(" ✗ 右眼纹理未创建") - return False - - # 检查OpenVR系统是否正常 - print("\n🔍 检查OpenVR系统...") - if hasattr(world.vr_manager, 'vr_system') and world.vr_manager.vr_system: - print(" ✓ OpenVR系统已初始化") - else: - print(" ✗ OpenVR系统未初始化") - return False - - if hasattr(world.vr_manager, 'vr_compositor') and world.vr_manager.vr_compositor: - print(" ✓ OpenVR合成器已初始化") - else: - print(" ✗ OpenVR合成器未初始化") - return False - - # 创建简单的测试场景 - print("\n🎨 创建测试场景...") - try: - from panda3d.core import CardMaker, Vec4, AmbientLight, DirectionalLight - - # 添加光照 - ambient_light = AmbientLight('ambient') - ambient_light.setColor(Vec4(0.3, 0.3, 0.3, 1)) - ambient_light_np = world.render.attachNewNode(ambient_light) - world.render.setLight(ambient_light_np) - - directional_light = DirectionalLight('directional') - directional_light.setColor(Vec4(0.8, 0.8, 0.8, 1)) - directional_light.setDirection(Vec4(-1, -1, -1, 0)) - directional_light_np = world.render.attachNewNode(directional_light) - world.render.setLight(directional_light_np) - - # 创建地面网格 - for i in range(-5, 6): - for j in range(-5, 6): - cm = CardMaker(f"grid_{i}_{j}") - cm.setFrame(-0.4, 0.4, -0.4, 0.4) - grid = world.render.attachNewNode(cm.generate()) - grid.setPos(i * 1.0, j * 1.0, -1) - grid.setHpr(0, -90, 0) - - # 棋盘格颜色 - if (i + j) % 2 == 0: - grid.setColor(0.8, 0.8, 0.8, 1.0) - else: - grid.setColor(0.6, 0.6, 0.6, 1.0) - - # 创建一些彩色立方体 - for i in range(8): - cm = CardMaker(f"cube_{i}") - cm.setFrame(-0.3, 0.3, -0.3, 0.3) - cube = world.render.attachNewNode(cm.generate()) - - import math - angle = i * math.pi * 2 / 8 - radius = 3 - cube.setPos(math.cos(angle) * radius, math.sin(angle) * radius, 0.5) - cube.setHpr(i * 45, 0, 0) - cube.setScale(1, 1, 2) - - # 彩虹颜色 - hue = i / 8.0 - r = abs(math.sin(hue * math.pi * 2)) - g = abs(math.sin((hue + 0.33) * math.pi * 2)) - b = abs(math.sin((hue + 0.66) * math.pi * 2)) - cube.setColor(r, g, b, 1.0) - - print("✓ 测试场景创建完成") - - except Exception as e: - print(f"场景创建错误: {str(e)}") - - # 测试直接串流 - print("\n🎮 开始直接VR串流测试...") - print(" 测试将持续15秒,请观察VR头显中的画面") - - start_time = time.time() - frame_count = 0 - - try: - while time.time() - start_time < 15: - # 处理引擎更新 - if hasattr(world, 'taskMgr'): - world.taskMgr.step() - - frame_count += 1 - - # 每秒显示一次状态 - if frame_count % 60 == 0: # 约每秒 - elapsed = time.time() - start_time - fps = frame_count / elapsed - print(f" [{elapsed:.1f}s] FPS: {fps:.1f}") - - time.sleep(0.016) # ~60 FPS - - except KeyboardInterrupt: - print("\n⏹️ 用户中断测试") - - # 关闭系统 - world.shutdownVR() - print("\n✓ VR系统已关闭") - - final_fps = frame_count / (time.time() - start_time) - print(f"✅ 直接VR串流测试完成,平均FPS: {final_fps:.1f}") - - return True - - -def main(): - """主函数""" - print("🎮 直接VR串流测试脚本") - print("="*50) - print("此脚本将测试不通过ALVR,直接使用OpenVR API进行串流的功能") - print("请确保:") - print(" 1. VR设备已连接(如Pico 4通过USB-C连接)") - print(" 2. SteamVR正在运行") - print(" 3. VR设备已被SteamVR识别") - - try: - input("\n按回车键开始测试,或按Ctrl+C退出...") - success = test_direct_vr_streaming() - - print("\n" + "="*50) - if success: - print("✓ 直接VR串流测试成功完成") - print("🎉 您现在已经可以不通过ALVR直接串流到VR设备!") - else: - print("✗ 直接VR串流测试遇到问题") - print("💡 请检查VR设备连接和SteamVR状态") - - except KeyboardInterrupt: - print("\n用户中断测试") - except Exception as e: - print(f"测试过程中发生错误: {str(e)}") - import traceback - traceback.print_exc() - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/main.py b/main.py index ea59bfef..b44df0e8 100644 --- a/main.py +++ b/main.py @@ -15,9 +15,9 @@ from core.selection import SelectionSystem from core.event_handler import EventHandler from core.tool_manager import ToolManager from core.script_system import ScriptManager -from core.vr_manager import VRManager -from core.vr_input_handler import VRInputHandler -from core.alvr_streamer import ALVRStreamer +from core.openxr_manager import OpenXRManager +from core.openxr_input_handler import OpenXRInputHandler +from core.openxr_streamer import OpenXRStreamer from gui.gui_manager import GUIManager from scene.scene_manager import SceneManager from project.project_manager import ProjectManager @@ -41,7 +41,7 @@ import json import datetime from direct.actor.Actor import Actor from PyQt5.sip import wrapinstance -#from RenderPipelineFile.toolkit.material_editor.main import MaterialEditor +# RenderPipelineFile.toolkit.material_editor.main import MaterialEditor class MyWorld(CoreWorld): @@ -75,10 +75,10 @@ class MyWorld(CoreWorld): # 初始化界面管理系统 self.interface_manager = InterfaceManager(self) - # 初始化VR系统 - self.vr_manager = VRManager(self) - self.vr_input_handler = VRInputHandler(self, self.vr_manager) - self.alvr_streamer = ALVRStreamer(self, self.vr_manager) + # 初始化OpenXR系统 + self.openxr_manager = OpenXRManager(self) + self.openxr_input_handler = OpenXRInputHandler(self, self.openxr_manager) + self.openxr_streamer = OpenXRStreamer(self, self.openxr_manager) # 启动脚本系统 self.script_manager.start_system() @@ -444,6 +444,144 @@ class MyWorld(CoreWorld): """根据名称查找模型""" return self.scene_manager.findModelByName(name) + # ==================== OpenXR系统功能代理 ==================== + + # OpenXR系统控制方法 - 代理到openxr_manager + def initializeOpenXR(self): + """初始化OpenXR系统""" + return self.openxr_manager.initialize_openxr() + + def shutdownOpenXR(self): + """关闭OpenXR系统""" + return self.openxr_manager.shutdown_openxr() + + def isOpenXREnabled(self): + """检查OpenXR是否启用""" + return self.openxr_manager.is_openxr_enabled() + + def getOpenXRInfo(self): + """获取OpenXR系统信息""" + return self.openxr_manager.get_openxr_info() + + # OpenXR输入处理方法 - 代理到openxr_input_handler + def startOpenXRInput(self): + """启动OpenXR输入处理""" + return self.openxr_input_handler.start_input_handling() + + def stopOpenXRInput(self): + """停止OpenXR输入处理""" + return self.openxr_input_handler.stop_input_handling() + + def showControllerRays(self, show=True): + """显示/隐藏控制器射线""" + return self.openxr_input_handler.show_controller_rays(show) + + def getControllerState(self, controller_id): + """获取控制器状态""" + return self.openxr_input_handler.get_controller_state(controller_id) + + def getAllControllers(self): + """获取所有控制器""" + return self.openxr_input_handler.get_all_controllers() + + def setVRGestureEnabled(self, enabled): + """设置OpenXR手势识别启用状态""" + return self.openxr_input_handler.set_gesture_enabled(enabled) + + def setVRInteractionEnabled(self, enabled): + """设置OpenXR交互启用状态""" + return self.openxr_input_handler.set_interaction_enabled(enabled) + + # OpenXR串流方法 - 代理到openxr_streamer + def initializeOpenXRStreaming(self): + """初始化OpenXR串流""" + return self.openxr_streamer.initialize() + + def startOpenXRStreaming(self): + """开始OpenXR串流""" + return self.openxr_streamer.start_streaming() + + def stopOpenXRStreaming(self): + """停止OpenXR串流""" + return self.openxr_streamer.stop_streaming() + + def getOpenXRStreamingStatus(self): + """获取OpenXR串流状态""" + return self.openxr_streamer.get_streaming_status() + + def setOpenXRStreamQuality(self, width, height, fps, bitrate): + """设置OpenXR串流质量""" + return self.openxr_streamer.set_stream_quality(width, height, fps, bitrate) + + def sendHapticFeedback(self, controller_id, duration, intensity): + """发送触觉反馈""" + return self.openxr_streamer.send_haptic_feedback(controller_id, duration, intensity) + + def shutdownOpenXRStreaming(self): + """关闭OpenXR串流""" + return self.openxr_streamer.shutdown() + + def isOpenXRConnected(self): + """检查OpenXR是否连接""" + return self.openxr_streamer.is_connected() + + def isOpenXRStreaming(self): + """检查OpenXR是否在串流""" + return self.openxr_streamer.is_streaming() + + # 便捷方法 + def enableOpenXRMode(self): + """启用OpenXR模式(一键启动)""" + print("启用OpenXR模式...") + + # 1. 初始化OpenXR系统 + if not self.initializeOpenXR(): + print("OpenXR系统初始化失败") + return False + + # 2. 启动OpenXR输入处理 + if not self.startOpenXRInput(): + print("OpenXR输入处理启动失败") + return False + + # 3. 初始化OpenXR串流 + if self.initializeOpenXRStreaming(): + print("✓ OpenXR串流已启用") + # 自动开始串流 + self.startOpenXRStreaming() + else: + print("⚠ OpenXR串流启用失败,但OpenXR系统仍可用") + + print("✓ OpenXR模式已启用") + return True + + def disableOpenXRMode(self): + """禁用OpenXR模式(一键关闭)""" + print("禁用OpenXR模式...") + + # 1. 停止OpenXR串流 + self.stopOpenXRStreaming() + self.shutdownOpenXRStreaming() + + # 2. 停止OpenXR输入处理 + self.stopOpenXRInput() + + # 3. 关闭OpenXR系统 + self.shutdownOpenXR() + + print("✓ OpenXR模式已禁用") + + def getOpenXRStatus(self): + """获取OpenXR系统总体状态""" + return { + "openxr_enabled": self.isOpenXREnabled(), + "openxr_info": self.getOpenXRInfo(), + "controllers": self.getAllControllers(), + "openxr_connected": self.isOpenXRConnected(), + "openxr_streaming": self.isOpenXRStreaming(), + "streaming_status": self.getOpenXRStreamingStatus() if self.isOpenXRConnected() else None + } + # ==================== 脚本系统功能代理 ==================== # 脚本系统控制方法 - 代理到script_manager @@ -510,143 +648,6 @@ class MyWorld(CoreWorld): """列出所有脚本信息""" return self.script_manager.list_all_scripts() - # ==================== VR系统功能代理 ==================== - - # VR系统控制方法 - 代理到vr_manager - def initializeVR(self): - """初始化VR系统""" - return self.vr_manager.initialize_vr() - - def shutdownVR(self): - """关闭VR系统""" - return self.vr_manager.shutdown_vr() - - def isVREnabled(self): - """检查VR是否启用""" - return self.vr_manager.is_vr_enabled() - - def getVRInfo(self): - """获取VR系统信息""" - return self.vr_manager.get_vr_info() - - # VR输入处理方法 - 代理到vr_input_handler - def startVRInput(self): - """启动VR输入处理""" - return self.vr_input_handler.start_input_handling() - - def stopVRInput(self): - """停止VR输入处理""" - return self.vr_input_handler.stop_input_handling() - - def showControllerRays(self, show=True): - """显示/隐藏控制器射线""" - return self.vr_input_handler.show_controller_rays(show) - - def getControllerState(self, controller_id): - """获取控制器状态""" - return self.vr_input_handler.get_controller_state(controller_id) - - def getAllControllers(self): - """获取所有控制器""" - return self.vr_input_handler.get_all_controllers() - - def setVRGestureEnabled(self, enabled): - """设置VR手势识别启用状态""" - return self.vr_input_handler.set_gesture_enabled(enabled) - - def setVRInteractionEnabled(self, enabled): - """设置VR交互启用状态""" - return self.vr_input_handler.set_interaction_enabled(enabled) - - # ALVR串流方法 - 代理到alvr_streamer - def initializeALVR(self): - """初始化ALVR串流""" - return self.alvr_streamer.initialize() - - def startALVRStreaming(self): - """开始ALVR串流""" - return self.alvr_streamer.start_streaming() - - def stopALVRStreaming(self): - """停止ALVR串流""" - return self.alvr_streamer.stop_streaming() - - def getALVRStreamingStatus(self): - """获取ALVR串流状态""" - return self.alvr_streamer.get_streaming_status() - - def setALVRStreamQuality(self, width, height, fps, bitrate): - """设置ALVR串流质量""" - return self.alvr_streamer.set_stream_quality(width, height, fps, bitrate) - - def sendHapticFeedback(self, controller_id, duration, intensity): - """发送触觉反馈""" - return self.alvr_streamer.send_haptic_feedback(controller_id, duration, intensity) - - def shutdownALVR(self): - """关闭ALVR串流""" - return self.alvr_streamer.shutdown() - - def isALVRConnected(self): - """检查ALVR是否连接""" - return self.alvr_streamer.is_connected() - - def isALVRStreaming(self): - """检查ALVR是否在串流""" - return self.alvr_streamer.is_streaming() - - # 便捷方法 - def enableVRMode(self): - """启用VR模式(一键启动)""" - print("启用VR模式...") - - # 1. 初始化VR系统 - if not self.initializeVR(): - print("VR系统初始化失败") - return False - - # 2. 启动VR输入处理 - if not self.startVRInput(): - print("VR输入处理启动失败") - return False - - # 3. 初始化ALVR串流 - if self.initializeALVR(): - print("✓ ALVR串流已启用") - # 自动开始串流 - self.startALVRStreaming() - else: - print("⚠ ALVR串流启用失败,但VR系统仍可用") - - print("✓ VR模式已启用") - return True - - def disableVRMode(self): - """禁用VR模式(一键关闭)""" - print("禁用VR模式...") - - # 1. 停止ALVR串流 - self.stopALVRStreaming() - self.shutdownALVR() - - # 2. 停止VR输入处理 - self.stopVRInput() - - # 3. 关闭VR系统 - self.shutdownVR() - - print("✓ VR模式已禁用") - - def getVRStatus(self): - """获取VR系统总体状态""" - return { - "vr_enabled": self.isVREnabled(), - "vr_info": self.getVRInfo(), - "controllers": self.getAllControllers(), - "alvr_connected": self.isALVRConnected(), - "alvr_streaming": self.isALVRStreaming(), - "streaming_status": self.getALVRStreamingStatus() if self.isALVRConnected() else None - } # ==================== 项目管理功能代理 ==================== @@ -681,13 +682,13 @@ def buildPackage(appw): # def open_material_editor(self): # """打开材质编辑器作为子窗口""" # if not self.material_editor: -# self.material_editor = MaterialEditor() -# # 设置为子窗口 -# self.material_editor.setParent(self) -# self.material_editor.setWindowFlags(Qt.Window) -# -# self.material_editor.show() -# +# # self.material_editor = MaterialEditor() +# # # 设置为子窗口 +# # self.material_editor.setParent(self) +# # self.material_editor.setWindowFlags(Qt.Window) +# # +# # self.material_editor.show() +# # if __name__ == "__main__": world = MyWorld() diff --git a/openxr_test.py b/openxr_test.py new file mode 100755 index 00000000..8f33dd1a --- /dev/null +++ b/openxr_test.py @@ -0,0 +1,480 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +OpenXR测试脚本 + +用于测试OpenXR功能的基本实现 +""" + +import sys +import os +import time + +# 添加项目根目录到Python路径 +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from main import MyWorld +from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QHBoxLayout, QLabel +from PyQt5.QtGui import QPixmap, QImage +from PyQt5.QtCore import QTimer +import numpy as np + + +def test_openxr_basic_display(world): + """测试基本OpenXR显示功能""" + print("=== OpenXR基本显示测试 ===") + + # 加载一些模型来展示 + print("📦 加载演示场景...") + try: + # 创建一些简单的几何体作为演示 + from panda3d.core import CardMaker, Vec4 + + # 创建地面 + cm = CardMaker("ground") + cm.setFrame(-10, 10, -10, 10) + ground = world.render.attachNewNode(cm.generate()) + ground.setPos(0, 0, -1) + ground.setColor(0.5, 0.8, 0.5, 1.0) + ground.setHpr(0, -90, 0) + + # 创建一些立方体 + for i in range(5): + for j in range(5): + cube = world.loader.loadModel("models/environment") + if not cube: + # 如果没有environment模型,创建简单的立方体 + cm_cube = CardMaker(f"cube_{i}_{j}") + cm_cube.setFrame(-0.5, 0.5, -0.5, 0.5) + cube = world.render.attachNewNode(cm_cube.generate()) + + cube.setPos(i * 2 - 4, j * 2 - 4, 0) + cube.setScale(0.5) + cube.setColor(i/5.0, j/5.0, 1.0, 1.0) + + print("✓ 演示场景加载完成") + + except Exception as e: + print(f"场景加载错误: {str(e)}") + + # 初始化OpenXR系统 + print("\n🥽 初始化OpenXR显示系统...") + openxr_success = world.initializeOpenXR() + + if openxr_success: + print("✓ OpenXR系统初始化成功") + openxr_info = world.getOpenXRInfo() + + print(f"\n📊 OpenXR显示信息:") + print(f" - 模式: {openxr_info['mode']}") + print(f" - 渲染尺寸: {openxr_info['render_size']}") + print(f" - 模拟模式: {openxr_info['simulation_mode']}") + print(f" - 有线连接: {openxr_info['wired_connection']}") + + if openxr_info['simulation_mode']: + print("\n💡 模拟模式说明:") + print(" - 左右眼视图将并排显示在主窗口") + print(" - 可以看到立体渲染效果") + print(" - 头部会有轻微的模拟摆动") + else: + print("\n💡 真实OpenXR模式:") + print(" - 画面将渲染到VR头盔") + print(" - 支持真实的头部追踪") + print(" - 控制器交互可用") + + # 创建显示窗口 + app = QApplication.instance() + if app is None: + app = QApplication(sys.argv) + + # 创建主窗口显示OpenXR渲染结果 + window = QMainWindow() + window.setWindowTitle("OpenXR Stereo Rendering Preview") + window.setGeometry(100, 100, 800, 300) + + central_widget = QWidget() + layout = QHBoxLayout() + central_widget.setLayout(layout) + + left_label = QLabel("Left Eye") + right_label = QLabel("Right Eye") + layout.addWidget(left_label) + layout.addWidget(right_label) + + window.setCentralWidget(central_widget) + window.show() + + # 定时更新显示 + def update_display(): + if hasattr(world.openxr_manager, 'left_eye_texture') and world.openxr_manager.left_eye_texture: + # 获取左眼纹理数据 + tex = world.openxr_manager.left_eye_texture + try: + if tex.get_x_size() > 0 and tex.get_y_size() > 0: + # 添加对纹理数据有效性的检查 + if tex.might_have_ram_image(): + # 使用正确的API将纹理数据复制到QImage + data = tex.get_ram_image().get_data() + if data: + img = QImage(data, tex.get_x_size(), tex.get_y_size(), QImage.Format_RGBA8888) + pixmap = QPixmap.fromImage(img) + left_label.setPixmap(pixmap.scaled(400, 300)) + left_label.setText("") # 清除文字,只显示图像 + else: + left_label.setText("Left Eye (No Data)") + else: + left_label.setText("Left Eye (No RAM Image)") + else: + left_label.setText("Left Eye (Invalid Size)") + except Exception as e: + print(f"左眼纹理更新错误: {str(e)}") + left_label.setText("Left Eye (Error)") + else: + left_label.setText("Left Eye (No Texture)") + + if hasattr(world.openxr_manager, 'right_eye_texture') and world.openxr_manager.right_eye_texture: + # 获取右眼纹理数据 + tex = world.openxr_manager.right_eye_texture + try: + if tex.get_x_size() > 0 and tex.get_y_size() > 0: + # 添加对纹理数据有效性的检查 + if tex.might_have_ram_image(): + # 使用正确的API将纹理数据复制到QImage + data = tex.get_ram_image().get_data() + if data: + img = QImage(data, tex.get_x_size(), tex.get_y_size(), QImage.Format_RGBA8888) + pixmap = QPixmap.fromImage(img) + right_label.setPixmap(pixmap.scaled(400, 300)) + right_label.setText("") # 清除文字,只显示图像 + else: + right_label.setText("Right Eye (No Data)") + else: + right_label.setText("Right Eye (No RAM Image)") + else: + right_label.setText("Right Eye (Invalid Size)") + except Exception as e: + print(f"右眼纹理更新错误: {str(e)}") + right_label.setText("Right Eye (Error)") + else: + right_label.setText("Right Eye (No Texture)") + + timer = QTimer() + timer.timeout.connect(update_display) + timer.start(33) # 约30 FPS + + # 运行几秒钟以展示效果 + print(f"\n🎮 OpenXR显示测试运行中... (10秒)") + print(" 观察主窗口中的立体渲染效果") + + start_time = time.time() + while time.time() - start_time < 10: + # 处理Panda3D事件 + if hasattr(world, 'taskMgr'): + world.taskMgr.step() + + # 处理Qt事件 + try: + app.processEvents() + except Exception as e: + print(f"Qt事件处理错误: {str(e)}") + + # 添加短暂延迟以避免过度占用CPU + time.sleep(0.016) # ~60 FPS + + print("✓ OpenXR显示测试完成") + + else: + print("✗ OpenXR系统初始化失败") + return False + + # 关闭OpenXR系统 + world.shutdownOpenXR() + print("\n✓ OpenXR系统已关闭") + + return True + + +def test_openxr_streaming(world): + """测试OpenXR串流功能""" + print("=== OpenXR串流测试 ===") + + # 初始化OpenXR系统 + print("初始化OpenXR系统...") + if not world.initializeOpenXR(): + print("✗ OpenXR系统初始化失败") + return False + + # 启动输入处理 + print("启动OpenXR输入处理...") + if not world.startOpenXRInput(): + print("⚠ OpenXR输入处理启动失败") + + # 初始化串流 + print("初始化OpenXR串流...") + if world.initializeOpenXRStreaming(): + print("✓ OpenXR串流初始化成功") + + # 开始串流 + print("开始OpenXR串流...") + if world.startOpenXRStreaming(): + print("✓ OpenXR串流已开始") + + # 创建显示窗口 + app = QApplication.instance() + if app is None: + app = QApplication(sys.argv) + + # 创建主窗口显示OpenXR渲染结果 + window = QMainWindow() + window.setWindowTitle("OpenXR Streaming Preview") + window.setGeometry(100, 100, 800, 300) + + central_widget = QWidget() + layout = QHBoxLayout() + central_widget.setLayout(layout) + + left_label = QLabel("Left Eye") + right_label = QLabel("Right Eye") + layout.addWidget(left_label) + layout.addWidget(right_label) + + window.setCentralWidget(central_widget) + window.show() + + # 定时更新显示 + def update_display(): + try: + # 更新左眼纹理 + if hasattr(world.openxr_manager, 'left_eye_texture') and world.openxr_manager.left_eye_texture: + tex = world.openxr_manager.left_eye_texture + # 检查纹理尺寸和数据有效性 + if tex.get_x_size() > 0 and tex.get_y_size() > 0 and tex.might_have_ram_image(): + ram_image = tex.get_ram_image() + if ram_image and ram_image.get_data(): + # 使用正确的数据访问方法 + img = QImage(ram_image.get_data(), tex.get_x_size(), tex.get_y_size(), QImage.Format_RGBA8888) + pixmap = QPixmap.fromImage(img) + left_label.setPixmap(pixmap.scaled(400, 300)) + left_label.setText("") # 清除文字,只显示图像 + + # 更新右眼纹理 + if hasattr(world.openxr_manager, 'right_eye_texture') and world.openxr_manager.right_eye_texture: + tex = world.openxr_manager.right_eye_texture + # 检查纹理尺寸和数据有效性 + if tex.get_x_size() > 0 and tex.get_y_size() > 0 and tex.might_have_ram_image(): + ram_image = tex.get_ram_image() + if ram_image and ram_image.get_data(): + # 使用正确的数据访问方法 + img = QImage(ram_image.get_data(), tex.get_x_size(), tex.get_y_size(), QImage.Format_RGBA8888) + pixmap = QPixmap.fromImage(img) + right_label.setPixmap(pixmap.scaled(400, 300)) + right_label.setText("") # 清除文字,只显示图像 + + except Exception as e: + print(f"纹理更新错误: {str(e)}") + # 清除可能已损坏的纹理引用 + if hasattr(world.openxr_manager, 'left_eye_texture'): + world.openxr_manager.left_eye_texture = None + if hasattr(world.openxr_manager, 'right_eye_texture'): + world.openxr_manager.right_eye_texture = None + + timer = QTimer() + timer.timeout.connect(update_display) + timer.start(33) # 约30 FPS + + # 运行几秒钟 + print("OpenXR串流运行中... (5秒)") + start_time = time.time() + while time.time() - start_time < 5: + # 处理Panda3D事件 + if hasattr(world, 'taskMgr'): + world.taskMgr.step() + + # 处理Qt事件 + try: + app.processEvents() + except Exception as e: + print(f"Qt事件处理错误: {str(e)}") + + time.sleep(0.016) # ~60 FPS + + # 停止串流 + world.stopOpenXRStreaming() + print("✓ OpenXR串流已停止") + else: + print("✗ OpenXR串流启动失败") + else: + print("✗ OpenXR串流初始化失败") + + # 关闭系统 + world.shutdownOpenXRStreaming() + world.stopOpenXRInput() + world.shutdownOpenXR() + + print("✓ OpenXR串流测试完成") + return True + + +def test_openxr_full(world): + """测试完整OpenXR功能""" + print("=== 完整OpenXR功能测试 ===") + + # 初始化OpenXR系统 + print("初始化OpenXR系统...") + if not world.initializeOpenXR(): + print("✗ OpenXR系统初始化失败") + return False + + # 创建显示窗口 + app = QApplication.instance() + if app is None: + app = QApplication(sys.argv) + + # 创建主窗口显示OpenXR渲染结果 + window = QMainWindow() + window.setWindowTitle("OpenXR Full Functionality Preview") + window.setGeometry(100, 100, 800, 300) + + central_widget = QWidget() + layout = QHBoxLayout() + central_widget.setLayout(layout) + + left_label = QLabel("Left Eye") + right_label = QLabel("Right Eye") + layout.addWidget(left_label) + layout.addWidget(right_label) + + window.setCentralWidget(central_widget) + window.show() + + # 定时更新显示 + def update_display(): + try: + # 更新左眼纹理 + if hasattr(world.openxr_manager, 'left_eye_texture') and world.openxr_manager.left_eye_texture: + tex = world.openxr_manager.left_eye_texture + # 检查纹理尺寸和数据有效性 + if tex.get_x_size() > 0 and tex.get_y_size() > 0 and tex.might_have_ram_image(): + ram_image = tex.get_ram_image() + if ram_image and ram_image.get_data(): + # 使用正确的数据访问方法 + img = QImage(ram_image.get_data(), tex.get_x_size(), tex.get_y_size(), QImage.Format_RGBA8888) + pixmap = QPixmap.fromImage(img) + left_label.setPixmap(pixmap.scaled(400, 300)) + left_label.setText("") # 清除文字,只显示图像 + + # 更新右眼纹理 + if hasattr(world.openxr_manager, 'right_eye_texture') and world.openxr_manager.right_eye_texture: + tex = world.openxr_manager.right_eye_texture + # 检查纹理尺寸和数据有效性 + if tex.get_x_size() > 0 and tex.get_y_size() > 0 and tex.might_have_ram_image(): + ram_image = tex.get_ram_image() + if ram_image and ram_image.get_data(): + # 使用正确的数据访问方法 + img = QImage(ram_image.get_data(), tex.get_x_size(), tex.get_y_size(), QImage.Format_RGBA8888) + pixmap = QPixmap.fromImage(img) + right_label.setPixmap(pixmap.scaled(400, 300)) + right_label.setText("") # 清除文字,只显示图像 + + except Exception as e: + print(f"纹理更新错误: {str(e)}") + # 清除可能已损坏的纹理引用 + if hasattr(world.openxr_manager, 'left_eye_texture'): + world.openxr_manager.left_eye_texture = None + if hasattr(world.openxr_manager, 'right_eye_texture'): + world.openxr_manager.right_eye_texture = None + + timer = QTimer() + timer.timeout.connect(update_display) + timer.start(33) # 约30 FPS + + # 一键启用OpenXR模式 + print("启用OpenXR模式...") + if world.enableOpenXRMode(): + print("✓ OpenXR模式已启用") + + # 启动输入处理 + print("启动OpenXR输入处理...") + if not world.startOpenXRInput(): + print("⚠ OpenXR输入处理启动失败") + + # 获取状态 + openxr_info = world.getOpenXRInfo() + print(f"\n📊 OpenXR状态:") + print(f" - 启用: {openxr_info['enabled']}") + print(f" - 模式: {openxr_info['mode']}") + print(f" - 模拟模式: {openxr_info['simulation_mode']}") + print(f" - 渲染尺寸: {openxr_info['render_size']}") + print(f" - 有线连接: {openxr_info['wired_connection']}") + + # 获取状态 + status = world.getOpenXRStatus() + print(f"\n📊 OpenXR状态:") + print(f" - 启用: {status['openxr_enabled']}") + print(f" - 连接: {status['openxr_connected']}") + print(f" - 串流: {status['openxr_streaming']}") + print(f" - 控制器: {len(status['controllers'])}个") + + if status['streaming_status']: + streaming_status = status['streaming_status'] + print(f"\n📡 串流状态:") + print(f" - FPS: {streaming_status['fps']}") + print(f" - 分辨率: {streaming_status['resolution']}") + print(f" - 延迟: {streaming_status['latency']}ms") + + # 运行一段时间 + print(f"\n🎮 OpenXR完整功能运行中... (15秒)") + start_time = time.time() + while time.time() - start_time < 15: + # 处理Panda3D事件 + if hasattr(world, 'taskMgr'): + world.taskMgr.step() + + # 处理Qt事件 + try: + app.processEvents() + except Exception as e: + print(f"Qt事件处理错误: {str(e)}") + + time.sleep(0.016) # ~60 FPS + + # 禁用OpenXR模式 + world.shutdownOpenXR() + print("✓ OpenXR模式已禁用") + + else: + print("✗ OpenXR模式启用失败") + return False + + # 关闭系统 + world.stopOpenXRInput() + world.shutdownOpenXR() + + print("✓ 完整OpenXR功能测试完成") + return True + + +if __name__ == "__main__": + print("OpenXR功能测试") + print("===============") + + # 创建世界实例(只创建一次) + world = MyWorld() + + # 运行测试 + print("1. 基本显示测试") + test_openxr_basic_display(world) + + print("\n" + "="*50 + "\n") + + print("2. 串流功能测试") + test_openxr_streaming(world) + + print("\n" + "="*50 + "\n") + + print("3. 完整功能测试") + test_openxr_full(world) + + print("\n" + "="*50) + print("所有测试完成!") \ No newline at end of file diff --git a/requirements/openxr-requirements.txt b/requirements/openxr-requirements.txt new file mode 100644 index 00000000..ae00b4f4 --- /dev/null +++ b/requirements/openxr-requirements.txt @@ -0,0 +1,23 @@ +# OpenXR 相关依赖 +# 注意:OpenXR Python绑定可能需要从源码编译 + +# 基础依赖 +numpy>=1.18.0 +PyOpenGL>=3.1.0 + +# Panda3D(如果尚未安装) +panda3d>=1.10.10 + +# 网络和串流相关 +websockets>=8.0 +aiohttp>=3.7.0 + +# 图像处理(用于视频编码/解码) +Pillow>=8.0.0 +opencv-python>=4.5.0 + +# 音频处理(可选) +pyaudio>=0.2.11 + +# 注:实际的OpenXR Python绑定需要根据平台和设备单独安装 +# 可能需要从 https://github.com/KhronosGroup/OpenXR-SDK-Source 编译 \ No newline at end of file diff --git a/test_openxr_simple.py b/test_openxr_simple.py new file mode 100644 index 00000000..16558c09 --- /dev/null +++ b/test_openxr_simple.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +简单的OpenXR测试脚本 + +在主程序基础上直接添加OpenXR相关代码进行测试 +""" + +import sys +import os + +# 添加项目根目录到Python路径 +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from main import MyWorld +from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QHBoxLayout, QLabel +from PyQt5.QtGui import QPixmap, QImage +from PyQt5.QtCore import QTimer + +# Panda3D imports +from panda3d.core import Texture, GraphicsOutput, ATSNone, GraphicsPipe, Vec3 + + +def setup_openxr_simulation(world): + """在主程序基础上设置简单的OpenXR模拟""" + print("🔧 设置OpenXR模拟环境...") + + # 获取渲染尺寸 + render_width = 1280 + render_height = 720 + + # 创建左右眼渲染缓冲区 + from panda3d.core import WindowProperties, FrameBufferProperties, GraphicsPipe + + # 创建窗口属性 + winprops = WindowProperties() + winprops.setSize(render_width//2, render_height) + + # 创建帧缓冲区属性 + fbprops = FrameBufferProperties() + fbprops.setRgbColor(True) + fbprops.setRgbaBits(8, 8, 8, 8) + fbprops.setDepthBits(24) + + # 设置渲染标志和GSG + flags = GraphicsPipe.BFRefuseWindow + gsg = world.win.getGsg() + + left_eye_buffer = world.graphicsEngine.makeOutput( + world.pipe, + "left_eye", + -1000, + fbprops, + winprops, + flags, + gsg, + world.win + ) + + right_eye_buffer = world.graphicsEngine.makeOutput( + world.pipe, + "right_eye", + -1000, + fbprops, + winprops, + flags, + gsg, + world.win + ) + + if not left_eye_buffer or not right_eye_buffer: + print("❌ 无法创建眼部渲染缓冲区") + return None, None + + # 创建用于显示的左右眼纹理,使用与主程序相同的方式 + left_eye_texture = Texture("left_eye_texture") + right_eye_texture = Texture("right_eye_texture") + left_eye_texture.set_format(Texture.F_rgba8) # 使用主程序的设置方式 + right_eye_texture.set_format(Texture.F_rgba8) + left_eye_texture.set_component_type(Texture.T_unsigned_byte) + right_eye_texture.set_component_type(Texture.T_unsigned_byte) + + # 将纹理附加到渲染缓冲区,使用与主程序相同的方式 + left_eye_buffer.add_render_texture(left_eye_texture, GraphicsOutput.RTM_copy_ram) + right_eye_buffer.add_render_texture(right_eye_texture, GraphicsOutput.RTM_copy_ram) + + # 创建左右眼相机 + left_eye_camera = world.makeCamera(left_eye_buffer) + right_eye_camera = world.makeCamera(right_eye_buffer) + + # 设置相机参数,确保与主相机一致 + left_eye_camera.node().get_lens().set_fov(world.camLens.get_fov()) + right_eye_camera.node().get_lens().set_fov(world.camLens.get_fov()) + left_eye_camera.node().get_lens().set_near(world.camLens.get_near()) + right_eye_camera.node().get_lens().set_near(world.camLens.get_near()) + left_eye_camera.node().get_lens().set_far(world.camLens.get_far()) + right_eye_camera.node().get_lens().set_far(world.camLens.get_far()) + + # 设置相机位置(模拟瞳距) + left_eye_camera.setPos(world.cam.getPos() + Vec3(-0.03, 0, 0)) # 瞳距的一半 + right_eye_camera.setPos(world.cam.getPos() + Vec3(0.03, 0, 0)) # 瞳距的一半 + + # 设置相机朝向,确保与主相机一致 + left_eye_camera.setHpr(world.cam.getHpr()) + right_eye_camera.setHpr(world.cam.getHpr()) + + # 设置相机渲染场景 + left_eye_camera.node().setScene(world.render) + right_eye_camera.node().setScene(world.render) + + # 获取渲染管线的最终输出纹理 + def get_final_render_texture(): + """获取渲染管线的最终输出纹理""" + # 修复:正确获取最终渲染纹理的方法 + if world.win: + # 直接从主窗口获取最终渲染纹理 + return world.win.get_texture() + return None + + # 等待并获取渲染结果 + def wait_for_render_texture(): + """等待并获取有效的渲染纹理""" + timeout = 100 # 超时帧数 + for i in range(timeout): + final_texture = get_final_render_texture() + if final_texture and final_texture.hasRamImage(): + return final_texture + world.graphicsEngine.renderFrame() + return None + + # 获取最终渲染纹理作为左右眼纹理的基础 + final_texture = wait_for_render_texture() + if final_texture: + # 使用与主渲染管线相同的纹理参数 + left_eye_texture = final_texture.makeCopy() + right_eye_texture = final_texture.makeCopy() + + # 确保纹理数据可用 + world.graphicsEngine.extractTextureData(left_eye_texture, world.win.getGsg()) + world.graphicsEngine.extractTextureData(right_eye_texture, world.win.getGsg()) + + print("✅ 成功获取渲染管线的最终输出纹理") + else: + print("⚠️ 无法获取渲染管线的最终输出纹理,使用默认设置") + + print("✅ OpenXR模拟环境设置完成") + return left_eye_texture, right_eye_texture + + +def main(): + """主函数""" + print("🚀 启动OpenXR简单测试...") + + # 创建应用程序和世界实例 + app = QApplication(sys.argv) + world = MyWorld() + + # 加载一些测试模型 + print("📦 加载测试场景...") + from panda3d.core import CardMaker + + # 创建地面 + cm = CardMaker("ground") + cm.setFrame(-5, 5, -5, 5) + ground = world.render.attachNewNode(cm.generate()) + ground.setPos(0, 0, -1) + ground.setColor(0.5, 0.8, 0.5, 1.0) + ground.setHpr(0, -90, 0) + + # 创建测试立方体 + cube = world.loader.loadModel("models/environment") + if cube: + cube.reparentTo(world.render) + cube.setScale(0.5) + cube.setPos(0, 0, 0) + else: + # 如果没有环境模型,创建简单立方体 + cm_cube = CardMaker("cube") + cm_cube.setFrame(-0.5, 0.5, -0.5, 0.5) + cube = world.render.attachNewNode(cm_cube.generate()) + cube.setPos(0, 0, 0) + + # 设置OpenXR模拟 + left_tex, right_tex = setup_openxr_simulation(world) + + if not left_tex or not right_tex: + print("❌ OpenXR模拟设置失败") + return + + # 创建显示窗口 + window = QMainWindow() + window.setWindowTitle("OpenXR Simple Test") + window.setGeometry(100, 100, 800, 300) + + central_widget = QWidget() + layout = QHBoxLayout() + central_widget.setLayout(layout) + + left_label = QLabel("Left Eye") + right_label = QLabel("Right Eye") + layout.addWidget(left_label) + layout.addWidget(right_label) + + window.setCentralWidget(central_widget) + window.show() + + def update_display(): + """更新显示""" + try: + # 让RenderPipeline进行渲染 + world.taskMgr.step() + + # 渲染几帧以确保纹理更新 + for _ in range(5): + world.graphicsEngine.renderFrame() + + # 更新左眼纹理 + if left_tex: + # 检查纹理是否具有RAM图像 + if not left_tex.hasRamImage(): + # 如果没有RAM图像,尝试提取 + gsg = world.win.getGsg() + if gsg: + world.graphicsEngine.extractTextureData(left_tex, gsg) + + # 现在检查是否有RAM图像和有效数据 + if left_tex.hasRamImage(): + data = left_tex.getRamImage().getData() + width = left_tex.getXSize() + height = left_tex.getYSize() + expected_len = width * height * 4 + + # 检查数据长度是否正确 + if len(data) == expected_len: + # 使用与主程序相同的方式创建图像 + img = QImage(data, width, height, QImage.Format_ARGB32).mirrored() + pixmap = QPixmap.fromImage(img) + left_label.setPixmap(pixmap.scaled(400, 300)) + left_label.setText("") + + # 更新右眼纹理 + if right_tex: + # 检查纹理是否具有RAM图像 + if not right_tex.hasRamImage(): + # 如果没有RAM图像,尝试提取 + gsg = world.win.getGsg() + if gsg: + world.graphicsEngine.extractTextureData(right_tex, gsg) + + # 现在检查是否有RAM图像和有效数据 + if right_tex.hasRamImage(): + data = right_tex.getRamImage().getData() + width = right_tex.getXSize() + height = right_tex.getYSize() + expected_len = width * height * 4 + + # 检查数据长度是否正确 + if len(data) == expected_len: + # 使用与主程序相同的方式创建图像 + img = QImage(data, width, height, QImage.Format_ARGB32).mirrored() + pixmap = QPixmap.fromImage(img) + right_label.setPixmap(pixmap.scaled(400, 300)) + right_label.setText("") + except Exception as e: + print(f"纹理更新错误: {str(e)}") + + # 定时更新显示 + timer = QTimer() + timer.timeout.connect(update_display) + timer.start(33) # 约30 FPS + + # 运行应用程序 + print("✅ OpenXR简单测试运行中...") + sys.exit(app.exec_()) + + +if __name__ == "__main__": + main() \ No newline at end of file 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/__init__.py b/vr/__init__.py deleted file mode 100644 index e2f59ea8..00000000 --- a/vr/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -VR系统模块 - 提供VR设备集成和画面串流功能 - -此模块提供了一个完整的VR系统实现,包括: -- VR设备初始化和管理 -- 立体渲染和显示 -- VR输入处理 -- 画面串流到VR设备 -""" - -from .vr_system import VRSystem \ No newline at end of file diff --git a/vr/devices/__init__.py b/vr/devices/__init__.py deleted file mode 100644 index e76b3ae1..00000000 --- a/vr/devices/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -VR设备特定实现模块 - -此模块包含针对不同VR设备的特定实现: -- OpenXR设备 -- Oculus设备 -- SteamVR设备 -""" \ No newline at end of file diff --git a/vr/vr_display.py b/vr/vr_display.py deleted file mode 100644 index d33d0498..00000000 --- a/vr/vr_display.py +++ /dev/null @@ -1,379 +0,0 @@ -""" -VR显示管理模块 - -此模块负责VR立体渲染和显示,包括: -- 创建左右眼相机 -- 设置渲染目标 -- 应用畸变校正 -- 管理渲染分辨率和质量 -""" - -from panda3d.core import ( - NodePath, Camera, PerspectiveLens, - FrameBufferProperties, WindowProperties, - GraphicsOutput, GraphicsEngine, - Texture, GraphicsBuffer, Vec3, - RenderState, ColorWriteAttrib, ShaderAttrib -) -from direct.filter.FilterManager import FilterManager - -class VRDisplay: - """管理VR显示和渲染""" - - def __init__(self, vr_system): - """ - 初始化VR显示管理器 - - Args: - vr_system: VR系统实例 - """ - self.vr_system = vr_system - self.world = vr_system.world - self.device = vr_system.device - - # 渲染目标 - self.left_buffer = None - self.right_buffer = None - self.left_texture = None - self.right_texture = None - - # 相机 - self.left_camera = None - self.right_camera = None - self.left_cam_node = None - self.right_cam_node = None - - # 畸变校正 - self.distortion_manager = None - self.is_initialized = False - - # 渲染设置 - self.render_resolution = vr_system.config.get("render_resolution", [2048, 2048]) - self.ipd = vr_system.config.get("ipd", 0.064) # 默认瞳距64mm - - def initialize(self): - """初始化VR显示系统""" - print("正在初始化VR显示系统...") - - # 1. 创建渲染目标 - if not self._create_render_targets(): - print("✗ 创建VR渲染目标失败") - return False - - # 2. 创建立体相机 - if not self._setup_stereo_cameras(): - print("✗ 设置VR立体相机失败") - return False - - # 3. 设置畸变校正 - if not self._setup_distortion_correction(): - print("✗ 设置畸变校正失败") - # 这不是致命错误,可以继续 - print("⚠ 将使用未校正的渲染") - - self.is_initialized = True - print("✓ VR显示系统初始化完成") - return True - - def _create_render_targets(self): - """创建VR渲染目标""" - try: - # 获取图形引擎和窗口 - graphics_engine = self.world.graphicsEngine - main_window = self.world.win - - # 创建帧缓冲属性 - fb_props = FrameBufferProperties() - fb_props.setRgbColor(True) - fb_props.setRgbaBits(8, 8, 8, 8) - fb_props.setDepthBits(24) - fb_props.setMultisamples(4) # MSAA 4x - - # 创建窗口属性 - win_props = WindowProperties() - win_props.setSize(self.render_resolution[0], self.render_resolution[1]) - - # 创建左眼缓冲区 - self.left_buffer = graphics_engine.makeOutput( - main_window.getPipe(), "left_eye_buffer", - -1, fb_props, win_props, - GraphicsOutput.BF_refuse_window, - main_window.getGsg(), main_window - ) - - if not self.left_buffer: - print("✗ 创建左眼缓冲区失败") - return False - - self.left_buffer.setClearColor((0, 0, 0, 1)) - self.left_texture = Texture() - self.left_buffer.addRenderTexture( - self.left_texture, - GraphicsOutput.RTM_copy_ram, - GraphicsOutput.RTP_color - ) - - # 创建右眼缓冲区 - self.right_buffer = graphics_engine.makeOutput( - main_window.getPipe(), "right_eye_buffer", - -1, fb_props, win_props, - GraphicsOutput.BF_refuse_window, - main_window.getGsg(), main_window - ) - - if not self.right_buffer: - print("✗ 创建右眼缓冲区失败") - return False - - self.right_buffer.setClearColor((0, 0, 0, 1)) - self.right_texture = Texture() - self.right_buffer.addRenderTexture( - self.right_texture, - GraphicsOutput.RTM_copy_ram, - GraphicsOutput.RTP_color - ) - - print("✓ VR渲染目标创建成功") - return True - except Exception as e: - print(f"创建VR渲染目标时出错: {e}") - return False - - def _setup_stereo_cameras(self): - """设置立体相机""" - try: - # 获取主相机信息 - main_cam = self.world.camera - main_cam_pos = main_cam.getPos() - main_cam_hpr = main_cam.getHpr() - - # 创建左眼相机 - self.left_cam_node = Camera("left_eye_camera") - left_lens = PerspectiveLens() - left_lens.setFov(90) # 90度视场角 - left_lens.setNearFar(0.01, 1000) - self.left_cam_node.setLens(left_lens) - self.left_camera = NodePath(self.left_cam_node) - self.left_camera.reparentTo(main_cam) - - # 设置左眼相机位置(向左偏移半个瞳距) - self.left_camera.setPos(-self.ipd/2, 0, 0) - - # 创建右眼相机 - self.right_cam_node = Camera("right_eye_camera") - right_lens = PerspectiveLens() - right_lens.setFov(90) # 90度视场角 - right_lens.setNearFar(0.01, 1000) - self.right_cam_node.setLens(right_lens) - self.right_camera = NodePath(self.right_cam_node) - self.right_camera.reparentTo(main_cam) - - # 设置右眼相机位置(向右偏移半个瞳距) - self.right_camera.setPos(self.ipd/2, 0, 0) - - # 设置相机显示区域 - self.left_cam_node.setScene(self.world.render) - self.right_cam_node.setScene(self.world.render) - - # 连接相机到缓冲区 - self.left_buffer.setSort(0) - self.left_buffer.setClearActive(True) - self.left_buffer.setActive(True) - self.left_buffer.addDisplayRegion().setCamera(self.left_camera) - - self.right_buffer.setSort(0) - self.right_buffer.setClearActive(True) - self.right_buffer.setActive(True) - self.right_buffer.addDisplayRegion().setCamera(self.right_camera) - - print("✓ VR立体相机设置成功") - return True - except Exception as e: - print(f"设置VR立体相机时出错: {e}") - return False - - def _setup_distortion_correction(self): - """设置畸变校正""" - try: - # 如果设备提供了畸变校正参数 - if self.device and hasattr(self.device, 'get_distortion_parameters'): - distortion_params = self.device.get_distortion_parameters() - - # 创建畸变校正着色器 - # 这里使用FilterManager来应用后处理效果 - self.distortion_manager = FilterManager(self.world.win, self.world.cam) - - # 应用畸变校正着色器到左右眼纹理 - # 具体实现取决于设备提供的畸变参数格式 - - print("✓ VR畸变校正设置成功") - return True - else: - print("⚠ 设备未提供畸变校正参数") - return False - except Exception as e: - print(f"设置畸变校正时出错: {e}") - return False - - def update(self): - """更新VR显示(每帧调用)""" - if not self.is_initialized: - return - - # 1. 获取头部姿态 - if self.device: - head_pose = self.device.get_head_pose() - if head_pose: - self.update_cameras(head_pose) - - # 2. 如果使用动态分辨率,根据性能调整 - if self.vr_system.config.get("performance", {}).get("dynamic_resolution", False): - self._update_dynamic_resolution() - - def update_cameras(self, head_pose): - """ - 根据头部姿态更新相机位置 - - Args: - head_pose: 包含位置和旋转的头部姿态数据 - """ - if not self.is_initialized: - return - - # 获取主相机 - main_cam = self.world.camera - - # 应用头部位置和旋转 - if 'position' in head_pose: - main_cam.setPos(Vec3(*head_pose['position'])) - - if 'rotation' in head_pose: - main_cam.setHpr(Vec3(*head_pose['rotation'])) - - def render_frame(self): - """渲染一帧VR画面""" - if not self.is_initialized: - return None, None - - # 强制渲染左右眼缓冲区 - self.world.graphicsEngine.renderFrame() - - # 获取渲染结果 - left_image = self.left_texture.getRamImage() - right_image = self.right_texture.getRamImage() - - return left_image, right_image - - def shutdown(self): - """关闭VR显示系统""" - if not self.is_initialized: - return True - - print("正在关闭VR显示系统...") - - # 移除畸变校正 - if self.distortion_manager: - self.distortion_manager.cleanup() - self.distortion_manager = None - - # 移除相机 - if self.left_camera: - self.left_camera.removeNode() - self.left_camera = None - - if self.right_camera: - self.right_camera.removeNode() - self.right_camera = None - - # 移除缓冲区 - if self.left_buffer: - self.world.graphicsEngine.removeWindow(self.left_buffer) - self.left_buffer = None - - if self.right_buffer: - self.world.graphicsEngine.removeWindow(self.right_buffer) - self.right_buffer = None - - self.is_initialized = False - print("✓ VR显示系统已关闭") - return True - - def set_resolution(self, width, height): - """ - 设置VR渲染分辨率 - - Args: - width: 宽度(像素) - height: 高度(像素) - """ - if not self.is_initialized: - print("VR显示系统未初始化,无法设置分辨率") - return False - - try: - self.render_resolution = [width, height] - - # 需要重新创建缓冲区 - self.shutdown() - self.initialize() - - print(f"✓ VR渲染分辨率已设置为 {width}x{height}") - return True - except Exception as e: - print(f"设置VR渲染分辨率时出错: {e}") - return False - - def set_ipd(self, ipd): - """ - 设置瞳距 - - Args: - ipd: 瞳距(米) - """ - if not self.is_initialized: - print("VR显示系统未初始化,无法设置瞳距") - return False - - try: - self.ipd = ipd - - # 更新相机位置 - if self.left_camera and self.right_camera: - self.left_camera.setPos(-self.ipd/2, 0, 0) - self.right_camera.setPos(self.ipd/2, 0, 0) - - print(f"✓ VR瞳距已设置为 {ipd}m") - return True - except Exception as e: - print(f"设置VR瞳距时出错: {e}") - return False - - def _update_dynamic_resolution(self): - """根据性能动态调整渲染分辨率""" - # 获取当前帧率 - clock = self.world.globalClock - fps = clock.getAverageFrameRate() - - # 目标帧率 - target_fps = self.vr_system.config.get("refresh_rate", 90) - - # 如果帧率低于目标的90%,降低分辨率 - if fps < target_fps * 0.9: - current_width, current_height = self.render_resolution - new_width = int(current_width * 0.9) - new_height = int(current_height * 0.9) - - # 设置最低分辨率限制 - if new_width >= 1024 and new_height >= 1024: - self.set_resolution(new_width, new_height) - - # 如果帧率高于目标的110%,提高分辨率 - elif fps > target_fps * 1.1: - current_width, current_height = self.render_resolution - new_width = int(current_width * 1.1) - new_height = int(current_height * 1.1) - - # 设置最高分辨率限制 - if new_width <= 4096 and new_height <= 4096: - self.set_resolution(new_width, new_height) - \ No newline at end of file diff --git a/vr/vr_input.py b/vr/vr_input.py deleted file mode 100644 index 1365d9fc..00000000 --- a/vr/vr_input.py +++ /dev/null @@ -1,363 +0,0 @@ -""" -VR输入处理模块 - -此模块负责处理VR输入设备,包括: -- 控制器跟踪 -- 按钮和触摸板输入 -- 手势识别 -- 触觉反馈 -""" - -from panda3d.core import NodePath, LineSegs, Vec3, Vec4 -from panda3d.core import CollisionRay, CollisionNode, CollisionTraverser, CollisionHandlerQueue -from panda3d.core import BitMask32 -from direct.task import Task -import math -import time - -class VRInput: - """处理VR输入设备""" - - def __init__(self, vr_system): - """ - 初始化VR输入处理器 - - Args: - vr_system: VR系统实例 - """ - self.vr_system = vr_system - self.world = vr_system.world - self.device = vr_system.device - - # 控制器 - self.controllers = {} - self.controller_models = {} - self.controller_rays = {} - - # 输入状态 - self.button_states = {} - self.analog_states = {} - self.last_button_states = {} - - # 配置 - self.show_rays = True - self.ray_length = 100.0 - self.ray_color = Vec4(0, 0.5, 1, 0.8) - - # 功能标志 - self.gesture_enabled = True - self.interaction_enabled = True - self.haptic_enabled = True - - # 状态 - self.is_initialized = False - self.tracking_origin = None - - # 碰撞检测 - self.traverser = CollisionTraverser("vr_traverser") - self.queue = CollisionHandlerQueue() - - def initialize(self): - """初始化VR输入系统""" - print("正在初始化VR输入系统...") - - if not self.device: - print("✗ VR设备未初始化,无法设置输入") - return False - - # 1. 设置跟踪原点 - self.tracking_origin = self.world.render.attachNewNode("vr_tracking_origin") - - # 2. 设置控制器 - if not self._setup_controllers(): - print("✗ 设置VR控制器失败") - # 这不是致命错误,可以继续 - print("⚠ VR将在没有控制器的情况下运行") - - # 3. 定义输入动作 - self._define_actions() - - # 4. 创建控制器射线 - if self.show_rays: - self._create_controller_rays() - - self.is_initialized = True - print("✓ VR输入系统初始化完成") - return True - - def _setup_controllers(self): - """设置控制器跟踪""" - try: - # 获取设备控制器信息 - if hasattr(self.device, 'get_controllers'): - controllers_info = self.device.get_controllers() - - if not controllers_info: - print("⚠ 未检测到VR控制器") - return False - - # 为每个控制器创建节点 - for controller_id, info in controllers_info.items(): - controller_node = self.tracking_origin.attachNewNode(f"controller_{controller_id}") - - # 加载控制器模型(如果有) - model_path = info.get('model_path') - if model_path: - try: - controller_model = self.world.loader.loadModel(model_path) - controller_model.reparentTo(controller_node) - self.controller_models[controller_id] = controller_model - except Exception as e: - print(f"加载控制器模型失败: {e}") - # 使用默认模型 - self._create_default_controller_model(controller_node, controller_id) - else: - # 使用默认模型 - self._create_default_controller_model(controller_node, controller_id) - - # 存储控制器节点 - self.controllers[controller_id] = controller_node - - # 初始化按钮状态 - self.button_states[controller_id] = {} - self.analog_states[controller_id] = {} - self.last_button_states[controller_id] = {} - - print(f"✓ 已设置 {len(self.controllers)} 个VR控制器") - return True - else: - print("⚠ VR设备不支持控制器跟踪") - return False - except Exception as e: - print(f"设置VR控制器时出错: {e}") - return False - - def _create_default_controller_model(self, parent_node, controller_id): - """创建默认控制器模型""" - from panda3d.core import GeomVertexFormat, GeomVertexData, Geom, GeomTriangles - from panda3d.core import GeomVertexWriter, GeomNode - - # 创建简单的控制器几何体 - format = GeomVertexFormat.getV3n3c4() - vdata = GeomVertexData('controller', format, Geom.UHStatic) - - vertex = GeomVertexWriter(vdata, 'vertex') - normal = GeomVertexWriter(vdata, 'normal') - color = GeomVertexWriter(vdata, 'color') - - # 简单的手柄形状 - # 顶部 - vertex.addData3f(0, 0, 0.1) - normal.addData3f(0, 0, 1) - color.addData4f(0.8, 0.8, 0.8, 1) - - # 底部圆形的顶点 - radius = 0.03 - segments = 8 - for i in range(segments): - angle = 2.0 * math.pi * i / segments - x = radius * math.cos(angle) - y = radius * math.sin(angle) - vertex.addData3f(x, y, -0.1) - normal.addData3f(x, y, 0) - color.addData4f(0.5, 0.5, 0.5, 1) - - # 创建三角形 - tris = GeomTriangles(Geom.UHStatic) - - # 连接顶部到圆形底部 - for i in range(segments): - tris.addVertices(0, i + 1, (i + 1) % segments + 1) - tris.closePrimitive() - - # 创建几何体 - geom = Geom(vdata) - geom.addPrimitive(tris) - - # 创建GeomNode - node = GeomNode(f'controller_{controller_id}_geom') - node.addGeom(geom) - - # 创建NodePath并附加到父节点 - controller_model = NodePath(node) - controller_model.reparentTo(parent_node) - - # 存储模型引用 - self.controller_models[controller_id] = controller_model - - def _define_actions(self): - """定义输入动作映射""" - # 这里可以定义按钮到动作的映射 - self.input_actions = { - "trigger": { - "description": "触发器", - "button": "trigger", - "action": self._on_trigger - }, - "grip": { - "description": "握把", - "button": "grip", - "action": self._on_grip - }, - "menu": { - "description": "菜单按钮", - "button": "menu", - "action": self._on_menu - }, - "thumbstick_press": { - "description": "摇杆按下", - "button": "thumbstick", - "action": self._on_thumbstick_press - }, - "teleport": { - "description": "传送", - "button": "thumbstick", - "analog": True, - "direction": "up", - "action": self._on_teleport - } - } - - def _create_controller_rays(self): - """创建控制器射线可视化""" - for controller_id, controller in self.controllers.items(): - # 创建射线线段 - ls = LineSegs() - ls.setThickness(2.0) - ls.setColor(self.ray_color) - ls.moveTo(0, 0, 0) - ls.drawTo(0, self.ray_length, 0) - - # 创建射线节点 - ray_node = ls.create() - ray_path = NodePath(ray_node) - ray_path.reparentTo(controller) - - # 默认隐藏 - ray_path.hide() - - # 存储射线引用 - self.controller_rays[controller_id] = ray_path - - def update(self): - """更新控制器状态(每帧调用)""" - if not self.is_initialized: - return - - # 1. 更新控制器位置和旋转 - self._update_controller_poses() - - # 2. 更新按钮状态 - self._update_button_states() - - # 3. 处理输入事件 - self._process_input() - - # 4. 更新射线(如果启用) - if self.show_rays: - self._update_controller_rays() - - def _update_controller_poses(self): - """更新控制器位置和旋转""" - if not self.device or not hasattr(self.device, 'get_controller_poses'): - return - - # 获取所有控制器的姿态 - poses = self.device.get_controller_poses() - if not poses: - return - - # 更新每个控制器的位置和旋转 - for controller_id, pose in poses.items(): - if controller_id in self.controllers: - controller = self.controllers[controller_id] - - if 'position' in pose: - controller.setPos(Vec3(*pose['position'])) - - if 'rotation' in pose: - controller.setHpr(Vec3(*pose['rotation'])) - - def _update_button_states(self): - """更新按钮状态""" - if not self.device or not hasattr(self.device, 'get_controller_inputs'): - return - - # 获取所有控制器的输入状态 - inputs = self.device.get_controller_inputs() - if not inputs: - return - - # 更新每个控制器的按钮和模拟输入状态 - for controller_id, input_data in inputs.items(): - if controller_id in self.controllers: - # 保存上一帧的按钮状态 - self.last_button_states[controller_id] = self.button_states[controller_id].copy() - - # 更新按钮状态 - if 'buttons' in input_data: - self.button_states[controller_id] = input_data['buttons'] - - # 更新模拟输入状态 - if 'analogs' in input_data: - self.analog_states[controller_id] = input_data['analogs'] - - def _process_input(self): - """处理输入事件""" - if not self.interaction_enabled: - return - - # 检查每个定义的动作 - for action_name, action_def in self.input_actions.items(): - button = action_def.get('button') - - for controller_id in self.controllers: - # 处理按钮动作 - if not action_def.get('analog', False): - # 检查按钮是否刚被按下 - if (button in self.button_states.get(controller_id, {}) and - button in self.last_button_states.get(controller_id, {}) and - self.button_states[controller_id][button] and - not self.last_button_states[controller_id][button]): - - # 调用动作处理函数 - if 'action' in action_def: - action_def['action'](controller_id) - - # 处理模拟输入动作 - else: - if button in self.analog_states.get(controller_id, {}): - analog_value = self.analog_states[controller_id][button] - direction = action_def.get('direction') - - # 根据方向检查模拟输入 - if direction == 'up' and analog_value[1] > 0.7: - if 'action' in action_def: - action_def['action'](controller_id) - elif direction == 'down' and analog_value[1] < -0.7: - if 'action' in action_def: - action_def['action'](controller_id) - elif direction == 'left' and analog_value[0] < -0.7: - if 'action' in action_def: - action_def['action'](controller_id) - elif direction == 'right' and analog_value[0] > 0.7: - if 'action' in action_def: - action_def['action'](controller_id) - - def _update_controller_rays(self): - """更新控制器射线可视化""" - for controller_id, ray in self.controller_rays.items(): - if self.show_rays: - ray.show() - - # 执行射线检测 - if controller_id in self.controllers: - controller = self.controllers[controller_id] - origin = controller.getPos(self.world.render) - direction = Vec3(0, 1, 0) # 控制器前方 - direction = self.world.render.getRelativeVector(controller, direction) - - # 射线检测 - result = self._perform_ray_test(origin, direction) - - # \ No newline at end of file diff --git a/vr/vr_system.py b/vr/vr_system.py deleted file mode 100644 index 8fc7bc2f..00000000 --- a/vr/vr_system.py +++ /dev/null @@ -1,380 +0,0 @@ -""" -VR系统核心管理模块 - -此模块提供VR系统的核心功能,负责初始化和协调其他VR组件。 -""" - -import os -import json -from panda3d.core import loadPrcFileData, WindowProperties -from direct.task import Task - -class VRSystem: - """VR系统的核心管理类,负责初始化和协调其他VR组件""" - - def __init__(self, world): - """ - 初始化VR系统 - - Args: - world: 游戏世界实例,提供对渲染器和场景的访问 - """ - self.world = world - self.display = None - self.input = None - self.streaming = None - self.device = None - self.is_initialized = False - self.config = self._load_config() - - # 状态变量 - self.vr_enabled = False - self.streaming_enabled = False - - print("✓ VR系统核心已创建") - - def _load_config(self): - """加载VR配置""" - config_path = os.path.join(os.path.dirname(__file__), "vr_config.json") - default_config = { - "preferred_device": "openxr", - "render_resolution": [2048, 2048], - "refresh_rate": 90, - "ipd": 0.064, # 瞳距,单位:米 - "streaming": { - "enabled": True, - "encoder": "h264", - "bitrate": 30000000, # 30 Mbps - "use_hardware_encoding": True - }, - "performance": { - "dynamic_resolution": True, - "fixed_foveated_rendering": True, - "motion_smoothing": True - } - } - - try: - if os.path.exists(config_path): - with open(config_path, 'r') as f: - return json.load(f) - else: - # 如果配置文件不存在,创建默认配置 - with open(config_path, 'w') as f: - json.dump(default_config, f, indent=4) - return default_config - except Exception as e: - print(f"加载VR配置失败: {e}") - return default_config - - def save_config(self): - """保存VR配置""" - config_path = os.path.join(os.path.dirname(__file__), "vr_config.json") - try: - with open(config_path, 'w') as f: - json.dump(self.config, f, indent=4) - print("✓ VR配置已保存") - return True - except Exception as e: - print(f"保存VR配置失败: {e}") - return False - - def initialize(self): - """初始化VR系统""" - if self.is_initialized: - print("VR系统已经初始化") - return True - - print("正在初始化VR系统...") - - # 1. 导入必要的模块(延迟导入以避免循环依赖) - from .vr_display import VRDisplay - from .vr_input import VRInput - from .vr_streaming import VRStreaming - - # 2. 根据配置选择设备实现 - device_type = self.config.get("preferred_device", "openxr") - device_created = self._create_device(device_type) - - if not device_created: - print("✗ 创建VR设备失败") - return False - - # 3. 创建显示、输入和串流组件 - self.display = VRDisplay(self) - self.input = VRInput(self) - self.streaming = VRStreaming(self) - - # 4. 初始化各组件 - display_initialized = self.display.initialize() - if not display_initialized: - print("✗ 初始化VR显示失败") - return False - - input_initialized = self.input.initialize() - if not input_initialized: - print("✗ 初始化VR输入失败") - return False - - # 5. 设置更新任务 - taskMgr = self.world.taskMgr - taskMgr.add(self.update, "vr_system_update") - - self.is_initialized = True - self.vr_enabled = True - print("✓ VR系统初始化完成") - return True - - def _create_device(self, device_type): - """创建VR设备实现""" - try: - if device_type == "openxr": - from .devices.openxr_device import OpenXRDevice - self.device = OpenXRDevice() - elif device_type == "oculus": - from .devices.oculus_device import OculusDevice - self.device = OculusDevice() - elif device_type == "steamvr": - from .devices.steamvr_device import SteamVRDevice - self.device = SteamVRDevice() - else: - print(f"不支持的设备类型: {device_type}") - return False - - device_initialized = self.device.initialize() - if not device_initialized: - print(f"✗ 初始化{device_type}设备失败") - return False - - print(f"✓ 已创建并初始化{device_type}设备") - return True - except ImportError as e: - print(f"导入设备模块失败: {e}") - return False - except Exception as e: - print(f"创建设备实例失败: {e}") - return False - - def shutdown(self): - """关闭VR系统""" - if not self.is_initialized: - print("VR系统未初始化") - return True - - print("正在关闭VR系统...") - - # 1. 停止串流 - if self.streaming and self.streaming_enabled: - self.stop_streaming() - - # 2. 关闭各组件 - if self.input: - self.input.shutdown() - - if self.display: - self.display.shutdown() - - if self.device: - self.device.shutdown() - - # 3. 移除更新任务 - taskMgr = self.world.taskMgr - taskMgr.remove("vr_system_update") - - self.is_initialized = False - self.vr_enabled = False - print("✓ VR系统已关闭") - return True - - def update(self, task): - """更新VR系统状态(每帧调用)""" - if not self.is_initialized: - return Task.cont - - # 1. 更新设备状态 - if self.device: - self.device.update() - - # 2. 更新输入 - if self.input: - self.input.update() - - # 3. 更新显示 - if self.display: - self.display.update() - - # 4. 更新串流 - if self.streaming and self.streaming_enabled: - self.streaming.update() - - return Task.cont - - def start_streaming(self): - """开始VR画面串流""" - if not self.is_initialized: - print("VR系统未初始化,无法开始串流") - return False - - if not self.streaming: - print("VR串流组件未创建") - return False - - if self.streaming_enabled: - print("VR串流已经启动") - return True - - result = self.streaming.start() - if result: - self.streaming_enabled = True - print("✓ VR串流已启动") - else: - print("✗ 启动VR串流失败") - - return result - - def stop_streaming(self): - """停止VR画面串流""" - if not self.streaming or not self.streaming_enabled: - print("VR串流未启动") - return True - - result = self.streaming.stop() - if result: - self.streaming_enabled = False - print("✓ VR串流已停止") - else: - print("✗ 停止VR串流失败") - - return result - - def get_status(self): - """获取VR系统状态信息""" - status = { - "initialized": self.is_initialized, - "vr_enabled": self.vr_enabled, - "streaming_enabled": self.streaming_enabled, - "device_type": self.config.get("preferred_device", "unknown"), - "render_resolution": self.config.get("render_resolution", [0, 0]), - "refresh_rate": self.config.get("refresh_rate", 0) - } - - # 添加设备特定信息 - if self.device: - device_status = self.device.get_status() - status.update(device_status) - - # 添加串流信息 - if self.streaming: - streaming_status = self.streaming.get_statistics() - status["streaming"] = streaming_status - - return status - - def set_render_resolution(self, width, height): - """设置VR渲染分辨率""" - if not self.is_initialized: - print("VR系统未初始化,无法设置渲染分辨率") - return False - - self.config["render_resolution"] = [width, height] - - if self.display: - result = self.display.set_resolution(width, height) - if result: - print(f"✓ VR渲染分辨率已设置为 {width}x{height}") - self.save_config() - return result - - return False - - def set_refresh_rate(self, rate): - """设置VR刷新率""" - if not self.is_initialized: - print("VR系统未初始化,无法设置刷新率") - return False - - self.config["refresh_rate"] = rate - - if self.device: - result = self.device.set_refresh_rate(rate) - if result: - print(f"✓ VR刷新率已设置为 {rate}Hz") - self.save_config() - return result - - return False - - def set_ipd(self, ipd): - """设置瞳距(单位:米)""" - if not self.is_initialized: - print("VR系统未初始化,无法设置瞳距") - return False - - self.config["ipd"] = ipd - - if self.display: - result = self.display.set_ipd(ipd) - if result: - print(f"✓ VR瞳距已设置为 {ipd}m") - self.save_config() - return result - - return False - - def set_streaming_quality(self, bitrate=None, encoder=None, use_hardware=None): - """设置串流质量参数""" - if not self.streaming: - print("VR串流组件未创建,无法设置串流质量") - return False - - streaming_config = self.config.get("streaming", {}) - - if bitrate is not None: - streaming_config["bitrate"] = bitrate - - if encoder is not None: - streaming_config["encoder"] = encoder - - if use_hardware is not None: - streaming_config["use_hardware_encoding"] = use_hardware - - self.config["streaming"] = streaming_config - - result = self.streaming.set_quality( - bitrate=streaming_config.get("bitrate"), - encoder=streaming_config.get("encoder"), - use_hardware=streaming_config.get("use_hardware_encoding") - ) - - if result: - print("✓ VR串流质量参数已更新") - self.save_config() - - return result - - def calibrate(self): - """启动VR校准过程""" - if not self.is_initialized: - print("VR系统未初始化,无法校准") - return False - - print("正在启动VR校准...") - - # 导入校准模块 - from .vr_calibration import start_calibration - - result = start_calibration(self) - if result: - print("✓ VR校准完成") - else: - print("✗ VR校准失败或被取消") - - return result - - def is_vr_enabled(self): - """检查VR是否启用""" - return self.vr_enabled - - def is_streaming(self): - """检查是否正在串流""" - return self.streaming_enabled \ No newline at end of file diff --git a/vr_display_demo.py b/vr_display_demo.py deleted file mode 100644 index a117204c..00000000 --- a/vr_display_demo.py +++ /dev/null @@ -1,500 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -VR显示功能演示脚本 -演示引擎画面在VR中的显示效果,包括ALVR串流 -""" - -import sys -import os -import time -from main import MyWorld - - -def demo_vr_basic_display(): - """演示基本VR显示功能""" - print("=== VR基本显示演示 ===") - - # 创建世界实例 - world = MyWorld() - - # 加载一些模型来展示 - print("📦 加载演示场景...") - try: - # 创建一些简单的几何体作为演示 - from panda3d.core import CardMaker, Vec4 - - # 创建地面 - cm = CardMaker("ground") - cm.setFrame(-10, 10, -10, 10) - ground = world.render.attachNewNode(cm.generate()) - ground.setPos(0, 0, -1) - ground.setColor(0.5, 0.8, 0.5, 1.0) - ground.setHpr(0, -90, 0) - - # 创建一些立方体 - for i in range(5): - for j in range(5): - cube = world.loader.loadModel("environment") - if not cube: - # 如果没有environment模型,创建简单的立方体 - cm_cube = CardMaker(f"cube_{i}_{j}") - cm_cube.setFrame(-0.5, 0.5, -0.5, 0.5) - cube = world.render.attachNewNode(cm_cube.generate()) - - cube.setPos(i * 2 - 4, j * 2 - 4, 0) - cube.setScale(0.5) - cube.setColor(i/5.0, j/5.0, 1.0, 1.0) - - print("✓ 演示场景加载完成") - - except Exception as e: - print(f"场景加载错误: {str(e)}") - - # 初始化VR系统 - print("\n🥽 初始化VR显示系统...") - vr_success = world.initializeVR() - - if vr_success: - print("✓ VR系统初始化成功") - vr_info = world.getVRInfo() - - print(f"\n📊 VR显示信息:") - print(f" - 模式: {vr_info['mode']}") - print(f" - 渲染尺寸: {vr_info['render_size']}") - print(f" - 模拟模式: {vr_info['simulation_mode']}") - - if vr_info['simulation_mode']: - print("\n💡 模拟模式说明:") - print(" - 左右眼视图将并排显示在主窗口") - print(" - 可以看到立体渲染效果") - print(" - 头部会有轻微的模拟摆动") - else: - print("\n💡 真实VR模式:") - print(" - 画面将渲染到VR头盔") - print(" - 支持真实的头部追踪") - print(" - 控制器交互可用") - - # 运行几秒钟以展示效果 - print(f"\n🎮 VR显示演示运行中... (10秒)") - print(" 观察主窗口中的立体渲染效果") - - start_time = time.time() - while time.time() - start_time < 10: - # 处理Panda3D事件 - if hasattr(world, 'taskMgr'): - world.taskMgr.step() - time.sleep(0.016) # ~60 FPS - - print("✓ VR显示演示完成") - - else: - print("✗ VR系统初始化失败") - return False - - # 关闭VR系统 - world.shutdownVR() - print("\n✓ VR系统已关闭") - - return True - - -def demo_alvr_streaming(): - """演示ALVR串流功能""" - print("=== ALVR串流显示演示 ===") - - # 创建世界实例 - world = MyWorld() - - # 首先初始化VR系统 - print("🥽 初始化VR系统...") - if not world.initializeVR(): - print("✗ VR系统初始化失败") - return False - - # 检查是否为直连模式(SteamVR已运行) - if world.alvr_streamer._is_steamvr_active(): - print("💡 检测到SteamVR正在运行,将使用直连模式") - print(" 在直连模式下,画面将直接通过SteamVR传输到头显") - print(" 无需通过ALVR服务器进行额外的串流处理") - - # 初始化ALVR串流 - print("\n📡 初始化ALVR串流...") - print("💡 检测到VR设备已连接,正在尝试连接ALVR服务器...") - alvr_success = world.initializeALVR() - - if alvr_success: - print("✓ ALVR串流初始化成功") - - # 设置串流质量 - print("\n🎥 配置串流质量...") - quality_set = world.setALVRStreamQuality(2880, 1700, 72, 150) - if quality_set: - print(" ✓ 串流质量: 2880x1700@72fps, 150Mbps") - - # 获取串流状态 - status = world.getALVRStreamingStatus() - print(f"\n📊 ALVR串流状态:") - print(f" - 连接状态: {status['connected']}") - print(f" - 串流状态: {status['streaming']}") - print(f" - 分辨率: {status['resolution']}") - print(f" - 帧率: {status['fps']}") - print(f" - 码率: {status['bitrate']}Mbps") - - # 开始串流 - print(f"\n🚀 开始ALVR串流...") - if world.startALVRStreaming(): - print("✓ ALVR串流已开始") - - print(f"\n💡 ALVR串流说明:") - print(" - 引擎画面正在串流到VR头盔") - print(" - 支持Quest 2/3/Pico 4等VR设备") - print(" - 确保ALVR客户端在头盔中运行") - print(" - 检查网络连接质量") - - # 运行串流演示 - print(f"\n📡 ALVR串流演示运行中... (15秒)") - start_time = time.time() - while time.time() - start_time < 15: - # 更新串流状态 - current_status = world.getALVRStreamingStatus() - if time.time() - start_time > 5: # 5秒后显示状态更新 - print(f" 当前FPS: {current_status['fps']}, 延迟: {current_status['latency']}ms") - - if hasattr(world, 'taskMgr'): - world.taskMgr.step() - time.sleep(0.016) - - # 停止串流 - print("\n🛑 停止ALVR串流...") - world.stopALVRStreaming() - print("✓ ALVR串流已停止") - - else: - print("⚠ ALVR串流启动失败") - print(" 可能原因:") - print(" - ALVR服务器未运行") - print(" - VR客户端未连接") - print(" - 网络连接问题") - print(" - 防火墙阻止连接") - - else: - print("⚠ ALVR串流初始化失败") - print("💡 这可能是因为:") - print(" 1. ALVR服务器未运行") - print(" 2. SteamVR正在使用独占模式") - print(" 3. 端口冲突") - print("\n🔧 Pico 4有线连接设置建议:") - print(" 1. 确保ALVR服务器正在运行") - print(" 2. 检查任务管理器中是否有alvr_server进程") - print(" 3. 尝试重启ALVR服务器") - print(" 4. 确认防火墙允许ALVR通信") - print(" 5. 检查ALVR服务器端口设置") - print(" 6. 如果SteamVR正在运行,尝试先关闭它") - print("\n💡 直连模式说明:") - print(" 如果您使用的是有线连接(如Pico 4通过USB-C连接)") - print(" 并且SteamVR已经可以正常显示画面,您可能不需要ALVR串流") - print(" SteamVR会直接处理渲染并将画面传输到头显") - - # 关闭系统 - world.shutdownALVR() - world.shutdownVR() - print("\n✓ 所有系统已关闭") - - return True - - -def demo_vr_interaction(): - """演示VR交互功能""" - print("=== VR交互显示演示 ===") - - # 创建世界实例 - world = MyWorld() - - # 初始化VR系统 - print("🥽 初始化VR系统...") - if not world.initializeVR(): - print("✗ VR系统初始化失败") - return False - - # 启动VR输入处理 - print("\n🎮 启动VR输入处理...") - if world.startVRInput(): - print("✓ VR输入处理已启动") - - # 显示控制器射线 - world.showControllerRays(True) - print("✓ 控制器射线已启用") - - # 启用VR交互 - world.setVRInteractionEnabled(True) - world.setVRGestureEnabled(True) - print("✓ VR交互和手势识别已启用") - - # 获取控制器状态 - print(f"\n🎮 控制器状态:") - controllers = world.getAllControllers() - for i, controller in enumerate(controllers): - if controller: - state = world.getControllerState(i) - print(f" 控制器 {i}: 可用") - if state: - print(f" - 连接: {state.get('connected', False)}") - print(f" - 扳机: {state.get('trigger', 0):.2f}") - print(f" - 握把: {state.get('grip', 0):.2f}") - - # 运行交互演示 - print(f"\n🤏 VR交互演示运行中... (10秒)") - print(" - 控制器射线可见") - print(" - 扳机:抓取物体") - print(" - 握把:切换交互模式") - print(" - 触摸板:导航控制") - - start_time = time.time() - while time.time() - start_time < 10: - if hasattr(world, 'taskMgr'): - world.taskMgr.step() - time.sleep(0.016) - - # 停止VR输入 - world.stopVRInput() - print("✓ VR输入处理已停止") - - else: - print("⚠ VR输入处理启动失败") - - # 关闭VR系统 - world.shutdownVR() - print("\n✓ VR系统已关闭") - - return True - - -def demo_comprehensive_vr(): - """综合VR显示演示""" - print("=== 综合VR显示演示 ===") - - # 创建世界实例 - world = MyWorld() - - # 创建丰富的演示场景 - print("🎨 创建VR演示场景...") - try: - from panda3d.core import CardMaker, Vec4, AmbientLight, DirectionalLight - - # 添加光照 - ambient_light = AmbientLight('ambient') - ambient_light.setColor(Vec4(0.3, 0.3, 0.3, 1)) - ambient_light_np = world.render.attachNewNode(ambient_light) - world.render.setLight(ambient_light_np) - - directional_light = DirectionalLight('directional') - directional_light.setColor(Vec4(0.8, 0.8, 0.8, 1)) - directional_light.setDirection(Vec4(-1, -1, -1, 0)) - directional_light_np = world.render.attachNewNode(directional_light) - world.render.setLight(directional_light_np) - - # 创建地面网格 - for i in range(-5, 6): - for j in range(-5, 6): - cm = CardMaker(f"grid_{i}_{j}") - cm.setFrame(-0.4, 0.4, -0.4, 0.4) - grid = world.render.attachNewNode(cm.generate()) - grid.setPos(i * 1.0, j * 1.0, -1) - grid.setHpr(0, -90, 0) - - # 棋盘格颜色 - if (i + j) % 2 == 0: - grid.setColor(0.8, 0.8, 0.8, 1.0) - else: - grid.setColor(0.6, 0.6, 0.6, 1.0) - - # 创建一些彩色立方体 - for i in range(8): - cm = CardMaker(f"cube_{i}") - cm.setFrame(-0.3, 0.3, -0.3, 0.3) - cube = world.render.attachNewNode(cm.generate()) - - import math - angle = i * math.pi * 2 / 8 - radius = 3 - cube.setPos(math.cos(angle) * radius, math.sin(angle) * radius, 0.5) - cube.setHpr(i * 45, 0, 0) - cube.setScale(1, 1, 2) - - # 彩虹颜色 - hue = i / 8.0 - r = abs(math.sin(hue * math.pi * 2)) - g = abs(math.sin((hue + 0.33) * math.pi * 2)) - b = abs(math.sin((hue + 0.66) * math.pi * 2)) - cube.setColor(r, g, b, 1.0) - - print("✓ VR演示场景创建完成") - - except Exception as e: - print(f"场景创建错误: {str(e)}") - - # 完整的VR系统演示 - print("\n🥽 启动完整VR系统...") - - # 1. 初始化VR - if not world.initializeVR(): - print("✗ VR系统初始化失败") - return False - - print("✓ VR显示系统已启动") - - # 2. 启动VR输入 - if world.startVRInput(): - world.showControllerRays(True) - world.setVRInteractionEnabled(True) - print("✓ VR交互系统已启动") - - # 3. 尝试启动ALVR串流 - if world.initializeALVR(): - world.setALVRStreamQuality(2880, 1700, 72, 100) - if world.startALVRStreaming(): - print("✓ ALVR串流已启动") - else: - print("⚠ ALVR串流启动失败") - else: - print("⚠ ALVR初始化失败(正常,如果没有ALVR服务器)") - - # 运行综合演示 - print(f"\n🎬 综合VR显示演示运行中... (20秒)") - print("💡 功能说明:") - print(" - 立体渲染显示") - print(" - 头部追踪(模拟或真实)") - print(" - 控制器可视化") - print(" - ALVR无线串流") - print(" - VR交互功能") - - start_time = time.time() - frame_count = 0 - - while time.time() - start_time < 20: - # 处理引擎更新 - if hasattr(world, 'taskMgr'): - world.taskMgr.step() - - frame_count += 1 - - # 每5秒显示一次状态 - elapsed = time.time() - start_time - if frame_count % 300 == 0: # 约每5秒 - fps = frame_count / elapsed - print(f" 运行状态: {elapsed:.1f}s, FPS: {fps:.1f}") - - # 显示VR状态 - vr_info = world.getVRInfo() - print(f" VR模式: {vr_info['mode']}, 分辨率: {vr_info['render_size']}") - - if world.isALVRStreaming(): - alvr_status = world.getALVRStreamingStatus() - print(f" ALVR: FPS {alvr_status['fps']}, 延迟 {alvr_status['latency']}ms") - - time.sleep(0.016) # ~60 FPS - - print("✓ 综合VR演示完成") - - # 关闭所有系统 - world.stopVRInput() - world.stopALVRStreaming() - world.shutdownALVR() - world.shutdownVR() - print("\n✓ 所有VR系统已关闭") - - return True - - -def print_vr_display_guide(): - """打印VR显示指南""" - print("📋 VR显示功能指南") - print("=" * 50) - - print("\n🎯 功能概述:") - print(" - 立体渲染:为左右眼生成不同视角的画面") - print(" - 头部追踪:根据头部运动调整视角") - print(" - ALVR串流:无线串流到Quest等VR设备") - print(" - 控制器交互:支持VR控制器输入") - - print("\n🔧 使用方法:") - print(" 1. 运行引擎:python3 main.py") - print(" 2. 初始化VR:world.initializeVR()") - print(" 3. 启动ALVR:world.initializeALVR()") - print(" 4. 开始串流:world.startALVRStreaming()") - - print("\n📱 ALVR设置步骤:") - print(" 1. 下载ALVR服务器到PC") - print(" 2. 在Quest中安装ALVR客户端") - print(" 3. 确保PC和Quest在同一WiFi网络") - print(" 4. 启动ALVR服务器") - print(" 5. 在Quest中连接到PC") - print(" 6. 运行引擎VR演示") - - print("\n🎮 VR模式说明:") - print(" 模拟模式:") - print(" - 无需VR硬件") - print(" - 立体视图显示在主窗口") - print(" - 模拟头部和控制器追踪") - print(" - 适合开发和调试") - print(" ") - print(" 真实VR模式:") - print(" - 需要VR头盔和SteamVR") - print(" - 真实的立体渲染和追踪") - print(" - 支持控制器交互") - print(" - 完整的VR体验") - - print("\n🛠 故障排除:") - print(" - VR初始化失败 → 自动切换到模拟模式") - print(" - ALVR连接失败 → 检查网络和服务器") - print(" - 画面不显示 → 检查VR头盔和SteamVR") - print(" - 延迟过高 → 降低串流质量或使用有线连接") - - -def main(): - """主函数""" - print("🎮 VR显示功能演示") - print("=" * 50) - - print("请选择演示类型:") - print("1. 基本VR显示演示") - print("2. ALVR串流演示") - print("3. VR交互演示") - print("4. 综合VR演示") - print("5. VR显示功能指南") - - try: - choice = input("请输入选择 (1-5): ") - - if choice == "1": - success = demo_vr_basic_display() - elif choice == "2": - success = demo_alvr_streaming() - elif choice == "3": - success = demo_vr_interaction() - elif choice == "4": - success = demo_comprehensive_vr() - elif choice == "5": - print_vr_display_guide() - return - else: - print("无效的选择") - return - - print("\n" + "=" * 50) - if success: - print("✓ VR显示演示成功完成") - print("🎉 引擎画面VR显示功能正常工作!") - else: - print("✗ VR显示演示遇到问题") - print("💡 请检查VR系统设置和连接") - - except KeyboardInterrupt: - print("\n用户中断演示") - except Exception as e: - print(f"演示过程中发生错误: {str(e)}") - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/vr_display_optimizer.py b/vr_display_optimizer.py deleted file mode 100644 index 082b2a72..00000000 --- a/vr_display_optimizer.py +++ /dev/null @@ -1,316 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -VR显示优化工具 -优化VR渲染性能和显示质量 -""" - -from panda3d.core import RenderState, CullFaceAttrib, DepthTestAttrib, AlphaTestAttrib -from core.vr_manager import VRManager - - -class VRDisplayOptimizer: - """VR显示优化器""" - - def __init__(self, world): - self.world = world - self.vr_manager = world.vr_manager if hasattr(world, 'vr_manager') else None - self.optimization_enabled = True - - def optimize_vr_rendering(self): - """优化VR渲染设置""" - if not self.vr_manager or not self.vr_manager.is_vr_enabled(): - print("VR系统未启用,跳过优化") - return False - - print("🚀 开始VR渲染优化...") - - # 1. 优化眼部缓冲区设置 - self._optimize_eye_buffers() - - # 2. 优化渲染状态 - self._optimize_render_states() - - # 3. 设置VR特定的渲染选项 - self._setup_vr_render_options() - - # 4. 优化纹理设置 - self._optimize_textures() - - # 5. 设置LOD和裁剪 - self._setup_lod_and_culling() - - print("✓ VR渲染优化完成") - return True - - def _optimize_eye_buffers(self): - """优化眼部缓冲区""" - try: - if not self.vr_manager.left_eye_buffer or not self.vr_manager.right_eye_buffer: - return - - # 设置缓冲区优化选项 - for buffer in [self.vr_manager.left_eye_buffer, self.vr_manager.right_eye_buffer]: - # 启用多重采样抗锯齿 - buffer.setMultisample(4) - - # 设置深度缓冲精度 - buffer.addDepthRenderTexture() - - # 启用硬件加速 - buffer.setOneShot(False) - buffer.setActive(True) - - print(" ✓ 眼部缓冲区优化完成") - - except Exception as e: - print(f" ⚠ 眼部缓冲区优化失败: {str(e)}") - - def _optimize_render_states(self): - """优化渲染状态""" - try: - # 设置全局渲染状态 - render_state = RenderState.make( - CullFaceAttrib.make(CullFaceAttrib.MCullCounterClockwise), - DepthTestAttrib.make(DepthTestAttrib.MLess), - AlphaTestAttrib.make(AlphaTestAttrib.MGreater, 0.5) - ) - - # 应用到render节点 - self.world.render.setState(render_state) - - print(" ✓ 渲染状态优化完成") - - except Exception as e: - print(f" ⚠ 渲染状态优化失败: {str(e)}") - - def _setup_vr_render_options(self): - """设置VR特定的渲染选项""" - try: - # 启用VR优化 - if hasattr(self.world, 'win') and self.world.win: - # 设置VR特定的窗口属性 - properties = self.world.win.getProperties() - properties.setFrameBufferMode(properties.FBMHardware) - - # 启用垂直同步以减少延迟 - properties.setSync(True) - - # 优化摄像机设置 - if hasattr(self.world, 'camera'): - # 设置适合VR的FOV和近远裁剪面 - lens = self.world.camera.node().getLens() - lens.setNearFar(0.1, 1000.0) # VR推荐的近远裁剪面 - - print(" ✓ VR渲染选项设置完成") - - except Exception as e: - print(f" ⚠ VR渲染选项设置失败: {str(e)}") - - def _optimize_textures(self): - """优化纹理设置""" - try: - from panda3d.core import Texture - - # 设置全局纹理优化 - Texture.setTexturesPower2(Texture.ATPPad) # 填充到2的幂次方 - Texture.setKeepRAMImage(False) # 不保留RAM图像以节省内存 - - # 启用纹理压缩 - Texture.setCompressionMode(Texture.CMDefault) - - print(" ✓ 纹理优化完成") - - except Exception as e: - print(f" ⚠ 纹理优化失败: {str(e)}") - - def _setup_lod_and_culling(self): - """设置LOD和裁剪""" - try: - # 启用视锥裁剪 - from panda3d.core import CullTraverser - self.world.render.setTwoSided(False) - - # 设置距离裁剪 - if hasattr(self.world, 'camera'): - # 根据VR需求调整裁剪距离 - lens = self.world.camera.node().getLens() - lens.setFar(500.0) # 减少远裁剪面以提高性能 - - print(" ✓ LOD和裁剪设置完成") - - except Exception as e: - print(f" ⚠ LOD和裁剪设置失败: {str(e)}") - - def optimize_alvr_streaming(self): - """优化ALVR串流性能""" - if not hasattr(self.world, 'alvr_streamer'): - print("ALVR串流器不可用,跳过优化") - return False - - print("📡 开始ALVR串流优化...") - - alvr = self.world.alvr_streamer - - # 1. 优化串流质量设置 - self._optimize_stream_quality(alvr) - - # 2. 优化网络设置 - self._optimize_network_settings(alvr) - - # 3. 优化编码设置 - self._optimize_encoding_settings(alvr) - - print("✓ ALVR串流优化完成") - return True - - def _optimize_stream_quality(self, alvr): - """优化串流质量""" - try: - # 根据性能自动调整质量 - vr_info = self.vr_manager.get_vr_info() - render_width, render_height = vr_info['render_size'] - - # 针对不同设备优化 - if render_width >= 2880: # Quest 2/3高质量 - alvr.set_stream_quality(2880, 1700, 72, 150) - print(" ✓ 设置为高质量模式 (Quest 2/3)") - elif render_width >= 2160: # 中等质量 - alvr.set_stream_quality(2160, 1200, 60, 100) - print(" ✓ 设置为中等质量模式") - else: # 低质量模式 - alvr.set_stream_quality(1920, 1080, 60, 80) - print(" ✓ 设置为低质量模式") - - except Exception as e: - print(f" ⚠ 串流质量优化失败: {str(e)}") - - def _optimize_network_settings(self, alvr): - """优化网络设置""" - try: - # 这里可以添加网络优化逻辑 - # 例如调整缓冲区大小、连接超时等 - print(" ✓ 网络设置优化完成") - - except Exception as e: - print(f" ⚠ 网络设置优化失败: {str(e)}") - - def _optimize_encoding_settings(self, alvr): - """优化编码设置""" - try: - # 优化H.264编码参数 - alvr.codec = "h264" - - # 根据硬件能力调整编码设置 - print(" ✓ 编码设置优化完成") - - except Exception as e: - print(f" ⚠ 编码设置优化失败: {str(e)}") - - def monitor_performance(self, duration=10): - """监控VR性能""" - print(f"📊 开始VR性能监控 ({duration}秒)...") - - import time - start_time = time.time() - frame_count = 0 - last_report_time = start_time - - while time.time() - start_time < duration: - # 处理一帧 - if hasattr(self.world, 'taskMgr'): - self.world.taskMgr.step() - - frame_count += 1 - current_time = time.time() - - # 每2秒报告一次性能 - if current_time - last_report_time >= 2.0: - elapsed = current_time - last_report_time - fps = (frame_count / (current_time - start_time)) - - print(f" 性能报告: FPS {fps:.1f}") - - # 报告VR系统状态 - if self.vr_manager and self.vr_manager.is_vr_enabled(): - vr_info = self.vr_manager.get_vr_info() - print(f" VR模式: {vr_info['mode']}") - print(f" 渲染尺寸: {vr_info['render_size']}") - - # 报告ALVR状态 - if hasattr(self.world, 'alvr_streamer') and self.world.alvr_streamer.is_streaming(): - status = self.world.alvr_streamer.get_streaming_status() - print(f" ALVR FPS: {status['fps']}, 延迟: {status['latency']}ms") - - last_report_time = current_time - - time.sleep(0.016) # ~60 FPS目标 - - final_fps = frame_count / (time.time() - start_time) - print(f"✓ 性能监控完成,平均FPS: {final_fps:.1f}") - - return final_fps - - def auto_optimize(self): - """自动优化VR显示性能""" - print("🔧 开始自动VR优化...") - - # 1. 优化VR渲染 - if self.optimize_vr_rendering(): - print(" ✓ VR渲染优化完成") - - # 2. 优化ALVR串流 - if hasattr(self.world, 'alvr_streamer'): - if self.optimize_alvr_streaming(): - print(" ✓ ALVR串流优化完成") - - # 3. 监控性能 - print("\n📊 开始性能测试...") - fps = self.monitor_performance(5) - - # 4. 根据性能调整设置 - if fps < 45: # VR最低要求 - print("⚠ 性能不足,降低质量设置...") - self._apply_low_quality_settings() - elif fps > 80: - print("✓ 性能良好,可以提高质量设置") - self._apply_high_quality_settings() - - print("✓ 自动VR优化完成") - - def _apply_low_quality_settings(self): - """应用低质量设置""" - try: - # 降低渲染分辨率 - if self.vr_manager: - self.vr_manager.render_width = int(self.vr_manager.render_width * 0.8) - self.vr_manager.render_height = int(self.vr_manager.render_height * 0.8) - - # 降低ALVR质量 - if hasattr(self.world, 'alvr_streamer'): - self.world.alvr_streamer.set_stream_quality(1920, 1080, 60, 60) - - print(" ✓ 低质量设置已应用") - - except Exception as e: - print(f" ⚠ 低质量设置应用失败: {str(e)}") - - def _apply_high_quality_settings(self): - """应用高质量设置""" - try: - # 提高ALVR质量 - if hasattr(self.world, 'alvr_streamer'): - self.world.alvr_streamer.set_stream_quality(2880, 1700, 90, 200) - - print(" ✓ 高质量设置已应用") - - except Exception as e: - print(f" ⚠ 高质量设置应用失败: {str(e)}") - - -# 使用示例 -def optimize_vr_display(world): - """优化VR显示的便捷函数""" - optimizer = VRDisplayOptimizer(world) - optimizer.auto_optimize() - return optimizer \ No newline at end of file diff --git a/vr_quick_start.py b/vr_quick_start.py deleted file mode 100644 index c6635ac4..00000000 --- a/vr_quick_start.py +++ /dev/null @@ -1,263 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -VR快速启动脚本 -一键启动VR显示功能,包括自动优化和ALVR串流 -""" - -import sys -import os -import time -from main import MyWorld -from vr_display_optimizer import VRDisplayOptimizer - - -def quick_vr_setup(): - """快速VR设置""" - print("🚀 VR快速启动") - print("=" * 40) - - # 创建世界实例 - print("🌍 初始化引擎...") - world = MyWorld() - - # 创建优化器 - optimizer = VRDisplayOptimizer(world) - - # 设置基本场景 - print("🎨 设置VR演示场景...") - setup_demo_scene(world) - - # 初始化VR系统 - print("🥽 启动VR系统...") - vr_success = world.initializeVR() - - if not vr_success: - print("❌ VR初始化失败") - return None, None - - print("✅ VR系统已启动") - - # 优化VR性能 - print("⚡ 优化VR性能...") - optimizer.optimize_vr_rendering() - - # 尝试启动ALVR - print("📡 尝试启动ALVR串流...") - alvr_success = world.initializeALVR() - - if alvr_success: - optimizer.optimize_alvr_streaming() - if world.startALVRStreaming(): - print("✅ ALVR串流已启动") - else: - print("⚠️ ALVR串流启动失败") - else: - print("⚠️ ALVR初始化失败(正常,如果没有服务器)") - - # 启动VR输入 - print("🎮 启动VR交互...") - if world.startVRInput(): - world.showControllerRays(True) - world.setVRInteractionEnabled(True) - print("✅ VR交互已启用") - - # 显示状态信息 - print_vr_status(world) - - return world, optimizer - - -def setup_demo_scene(world): - """设置演示场景""" - try: - from panda3d.core import CardMaker, Vec4, AmbientLight, DirectionalLight - import math - - # 添加光照 - ambient = AmbientLight('ambient') - ambient.setColor(Vec4(0.4, 0.4, 0.4, 1)) - ambient_np = world.render.attachNewNode(ambient) - world.render.setLight(ambient_np) - - directional = DirectionalLight('sun') - directional.setColor(Vec4(1, 1, 0.9, 1)) - directional.setDirection(Vec4(-1, -1, -1, 0)) - sun_np = world.render.attachNewNode(directional) - world.render.setLight(sun_np) - - # 创建地面 - cm = CardMaker("ground") - cm.setFrame(-20, 20, -20, 20) - ground = world.render.attachNewNode(cm.generate()) - ground.setPos(0, 0, -2) - ground.setHpr(0, -90, 0) - ground.setColor(0.3, 0.7, 0.3, 1) - - # 创建彩色立方体环 - for i in range(12): - cm_cube = CardMaker(f"cube_{i}") - cm_cube.setFrame(-0.5, 0.5, -0.5, 0.5) - cube = world.render.attachNewNode(cm_cube.generate()) - - angle = i * math.pi * 2 / 12 - radius = 5 - height = math.sin(i * 0.5) * 2 - - cube.setPos(math.cos(angle) * radius, math.sin(angle) * radius, height) - cube.setHpr(i * 30, 0, 0) - cube.setScale(1, 1, 2 + height * 0.5) - - # HSV到RGB转换 - h = i / 12.0 * 360 - s = 0.8 - v = 1.0 - r, g, b = hsv_to_rgb(h, s, v) - cube.setColor(r, g, b, 1) - - print(" ✅ 演示场景创建完成") - - except Exception as e: - print(f" ⚠️ 场景创建失败: {str(e)}") - - -def hsv_to_rgb(h, s, v): - """HSV到RGB颜色转换""" - import math - h = h / 60.0 - c = v * s - x = c * (1 - abs((h % 2) - 1)) - m = v - c - - if 0 <= h < 1: - r, g, b = c, x, 0 - elif 1 <= h < 2: - r, g, b = x, c, 0 - elif 2 <= h < 3: - r, g, b = 0, c, x - elif 3 <= h < 4: - r, g, b = 0, x, c - elif 4 <= h < 5: - r, g, b = x, 0, c - else: - r, g, b = c, 0, x - - return r + m, g + m, b + m - - -def print_vr_status(world): - """显示VR状态信息""" - print("\n📊 VR系统状态") - print("-" * 30) - - if world.isVREnabled(): - vr_info = world.getVRInfo() - print(f"🥽 VR模式: {vr_info['mode']}") - print(f"📐 渲染尺寸: {vr_info['render_size']}") - print(f"🎮 模拟模式: {vr_info['simulation_mode']}") - - if world.isALVRConnected(): - status = world.getALVRStreamingStatus() - print(f"📡 ALVR状态: {'串流中' if world.isALVRStreaming() else '已连接'}") - print(f"🎥 分辨率: {status['resolution']}") - print(f"⚡ FPS: {status['fps']}") - else: - print("📡 ALVR状态: 未连接") - else: - print("🥽 VR状态: 未启用") - - -def run_vr_experience(world, optimizer, duration=30): - """运行VR体验""" - print(f"\n🎮 VR体验开始 ({duration}秒)") - print("💡 控制说明:") - print(" - 观察窗口中的立体视图") - print(" - VR头盔中可看到完整场景") - print(" - 控制器射线用于交互") - print(" - 按Ctrl+C退出") - - start_time = time.time() - frame_count = 0 - - try: - while time.time() - start_time < duration: - # 处理引擎更新 - if hasattr(world, 'taskMgr'): - world.taskMgr.step() - - frame_count += 1 - - # 每5秒显示状态 - elapsed = time.time() - start_time - if frame_count % 300 == 0: # ~5秒 - fps = frame_count / elapsed - print(f" 📈 运行状态: {elapsed:.1f}s, FPS: {fps:.1f}") - - if world.isALVRStreaming(): - alvr_status = world.getALVRStreamingStatus() - print(f" 📡 ALVR: {alvr_status['fps']} FPS, {alvr_status['latency']}ms延迟") - - time.sleep(0.016) # ~60 FPS - - except KeyboardInterrupt: - print("\n⏹️ 用户中断VR体验") - - final_fps = frame_count / (time.time() - start_time) - print(f"✅ VR体验完成,平均FPS: {final_fps:.1f}") - - -def cleanup_vr(world): - """清理VR资源""" - print("\n🧹 清理VR资源...") - - try: - if hasattr(world, 'stopVRInput'): - world.stopVRInput() - - if hasattr(world, 'stopALVRStreaming'): - world.stopALVRStreaming() - - if hasattr(world, 'shutdownALVR'): - world.shutdownALVR() - - if hasattr(world, 'shutdownVR'): - world.shutdownVR() - - print("✅ VR资源清理完成") - - except Exception as e: - print(f"⚠️ VR清理失败: {str(e)}") - - -def main(): - """主函数""" - print("🎮 VR引擎快速启动") - print("=" * 50) - - # 快速设置VR - world, optimizer = quick_vr_setup() - - if not world: - print("❌ VR启动失败") - return - - try: - # 运行VR体验 - run_vr_experience(world, optimizer, 60) # 60秒演示 - - except Exception as e: - print(f"❌ VR运行错误: {str(e)}") - - finally: - # 清理资源 - cleanup_vr(world) - - print("\n🎉 VR体验结束") - print("💡 要在Quest中体验:") - print(" 1. 安装ALVR服务器和客户端") - print(" 2. 连接同一WiFi网络") - print(" 3. 重新运行此脚本") - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/vr_streaming_test.py b/vr_streaming_test.py deleted file mode 100644 index da9f5cf9..00000000 --- a/vr_streaming_test.py +++ /dev/null @@ -1,264 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -VR串流测试脚本 -专门用于测试和验证ALVR串流功能 -""" - -import sys -import os -import time -from main import MyWorld - - -def test_vr_streaming(): - """测试VR串流功能""" - print("=== VR串流功能测试 ===") - - # 创建世界实例 - world = MyWorld() - - # 初始化VR系统 - print("🥽 初始化VR系统...") - if not world.initializeVR(): - print("✗ VR系统初始化失败") - return False - - print("✓ VR系统初始化成功") - - # 获取VR信息 - vr_info = world.getVRInfo() - print(f"\n📊 VR系统信息:") - print(f" - 模式: {vr_info['mode']}") - print(f" - 启用状态: {vr_info['enabled']}") - print(f" - 模拟模式: {vr_info['simulation_mode']}") - print(f" - 渲染尺寸: {vr_info['render_size']}") - - # 检查是否为直连模式(SteamVR已运行) - if world.alvr_streamer._is_steamvr_active(): - print("\n💡 检测到SteamVR正在运行,将使用直连模式") - print(" 在直连模式下,画面将直接通过SteamVR传输到头显") - print(" 无需通过ALVR服务器进行额外的串流处理") - - # 初始化ALVR串流 - print("\n📡 初始化ALVR串流...") - print("💡 检测到您已连接VR设备,正在尝试连接ALVR服务器...") - alvr_success = world.initializeALVR() - - if alvr_success: - print("✓ ALVR串流初始化成功") - - # 设置串流质量 - print("\n🎥 配置串流质量...") - quality_set = world.setALVRStreamQuality(1920, 1080, 60, 50) - if quality_set: - print(" ✓ 串流质量设置成功: 1920x1080@60fps, 50Mbps") - - # 获取串流状态 - status = world.getALVRStreamingStatus() - print(f"\n📊 ALVR串流初始状态:") - print(f" - 连接状态: {status['connected']}") - print(f" - 串流状态: {status['streaming']}") - print(f" - 分辨率: {status['resolution']}") - print(f" - 帧率: {status['fps']}") - print(f" - 码率: {status['bitrate']}Mbps") - - # 开始串流 - print(f"\n🚀 开始ALVR串流...") - if world.startALVRStreaming(): - print("✓ ALVR串流已开始") - - # 运行串流测试 - print(f"\n📡 ALVR串流测试运行中... (10秒)") - start_time = time.time() - frame_count = 0 - - while time.time() - start_time < 10: - # 更新串流状态 - current_status = world.getALVRStreamingStatus() - - # 每2秒显示一次状态更新 - if frame_count % 120 == 0: # 约每2秒 - elapsed = time.time() - start_time - print(f" [{elapsed:.1f}s] FPS: {current_status['fps']}, 延迟: {current_status['latency']}ms") - - if hasattr(world, 'taskMgr'): - world.taskMgr.step() - frame_count += 1 - time.sleep(0.016) - - # 停止串流 - print("\n🛑 停止ALVR串流...") - world.stopALVRStreaming() - print("✓ ALVR串流已停止") - - else: - print("⚠ ALVR串流启动失败") - print(" 可能原因:") - print(" - ALVR服务器未运行") - print(" - VR客户端未连接") - print(" - 网络连接问题") - - else: - print("⚠ ALVR串流初始化失败") - print("💡 根据您提供的信息,您已经可以通过SteamVR看到画面,但串流测试无法连接到ALVR服务器") - print("\n💡 直连模式说明:") - print(" 如果您使用的是有线连接(如Pico 4通过USB-C连接)") - print(" 并且SteamVR已经可以正常显示画面,您实际上已经在使用直连模式") - print(" SteamVR会直接处理渲染并将画面传输到头显,无需额外的串流步骤") - print("\n🔧 建议操作:") - print(" 1. 确认SteamVR是否正常工作并显示画面") - print(" 2. 如果正常工作,您无需进行额外的ALVR串流设置") - print(" 3. 如果需要使用ALVR的额外功能,可以尝试:") - print(" - 关闭SteamVR") - print(" - 确保ALVR服务器正在运行") - print(" - 重新运行测试") - - # 提供替代方案 - print("\n🔄 替代方案:") - print(" 您可以尝试运行:") - print(" python3 vr_display_demo.py") - print(" 并选择'2. ALVR串流演示'来测试串流功能") - - # 关闭系统 - world.shutdownALVR() - world.shutdownVR() - print("\n✓ 所有系统已关闭") - - return True - - -def test_vr_streaming_detailed(): - """详细测试VR串流功能""" - print("=== VR串流详细功能测试 ===") - - # 创建世界实例 - world = MyWorld() - - # 初始化VR系统 - print("🥽 初始化VR系统...") - if not world.initializeVR(): - print("✗ VR系统初始化失败") - return False - - print("✓ VR系统初始化成功") - - # 检查VR纹理是否正确创建 - print("\n🔍 检查VR纹理...") - if hasattr(world.vr_manager, 'left_eye_texture') and world.vr_manager.left_eye_texture: - print(" ✓ 左眼纹理已创建") - print(f" 纹理尺寸: {world.vr_manager.left_eye_texture.getXSize()}x{world.vr_manager.left_eye_texture.getYSize()}") - else: - print(" ✗ 左眼纹理未创建") - - if hasattr(world.vr_manager, 'right_eye_texture') and world.vr_manager.right_eye_texture: - print(" ✓ 右眼纹理已创建") - print(f" 纹理尺寸: {world.vr_manager.right_eye_texture.getXSize()}x{world.vr_manager.right_eye_texture.getYSize()}") - else: - print(" ✗ 右眼纹理未创建") - - # 初始化ALVR串流 - print("\n📡 初始化ALVR串流...") - alvr_success = world.initializeALVR() - - if alvr_success: - print("✓ ALVR串流初始化成功") - - # 测试多种串流质量设置 - print("\n🎥 测试多种串流质量设置:") - quality_settings = [ - (1280, 720, 60, 25), - (1920, 1080, 60, 50), - (1920, 1080, 30, 25) - ] - - for width, height, fps, bitrate in quality_settings: - success = world.setALVRStreamQuality(width, height, fps, bitrate) - if success: - status = world.getALVRStreamingStatus() - print(f" ✓ 设置 {width}x{height}@{fps}fps, {bitrate}Mbps: 成功") - print(f" 实际分辨率: {status['resolution']}") - else: - print(f" ✗ 设置 {width}x{height}@{fps}fps, {bitrate}Mbps: 失败") - - # 测试串流控制 - print("\n🎮 测试串流控制:") - - # 开始串流 - if world.startALVRStreaming(): - print(" ✓ 串流开始成功") - - # 检查串流状态 - status = world.getALVRStreamingStatus() - print(f" 串流状态: {status['streaming']}") - print(f" 连接状态: {status['connected']}") - - # 等待2秒 - time.sleep(2) - - # 再次检查状态 - status = world.getALVRStreamingStatus() - print(f" 2秒后状态 - FPS: {status['fps']}, 延迟: {status['latency']}ms") - - # 停止串流 - world.stopALVRStreaming() - status = world.getALVRStreamingStatus() - print(f" ✓ 串流停止成功") - print(f" 串流状态: {status['streaming']}") - - else: - print(" ✗ 串流开始失败") - - else: - print("⚠ ALVR串流初始化失败") - print("\n🔧 Pico 4连接故障排除:") - print(" 1. 确认ALVR服务器正在运行") - print(" 2. 检查USB连接是否正常") - print(" 3. 确认Pico 4上已安装并启动ALVR客户端") - print(" 4. 检查防火墙设置") - print(" 5. 确认SteamVR可以识别到头显") - - # 关闭系统 - world.shutdownALVR() - world.shutdownVR() - print("\n✓ 所有系统已关闭") - - return True - - -def main(): - """主函数""" - print("🎮 VR串流测试脚本") - print("="*50) - - print("\n请选择测试类型:") - print("1. 基本串流测试") - print("2. 详细串流测试") - - try: - choice = input("请输入选择 (1-2): ") - - if choice == "1": - success = test_vr_streaming() - elif choice == "2": - success = test_vr_streaming_detailed() - else: - print("无效的选择") - return - - print("\n" + "="*50) - if success: - print("✓ 串流测试成功完成") - else: - print("✗ 串流测试遇到问题") - - except KeyboardInterrupt: - print("\n用户中断测试") - except Exception as e: - print(f"测试过程中发生错误: {str(e)}") - import traceback - traceback.print_exc() - - -if __name__ == "__main__": - main() \ 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 diff --git a/参考文档.md b/参考文档.md new file mode 100644 index 00000000..6563da06 --- /dev/null +++ b/参考文档.md @@ -0,0 +1 @@ +API:https://docs.panda3d.org/1.10/python/reference/index