From bb56813e447b6c1c02cccd70e79088f379f0b09a Mon Sep 17 00:00:00 2001 From: Rowland <975945824@qq.com> Date: Tue, 29 Jul 2025 10:17:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E4=BC=98=E5=8C=96=20ALVR=20?= =?UTF-8?q?=E4=B8=B2=E6=B5=81=E5=8A=9F=E8=83=BD=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9=20Pico=204=20=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加对有线连接的支持,特别是 Pico 4 的有线连接 - 改进 ALVR 服务器检测和连接逻辑 - 优化 VR 帧获取和处理流程 - 增加备用帧获取功能(主摄像机视图) - 修复 VR 眼部纹理相关问题 - 优化 VR 任务管理,确保正确渲染和提交帧 --- .vscode/settings.json | 3 + CLAUDE.md | 102 ++++ RenderPipelineFile/rpcore/render_pipeline.py | 3 +- .../toolkit/day_time_editor/curve_widget.py | 5 +- .../toolkit/day_time_editor/main.py | 4 +- __pycache__/main.cpython-310.pyc | Bin 25562 -> 25562 bytes .../vr_display_optimizer.cpython-310.pyc | Bin 0 -> 8695 bytes .../__pycache__/alvr_streamer.cpython-310.pyc | Bin 11703 -> 15362 bytes core/__pycache__/simple_vr.cpython-310.pyc | Bin 0 -> 6173 bytes core/__pycache__/vr_manager.cpython-310.pyc | Bin 14273 -> 15929 bytes core/__pycache__/world.cpython-310.pyc | Bin 12349 -> 12334 bytes core/alvr_streamer.py | 272 ++++++++-- core/vr_manager.py | 135 ++++- core/world.py | 2 +- direct_vr_streaming_test.py | 197 +++++++ 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 +++++++++ 24 files changed, 3131 insertions(+), 76 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 CLAUDE.md create mode 100644 __pycache__/vr_display_optimizer.cpython-310.pyc create mode 100644 core/__pycache__/simple_vr.cpython-310.pyc create mode 100644 direct_vr_streaming_test.py create mode 100644 vr/__init__.py create mode 100644 vr/devices/__init__.py create mode 100644 vr/vr_display.py create mode 100644 vr/vr_input.py create mode 100644 vr/vr_system.py create mode 100644 vr_display_demo.py create mode 100644 vr_display_optimizer.py create mode 100644 vr_quick_start.py create mode 100644 vr_streaming_test.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..95bf679f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "Lingma.aI Chat.webToolsInAsk/AgentMode": "Auto-execute" +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..1489fa62 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,102 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +This is a Python-based 3D engine editor built with Panda3D and PyQt5. It integrates a comprehensive render pipeline (RenderPipelineFile) and provides VR support through ALVR streaming. The application follows a modular architecture with separate systems for world management, GUI, VR, scripting, and project management. + +## Development Commands + +This project is a Python application without npm/package.json build commands. Key development commands: + +```bash +# Run the main application +python3 main.py + +# Run specific demo/test files +python3 demo/basic_gui_demo.py +python3 demo/script_system_demo.py +python3 vr_test.py + +# Install dependencies +pip install -r requirements/requirements.txt +``` + +## Core Architecture + +### Main Application Structure +- **main.py**: Entry point - initializes MyWorld class which extends CoreWorld +- **MyWorld class**: Central coordinator that initializes all major systems: + - SelectionSystem, EventHandler, ToolManager, ScriptManager + - GUIManager, SceneManager, ProjectManager, PropertyPanelManager + - InterfaceManager, VRManager, VRInputHandler, ALVRStreamer + +### Key Modules + +1. **core/**: Core engine functionality + - `world.py`: CoreWorld base class extending Panda3DWorld + - `selection.py`: Object selection and transformation system + - `event_handler.py`: Event processing and input handling + - `tool_manager.py`: Tool system management + - `script_system.py`: Dynamic script loading and execution + - `vr_manager.py`, `vr_input_handler.py`, `alvr_streamer.py`: VR support + +2. **ui/**: User interface components + - `main_window.py`: MainWindow class using PyQt5 + - `widgets.py`: Custom PyQt5/Panda3D integration widgets + - `property_panel.py`: Object property editing interface + - `interface_manager.py`: UI state management + +3. **QPanda3D/**: Custom Panda3D-Qt integration + - `QPanda3DWidget.py`: Core Qt widget for embedding Panda3D + - `Panda3DWorld.py`: Base world class + - Translation modules for Qt/Panda3D event mapping + +4. **RenderPipelineFile/**: Advanced rendering pipeline + - Complete PBR rendering system with plugins + - Shader management, lighting, post-processing effects + - Material system and environment probes + +5. **scripts/**: User scripts directory + - Contains example scripts like BouncerScript.py, RotatorScript.py + - Scripts are dynamically loaded by the script system + +### VR Integration +- ALVR streaming support for wireless VR +- VR input handling with hand tracking +- VR-specific UI and interaction systems + +### Demo and Testing +- **demo/**: Extensive collection of test files and examples + - GUI testing examples (basic_gui_demo.py, etc.) + - VR testing and integration demos + - Picking and selection system tests + - Script system demonstrations + +## Key Dependencies +- **Panda3D 1.10.15**: Core 3D engine +- **PyQt5**: GUI framework +- **QPanda3D 0.2.10**: Qt-Panda3D integration library +- Various supporting libraries for VR, rendering, and utilities + +## Development Notes + +### Camera Controls +- Default camera speed: 20.0 units +- Default rotation speed: 10.0 degrees +- Mouse right-click for camera rotation + +### Coordinate System +The engine uses Panda3D's coordinate system with specific camera and world setups defined in CoreWorld. + +### Script System +Scripts in the `scripts/` directory are automatically discovered and can be loaded dynamically. The script system supports hot-reloading and provides a management interface. + +### Render Pipeline Integration +The project integrates a sophisticated render pipeline with: +- PBR materials and lighting +- Post-processing effects (bloom, SSAO, motion blur, FXAA, etc.) +- Environment mapping and IBL +- Shadow mapping (PSSM) +- Day/night cycle simulation \ No newline at end of file diff --git a/RenderPipelineFile/rpcore/render_pipeline.py b/RenderPipelineFile/rpcore/render_pipeline.py index f5fa7e34..46728df3 100644 --- a/RenderPipelineFile/rpcore/render_pipeline.py +++ b/RenderPipelineFile/rpcore/render_pipeline.py @@ -38,7 +38,8 @@ from direct.showbase.ShowBase import ShowBase from direct.stdpy.file import isfile from rplibs.yaml import load_yaml_file_flat -from six.moves import range # pylint: disable=import-error +from rplibs.six import moves # pylint: disable=import-error +range = moves.range from rpcore.globals import Globals from rpcore.effect import Effect diff --git a/RenderPipelineFile/toolkit/day_time_editor/curve_widget.py b/RenderPipelineFile/toolkit/day_time_editor/curve_widget.py index 249030af..6352c96e 100644 --- a/RenderPipelineFile/toolkit/day_time_editor/curve_widget.py +++ b/RenderPipelineFile/toolkit/day_time_editor/curve_widget.py @@ -29,11 +29,12 @@ from __future__ import print_function # from PyQt5 import Qt # from PyQt5.QtGui import QPainter, QColor, QPen # from PyQt5.QtWidgets import QWidget -from six.moves import range # pylint: disable=import-error +from rplibs.six import moves # pylint: disable=import-error +range = moves.range import math -from RenderPipelineFile.rplibs.pyqt_imports import * # noqa +from rplibs.pyqt_imports import * # noqa class CurveWidget(QWidget): diff --git a/RenderPipelineFile/toolkit/day_time_editor/main.py b/RenderPipelineFile/toolkit/day_time_editor/main.py index 3031b478..ff4d71ea 100644 --- a/RenderPipelineFile/toolkit/day_time_editor/main.py +++ b/RenderPipelineFile/toolkit/day_time_editor/main.py @@ -40,8 +40,8 @@ os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)))) # Add the render pipeline to the path sys.path.insert(0, "../../") -from RenderPipelineFile.rplibs.six import iteritems # noqa -from RenderPipelineFile.rplibs.pyqt_imports import * # noqa +from rplibs.six import iteritems # noqa +from rplibs.pyqt_imports import * # noqa from curve_widget import CurveWidget # noqa diff --git a/__pycache__/main.cpython-310.pyc b/__pycache__/main.cpython-310.pyc index c8e668c0e2a6cf8ad86085893dfbc8626cb3e1c7..4d7ef861f7b4caf8c59e5c282197305ca41b5264 100644 GIT binary patch delta 9459 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 delta 9474 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 H_l^Gtd%JJW diff --git a/__pycache__/vr_display_optimizer.cpython-310.pyc b/__pycache__/vr_display_optimizer.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..220c9d60b86fc70c1cbde0e55829ca2d96c0500e GIT binary patch literal 8695 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 literal 0 HcmV?d00001 diff --git a/core/__pycache__/alvr_streamer.cpython-310.pyc b/core/__pycache__/alvr_streamer.cpython-310.pyc index 4ef3703e4df9ed4013f9ae54a5798112b86d1726..d1b34ea0331a60538c1a38ab26720ad379631163 100644 GIT binary patch literal 15362 zcmai5S(F>qnXW38N+q>gy|}#sMyx?0wAlj0A*{wgmJWmaK%yC<$hy_tYOAI8t&&aa zs2wvl*no}Angs07%?=qb69|~q4>OaQlR5J?XO?=H2gd5n?Tc-hch>uS$1!Lc`z0rhQ{C<~8jDR2#~-2Exg!S~ z56$BF;_jzUo|tVsc3|P<2Ns`u6kSe!IJ5Ba&?IZ;9Yk%UU+-% z7az|*y?5^CGxP7CP{St{@j7iNqx;ECMd=j0A8s8-nlb#?uye0hm5y)UG4zAmC-S3C zftoJ;iv$Jte~@uBERRM@la1`tMzCNave(Aam?FAY!_rtHhPx$f5yw3y5~2%tTO>s{ z?s1V4J-8=Cujs?QOZ1BsxF^McSc!YLSS42Do)T-sTHJfYI&lW>z2Z!97Vdpwy*L~9 zevuaE;J!kfE6&4xK%6fwzdQS z8SZC@%f%JApDC^s-^Beaah13l_x0i$@h#lX-lv&bHjORY=qG7Iws_LXPdKu=0*kTC z{uRR2qD7;;5!~GuknyxV+KfJ{=~`W{YdNECAO(3LH3j9PB7)QsCeoOQBDIBuG|p*4 zXvlYQniMhQyM>K3CE`eXL;`8A=tA1ZZTdM~!F>lr63YMR}Wr_|I3R1dZDH*{u>hC%vZh2;=d!ixp+gDrWIeXP<0zVYw;;0 zS##|2tFDwfvQ@OvJ~{GudYJ`k>_6Ig`QgTary6e@nA?9ibA}&vtCNoO?Od){E_%6~ zAKh7zCE+J_$lOG}43d<7Dqlj$b>t35=8BVk@6z&QMS9s4>IrX8u{@gNhdqT#xeP)t zR?4}Fig5hIPLRLgNL{;VL!h+c!^snY;!!nw^@@5o+Q&LbhJ(>3$KU%=63x3~{$#`QRBe8V!;7LqY%Vo^p?;4Syvs|H4EqlJ{ z6(=0OyOejm9EQ$uv8zy((kb&3KUTt<%7rOEIw^}~FB9=i*C~z2ZtR3iA$e8zCQmL}xmRmOzD$?1oWDOlTI5{Pg=(!4)dpVMXZqa|FnW|j) zWJ#TBnKWuEm-5VDxWfe^lJJihgyi(ixZEd@)Gn!OGuo`?4tS8nM3yKULD``4-I|DO zp{Ef%JsYjewx=dDm^KroJ8Gjrt+fTr>{(N{>)JF%sr8~R=EdrG$1~c|VB{Zb!k*T5 zL85O7ay#{%S|-j6-z44^VOKB0l&RPh)pJK)TR3reVfMfuK0MHP^XS5{CmV;4H6D6p zaB#4?7L6g8=iUmQwRPNFJ+oQ8aN<{UkG$B}|7Kf@>RD*e!B`2{r%@1r z2WdPCmHnJPt8dco8Jsq1>mi4YacH%*OkO)M6EDzi{M=frO{Pb+P1+uijm*^bnP}aZ zWun}k0C{QS7Ot3Qk3^=f(LsQ^_G4{(VkacNobcixxP&4&VIPe!anqa6#hX{4HXgqF z84c2$=FzoQLq2&lAmZ(}w%##fOck4NxOFG7MfVoX>ze8Ey2g{Zx@V2)NIf!>5-FM? zay@lzycbvB2U-N!=7Z+YI>_X3!fj3r0e_6{3$cG->BiYdTtwVy>4=Tg$#< zRogqPW#907ykT|S3fZkrx~-2H8t>bF6ko80nRH-k5lk9mCXI`=-pYEcgGtv-$9Bc2 zwcs2x=^53tvBz`I@1A@1#Wuc~`^B-w;g=LwLNrDui^4a{kgtBMFd<6CvIFe~4I{5W z|2D!Q=|py-OII4neOQ)zx~5?S)%8hR=p?CjXm?=|w;OERMdUZp%x7yWTB_`HK=i&b zc&Q)B%h67%k6%0Ll%0Dg<#k)>1@tCk_j;{%c^grm%Cv*mmMVpO$-Qo{Ie@$povS43 zNkGsd{PtnUk92hakJ2ZPzeYpOKXbfs|62J&j!3z-6iS@9Vm>aS5>4#_> zc}0zq-dsHcEtlEGmLyx{Ta#|pE0+A^q^uMiD7B&oJHafLM=J7abjNa+r+h0vIq8&z z-#t{>QZ0<#q1wWfD^Q~9HsQcKBn{&4h-7@TVZTaEN z-^*z-9=uuh((9vzQU#_&A3YePWCN0n&EhYs%$Z>3 zj_aG0g~o=>;ak#it0gbnmoMRq%6YH2!(oGkn!(bMm(pnM(o<&eEQ`y%3yEQv5RZmF zXe4xV_bS~qj4w@d0Ake8FVqKgQ#Zb{jNV8>?TrLKh+vCNOFAX~T;M7J|5kIy}k(z?`}a0wBTyAR;U; zT1UH3Y}^fzDj?c+MFd)_91DP$($cJM+vt;Nc&PeqOyrZj@3pvN{;dz@KX`Wjkzo>$+bSFH{stthWQ?NVWkv`oIAi(t zBuq9C>jX$`%JrNHCR?V!B?MzK!N@I`xE!Wrx!wOdDh}du$y6hynbd8QcJ=hTcZ#-e z+r1dZA?d(2*&C!T-&Z!7>=Q6T)xg<(??2Itfa4FT3$JA z?XtFCAoxy{jCEbbe9$xXFTv#acxlG;qdSVisrb=+RTL|}4ezP2 z4d!JwHkOA8S#Z(B^dPJKu0p9uo{v)D@CjWQnAv0@KbhZJEWwU+TuI_E+uwX`4q|n( zQg)r{dQ^}a0>hqW>Q&NNpB(vl`s4?XqjqiNx0@1 z>1rIsbmN7;UU>UOnA=JSsOmOJ(G&vxFtLj+Y-cP#+}k`HReTHCALSJX3jP=NqQwhPg+xU|eJH22Fm2uN$ zX%lRrT8hx*-a%5kPTBv;7a%li6oLg}Y6C3tbZCu}qxqobnT&W5(q)qcXDD8={d(|mM?WtVU?l4lgw(gSz>L-kIBdU&w_+nS{InQ;;`OauX$FKT6VliWhI8+-6Ez zhMdyN*}h;^`AfldkUKx5hAk~!pdxv=@*X7rfvf4-l8u%E8>B$m=9dEzWtXL3l?ASC zhsAZuf)vO+5C2HaHZ88dM4@&*bdBZ$6dc#x1T=~cO=iqQ>Tq^v05`$`G`#4HB_hB; zpikIYM2{I8I?-6Ik@i%*K*sT@8$hNe+hYk~dR=ws$QIGV9)ml%3kW&Llygj`QQn`% zp+^^Y-#7Qp{<-^(bs`!=pGB+Q$R*c&W?Xwadn!vChg+0MDq%-L(Kp9kfC*7iodCvW z0rMhU&jPkkEdVH+gvyY&aXS))YRAq)S=rxFmUX_#GAtHKMGFT1C=iR*uCaN|yOu-W zKjLyp+!{s_;6ty`%e$5)Vf0mP9SQd)0MRCa+hh-UL(L8P9SXJ0tqqdF2GNrl7&F)$ zaG1L?*tQ5Zj5f{?=31D|B03#WCUM=^28FD8)v=u%$m8v(k)4UqSbgPGZ z0nDrzynmp}vml4N!N0u8Td~Q}V3T9mWS|Zmn;hMR;}70s9-ejoCHYKMNrrPwO zMdESkP(q&Ir?@mpUIB0U(x%IAIn>w=Swwr^s+OBuEJx@GeC)gcB>-XtC3J4MrIcHj z+Tk4SyDXzyHW_R_KZ3V5RxNK^mH>W=)?eUqi5wIGBqJttQLkY}lI%{Avy}Y2-&&%T z&T1!$4n5pP7okoff#bf0q;?IR2#k}#NcYgF2Fo77`2d}?2WJY>@&=)#-5cupUBd z!8zXhUWOYN?>{*I(HnG(_1a@6KYD!OjgL{xhR`@PG62I&6u3S^=2n1}M}TBY0z~CH zB*VqsA0T@zvOs8JPC37|?El(4l&a6ewv zH1$X<iC$DAcrztl8kCeZ6qne*4!lt3Heh> z@|3W=IYn1=1+!xs+)C}1lQ-W$#ab{4BknMYBBo)&W9R=r!L@_VFu$<=JJCxf67&QJ z(Sm{0)EVcr6P|i!8(P~&e8i3sTugjq4)S3XJRU`M!Z@f7r-%^E91f($O(HYG)wxA1 zXaOV)bvC!KRSQDUGiH^9w(^A4StJf60X_}mEYbom!X6#U#3kX{eq4dZ1Sng>>VRZL z2tkuoN+xK@k&@6M@O$2Y<$}{A!mSy<4$$akJ3`Z6)mZ>^;w6`$>RMXm6f%T`*o1u` zP3g4@+E-9{%Fr(g>kg6awpDCnVKRKN7MlD)?Ht^7xnAhdyTCiZzy`A{>tJSCGho^E z%*gE1qAhf?(!S;Mz?!kCTLacy4dyl5O+-EQ6&8*c8BASc8el`KNzLfA3w-LVIcFkViJl>+$Vm zP|Vsp9V5VoIG_4XAj#Sj?r4snrA|e$AGK~MTtIv<|A`P)x(+!i0b0jp zepNfKU3Dk|>=1QrP8*RXMrU&Tln__g{DBjNtu~bMWC6YK?z0QWUz>aK&AEebG#zdJ zfJf1-bJ~yN*~1w4Q=)aYAk5mi%TBIsu}(WT`6R|^pU=xLExi0-xEw6d+bWNf z_oO^3)N%>x6%4P-bL)nCF%TR$v!Mk|z7QBMhB2=oI~pMdRg`TnJ`Niit&CQ_whkHq zXRR~c6wiZPYNlrv0)WN}o|E;yFuvzy)}|Al}J(DrBds~= zjw)-0lpbW0!rVwjI?|~GbBfat4o%VrMuxbZoO5p>qkL9BNsAv2?|1Vighk~#Jc6O+ z^OSV3*~55t6szYF$VT`p&{K+c6QPrrah%bNM`y`})dRbq=fgQ>g=m$QQW0b0g?%7r5ab^|+S?o4~P15QlkFbz}rCPSex$6Ik>xMl(STZ>I3W zyd*EfPa^&cn1`Y!Y0@M3Lx|+^lhpLi`~-(|(wXu^1xE`VrdWXdcku^oMQNZIV-L<1Da3NxTm4D>_z3HOTC zW3aZ+64Vp{paQQJ3L^q9x7Eh3+h~I^f;M*DQf<(~nuTy>Fh$W`Y)XLk;!JzWV~e+p zdltiy>G-aAKzqmqv_}U%jl=s9$~FJ%hk>Mk_81Wfj;{%N=%{W|R)V*`ML6=W(Ae+U zUM&{3Z7xzM!Dd;R@RNngK!rCT;O+G-$4oY?+ z@h$fJ0F9u)a!;Z6&5=Ut9QLDho;byJMzc*=o9R|6o)C8BJT}FjLJn@D3W9&-I3?RC z=@4Z~XR>U{b}QF1fEEyA4$YiX0C2WD0D^&YKyWTgiw7`#9EQ1j4Kfr>K?s|LV@iur zIN%F^GUEv#b7cGj0ESXMmlddO_5yDRP6gJq+qQfz%kyeeoMiu##li0A4an3%9pj*q zs}VS!1IR|9GNd9&P^|%gv@I$D6%jU!xN!#4xbdYW?QAPM6$lVNNzp+BA01%;YiNeh%hCIx$fE;^qz zzo=c<{@JI)2u{BUH(|_xExQ?uB!8QAHjyr|qq_=EIBJML8fI^i4olcyCh1J=@Cys_ zrr=G&;Z?Zfu!{bnga1G5A3($dh2FB)rEFpR&Z4pJeHFY*!M)3c=5AID zatLn)v)qH5Y`Ufj3CmHHihm_+oT6<5675`I8ss`!VB!ce-fU2wg+(@DzG0d5+L>+3 z+~M$_esM{<67{(13I)BJjLmSKiOdvx(sza* zhUj=;3a9xl>N-T~) z`C|eUn#J5go8$TzMguF5aPzx?Pk6FYf)|iOfT%61ICY@NE@})=#3RXGI8u$Y&2|9e-yJ=B+(VG^+@8s)(+<{*SBrAq17}S z7cbCHkO^36EhCUDMTmuPnFzTeGdv2gipnsAq2T}yBadH}w4Xek(iR>wwar)xNM_G5 zfcN=uec0FU1w_xkH9~+1uNkw$y_l0c7m1%h1WBcY7|u$WPf~o7exbp^vj~%HF%XF* z?y(CO@i|q*FBf&!^LQk;Ry7j+iS9`f*(U%*o zKvWq1Q68hKMJK()A{jsdm`UgOGNVq6#mg=c^`(B9K$OYUsbC&;YNi%(;q6xz_Z=F7 zC)m=2YcQ}na^i*j28tmCHWjku<>l4sSd)Hoch#EYwpa6rk(!#O6(JE$+KvBVr~|}m z$5T)3*G6#k-Ax5X)`5p_VswB$QzZI*3$Da!VE zoDMw?lI^XHC*(#-ThI6{u>1pD7NIiuy#~ww``=pp`Ri&;RcI(bAaSD}B0fzCZa>k& zC)FXO?#F@%U$$%rt!(w9U{4;3js?%dXDjHOccv*IRM5f@)zXhL)VY;8UMZF(f!d+S z>~FpKbV9>+$Eu#F>@4r6JxilAtP6KV4PtC)oR9Y#BK8qGGDbLgjM6iJAmo+WbhJFzMboO6CM z$D9(>#Bw=-Af_DuBuZX}F*)d3ew}g@@1{f+DS34gObjaNoKfzkWDg~TCdfP!n>) zB?%{5EcaR@wr1){!@kHq)4sy)vv08b?QZ1Hvs3m;d%#{}$MJu$owm#^p<xBit(dr?|p_$o~OV1ouP$ delta 4870 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 literal 0 HcmV?d00001 diff --git a/core/__pycache__/vr_manager.cpython-310.pyc b/core/__pycache__/vr_manager.cpython-310.pyc index 25a3cebab470c260b9f28f6e501efc913b5b06c7..d1fccd6f8e024af4ab4906e41e869b70398cf216 100644 GIT binary patch delta 5737 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 XCm0xj6nG0NVgOF$f#;q=0^)xG5QZdA delta 4085 zcma)9eQXrh5#P7Fx3~9wHiz-q#@}CuF*e5EK&WD3LJG#%;3StQ$HlvtGk3Sn+qKPE zXGlU)S}7Hf*MJiGVX7!CN>fU&e~>g)MgBoZl`1u=nr@>iks2DL{?R`wwP~xSGw+Vr zzFw=^y4l(HX5PMeGxM8yZ)a~Ae#7T;3-Gt)kI$d@@X$qn4Vk#u(K;@t98m`mG(l66 zO3E2^a-JmBsJc(koSOQApsBj!tQ2)=WKxK@?%^V1wvw1h8&r=FTb+(u`iwy{z>_UE z{`@8(KVaV!FZ&ZhR)_^S4O(SvQd&?vpi8~%mr`BW2W&*71V?aVz^ll+m{Xo4qAr@VG&z^_ZDR%_rkl!bsahr>!uPCv7O>OQaK4BA!ev>9GacnftjqL`Yfg zGgmX&SdF4JNGg$pkun`x8OQ(oO~k#B_ty- zb~>l|y9wFOUI{dj0rqyFE>r=-QSa2mh>@Bz%!Fl7x{3WM(EPv}lX10GW2H6UC}r^$ z5I>h23qDEOxoux)mNWp*V}<>y>=E`-olp5bye$o|uJU6KTOQ4EmJs1l2*^o67A$XO zk_gK%vJ`|l#qwpzS#m}AmYA1%9ORbCX@b&uZjmes5tTVZd)Q>CgS{1MaEwL#5IP@( zYT8%L+YH~dou+P08q}h-_~xF`tt0v=Yo6+CtD|K!@1*~2$WBna0Y9@Ih$yO}n<$@q zB~{8ly^@!O!%ewM6^99Nvrji2+ZF;r+ntJ^)JNheoyIBdgx2Ax0^1okOuidhkHgh$ zp>m662gY@3&gs)uVnIKkr%f(7YAo1p&=}L>bTm%6F*sSY?MU8pww%PdQm)$tU%JEo zR{3NAlXC=q=C^??^|0yik>;$p;>s#l^FEQePu!U?@Cm>N_leW;vc9Ulo>9=D`;a`$ zUa0D6^-&)BV>ogM$v6;z5MwSgN{tld;ab5ytm+<&hQW@JdDAjd~t8A-8#RKl<}X?SW0UYa~v!gWykUt6;_`MYo37joKYG zdqJ8$gk=5F5eIF0xvnl;YSTwS+>;xun#o9g0&+00jdAeFj!R& zZ8?G1+NE4G0~5pSa?4+b=1^q)O4kYq`OJUQ zJ^>R^cDwa4Ad#T-e zYP|swB@gl3091qOYnZwjb4KiOVmQ4qKKS^v1ic$3?5Mg1DK=ZdABVC z1oa|*vI`xRp(3RtAbXy@+woC8P1m1g$uyk@jZ5s#)+v%@^_?S4%kW-OZ~-R13;Us` z*;hJ$(S>pa6sM<9r&Me%!o&~rI?rwEJ>!7aFVOrwcv;%J#tCjHuh8LXkiSWBnPdsN zVj1y5UU6I%vSLgDH#y;{!qWv$H)^k-^SoIR5v8Cb!MKm>NCh1Ub*4Z`TBGELNCu_+ zXQ8SGGRMHvGOr*(O|r_ea#2+$%kpWNZ<2Gos>|$+?uNZ(V9h5tzjycig+ILW`hEXL z%GbCbgE#6bl=7HG5r(7Q)#|=qV|TV6Im3}T>Ut=n$0iapC#<4SL68Q;e4ScF@=}MN z0l9ZDfSQ590Kx*JN<0%tklg1gh+nKH>a*hKORZBAxet1#N%?lrut~;t&QaYob&Uqu z!QPfK1i7)*j+xG~v%QfZF9^Iffw1V^4&C#Eq6_x-xjAVUw z+n)QYKwth3^elGt&_X~1;eZZ8bhFn77TWo0S_ySu!o-GNPsg85>ROmRIQX82I}d}G zR0%-$IKdd)Ue zA+#I6R=huUXt@7XKcqpJUPGaO2vM* zvziRD-|p1U;N>j;T|6<0Hx-JP5Q?n{#lD~JM1oBx#RiRHOGS?&=|j?w1bYOE<(6XA zq*z5LqBKR+rSnJ-Bq^dE#q6UmBKfLt2I$w2J&)u9l9!RZ0>t*kV$;dEX-@0OWGuGk z6ftcnUJR)dhzow^gFsY4CSK9qpw_6H)k@W+hSf57*1*%JZd3!RqB_)i)uUDe?^M0; zU*UzxcC}WO)eWi~l=*@YH=EeKi{08?cNB5i_Gk$TJ?=K%b0|0>nHiIduGtiKGH%LN ccclz%KB+$dfhd>=4XWS=gz-W%tCEoTZ)k&2pa1{> diff --git a/core/__pycache__/world.cpython-310.pyc b/core/__pycache__/world.cpython-310.pyc index 485744ac6ba9a4454d0b7750f024bd601ffcc319..f7fd572ef67b4d43dd2bfde74770bdd0765ddc2e 100644 GIT binary patch delta 342 zcmdm+ur7f&pO=@50SMlUv}b(Y$h(_~QEBr@CP_x765h>kn6EN3@=ZR(zHD*>hYq98 zjG9Fqi2JNIUvGd^DFsT5CbGO&sCfW(rT`}hKbQ?^Aptf{5+VZ9#ZDt_C e0Yr$x2?-F(ak8?WH>2a^e7&iRhMQUSEf@jdp;$%$ delta 375 zcmZ3Nus4A>pO=@50SL67H)VX>$h(_~QET%_CP_xF4u%E13mF(0O87QEVZO@D$Uk`( z`!aE{oW#<+$$iX84V||l@Z^(kb4i1xjBxPiwQ_&^3Q`x zOqLg10~9zTxDq5?Cal5&)G%B8K9Ex_QNhM&J^72=bpdOT$+JPk91vl+`L_HlhyfCt z`xIw_v}!1?VPdr2d{y-YBO6$`;pD^Wo{W~0c{QZjY(cv0ChKX`GFon4qw$ClXl}pO zY^cJ`2HIahrm5=A= 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 new file mode 100644 index 00000000..1365d9fc --- /dev/null +++ b/vr/vr_input.py @@ -0,0 +1,363 @@ +""" +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 new file mode 100644 index 00000000..8fc7bc2f --- /dev/null +++ b/vr/vr_system.py @@ -0,0 +1,380 @@ +""" +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 new file mode 100644 index 00000000..a117204c --- /dev/null +++ b/vr_display_demo.py @@ -0,0 +1,500 @@ +#!/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 new file mode 100644 index 00000000..082b2a72 --- /dev/null +++ b/vr_display_optimizer.py @@ -0,0 +1,316 @@ +#!/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 new file mode 100644 index 00000000..c6635ac4 --- /dev/null +++ b/vr_quick_start.py @@ -0,0 +1,263 @@ +#!/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 new file mode 100644 index 00000000..da9f5cf9 --- /dev/null +++ b/vr_streaming_test.py @@ -0,0 +1,264 @@ +#!/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