Browse Source
Add start screen UI, hand/body tracking, upgrade to Godot 4.6.1
Add start screen UI, hand/body tracking, upgrade to Godot 4.6.1
- Two-phase startup: CONFIG phase (VR start screen) -> AR phase (passthrough) - Start screen with server host/port input, connect button, and launch AR button - VR UI pointer with controller ray-pointing and hand poke interaction - Body tracking visualization with colored spheres (spine, head, arms) - Hand tracking with camera-based tracking (no controllers required) - Android flavor manifest for Quest hand/body tracking permissions - Upgrade OpenXR Vendors plugin from v3.1.2 to v4.3.0-stable - Export presets updated for Godot 4.6.1 Meta plugin format - Build scripts for Godot 4.6.1 with vendor plugin setup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>master
52 changed files with 1923 additions and 56 deletions
-
25.gitignore
-
108Main.gd
-
1Main.gd.uid
-
30Main.tscn
-
BINaddons/godotopenxrvendors/.bin/android/debug/godotopenxr-androidxr-debug.aar
-
BINaddons/godotopenxrvendors/.bin/android/debug/godotopenxr-khronos-debug.aar
-
BINaddons/godotopenxrvendors/.bin/android/debug/godotopenxr-lynx-debug.aar
-
BINaddons/godotopenxrvendors/.bin/android/debug/godotopenxr-magicleap-debug.aar
-
BINaddons/godotopenxrvendors/.bin/android/debug/godotopenxr-meta-debug.aar
-
BINaddons/godotopenxrvendors/.bin/android/debug/godotopenxr-pico-debug.aar
-
BINaddons/godotopenxrvendors/.bin/android/release/godotopenxr-androidxr-release.aar
-
BINaddons/godotopenxrvendors/.bin/android/release/godotopenxr-khronos-release.aar
-
BINaddons/godotopenxrvendors/.bin/android/release/godotopenxr-lynx-release.aar
-
BINaddons/godotopenxrvendors/.bin/android/release/godotopenxr-magicleap-release.aar
-
BINaddons/godotopenxrvendors/.bin/android/release/godotopenxr-meta-release.aar
-
BINaddons/godotopenxrvendors/.bin/android/release/godotopenxr-pico-release.aar
-
BINaddons/godotopenxrvendors/.bin/android/template_debug/arm64/libgodotopenxrvendors.so
-
BINaddons/godotopenxrvendors/.bin/android/template_debug/x86_64/libgodotopenxrvendors.so
-
BINaddons/godotopenxrvendors/.bin/android/template_release/arm64/libgodotopenxrvendors.so
-
BINaddons/godotopenxrvendors/.bin/android/template_release/x86_64/libgodotopenxrvendors.so
-
BINaddons/godotopenxrvendors/.bin/linux/template_debug/arm64/libgodotopenxrvendors.so
-
BINaddons/godotopenxrvendors/.bin/linux/template_debug/x86_64/libgodotopenxrvendors.so
-
BINaddons/godotopenxrvendors/.bin/linux/template_release/arm64/libgodotopenxrvendors.so
-
BINaddons/godotopenxrvendors/.bin/linux/template_release/x86_64/libgodotopenxrvendors.so
-
BINaddons/godotopenxrvendors/.bin/macos/template_debug/libgodotopenxrvendors.macos.framework/libgodotopenxrvendors.macos
-
BINaddons/godotopenxrvendors/.bin/macos/template_release/libgodotopenxrvendors.macos.framework/libgodotopenxrvendors.macos
-
BINaddons/godotopenxrvendors/.bin/windows/template_debug/x86_64/libgodotopenxrvendors.dll
-
BINaddons/godotopenxrvendors/.bin/windows/template_release/x86_64/libgodotopenxrvendors.dll
-
104addons/godotopenxrvendors/GodotOpenXRVendors_CHANGES.md
-
203addons/godotopenxrvendors/androidxr/LICENSE
-
203addons/godotopenxrvendors/meta/LICENSE-LOADER
-
0addons/godotopenxrvendors/meta/LICENSE-SDK
-
4addons/godotopenxrvendors/plugin.gdextension
-
1addons/godotopenxrvendors/plugin.gdextension.uid
-
47android/AndroidManifest.xml
-
32android/build/src/standard/AndroidManifest.xml
-
2build/build_461.bat
-
57build/install_461.py
-
34build/setup_android_template.py
-
45build/setup_vendor_plugin.py
-
14export_presets.cfg
-
304openxr_action_map.tres
-
17project.godot
-
112scenes/start_screen.tscn
-
86scripts/body_tracker.gd
-
1scripts/body_tracker.gd.uid
-
74scripts/start_screen.gd
-
1scripts/start_screen.gd.uid
-
1scripts/teleop_client.gd.uid
-
469scripts/vr_ui_pointer.gd
-
1scripts/vr_ui_pointer.gd.uid
-
1scripts/webcam_display.gd.uid
@ -0,0 +1 @@ |
|||||
|
uid://c4co4sxvbrqxn |
||||
@ -0,0 +1,203 @@ |
|||||
|
|
||||
|
Apache License |
||||
|
Version 2.0, January 2004 |
||||
|
http://www.apache.org/licenses/ |
||||
|
|
||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
|
||||
|
1. Definitions. |
||||
|
|
||||
|
"License" shall mean the terms and conditions for use, reproduction, |
||||
|
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
|
||||
|
"Licensor" shall mean the copyright owner or entity authorized by |
||||
|
the copyright owner that is granting the License. |
||||
|
|
||||
|
"Legal Entity" shall mean the union of the acting entity and all |
||||
|
other entities that control, are controlled by, or are under common |
||||
|
control with that entity. For the purposes of this definition, |
||||
|
"control" means (i) the power, direct or indirect, to cause the |
||||
|
direction or management of such entity, whether by contract or |
||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
|
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
|
||||
|
"You" (or "Your") shall mean an individual or Legal Entity |
||||
|
exercising permissions granted by this License. |
||||
|
|
||||
|
"Source" form shall mean the preferred form for making modifications, |
||||
|
including but not limited to software source code, documentation |
||||
|
source, and configuration files. |
||||
|
|
||||
|
"Object" form shall mean any form resulting from mechanical |
||||
|
transformation or translation of a Source form, including but |
||||
|
not limited to compiled object code, generated documentation, |
||||
|
and conversions to other media types. |
||||
|
|
||||
|
"Work" shall mean the work of authorship, whether in Source or |
||||
|
Object form, made available under the License, as indicated by a |
||||
|
copyright notice that is included in or attached to the work |
||||
|
(an example is provided in the Appendix below). |
||||
|
|
||||
|
"Derivative Works" shall mean any work, whether in Source or Object |
||||
|
form, that is based on (or derived from) the Work and for which the |
||||
|
editorial revisions, annotations, elaborations, or other modifications |
||||
|
represent, as a whole, an original work of authorship. For the purposes |
||||
|
of this License, Derivative Works shall not include works that remain |
||||
|
separable from, or merely link (or bind by name) to the interfaces of, |
||||
|
the Work and Derivative Works thereof. |
||||
|
|
||||
|
"Contribution" shall mean any work of authorship, including |
||||
|
the original version of the Work and any modifications or additions |
||||
|
to that Work or Derivative Works thereof, that is intentionally |
||||
|
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
|
or by an individual or Legal Entity authorized to submit on behalf of |
||||
|
the copyright owner. For the purposes of this definition, "submitted" |
||||
|
means any form of electronic, verbal, or written communication sent |
||||
|
to the Licensor or its representatives, including but not limited to |
||||
|
communication on electronic mailing lists, source code control systems, |
||||
|
and issue tracking systems that are managed by, or on behalf of, the |
||||
|
Licensor for the purpose of discussing and improving the Work, but |
||||
|
excluding communication that is conspicuously marked or otherwise |
||||
|
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
|
||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
|
on behalf of whom a Contribution has been received by Licensor and |
||||
|
subsequently incorporated within the Work. |
||||
|
|
||||
|
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
|
copyright license to reproduce, prepare Derivative Works of, |
||||
|
publicly display, publicly perform, sublicense, and distribute the |
||||
|
Work and such Derivative Works in Source or Object form. |
||||
|
|
||||
|
3. Grant of Patent License. Subject to the terms and conditions of |
||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
|
(except as stated in this section) patent license to make, have made, |
||||
|
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
|
where such license applies only to those patent claims licensable |
||||
|
by such Contributor that are necessarily infringed by their |
||||
|
Contribution(s) alone or by combination of their Contribution(s) |
||||
|
with the Work to which such Contribution(s) was submitted. If You |
||||
|
institute patent litigation against any entity (including a |
||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
|
or a Contribution incorporated within the Work constitutes direct |
||||
|
or contributory patent infringement, then any patent licenses |
||||
|
granted to You under this License for that Work shall terminate |
||||
|
as of the date such litigation is filed. |
||||
|
|
||||
|
4. Redistribution. You may reproduce and distribute copies of the |
||||
|
Work or Derivative Works thereof in any medium, with or without |
||||
|
modifications, and in Source or Object form, provided that You |
||||
|
meet the following conditions: |
||||
|
|
||||
|
(a) You must give any other recipients of the Work or |
||||
|
Derivative Works a copy of this License; and |
||||
|
|
||||
|
(b) You must cause any modified files to carry prominent notices |
||||
|
stating that You changed the files; and |
||||
|
|
||||
|
(c) You must retain, in the Source form of any Derivative Works |
||||
|
that You distribute, all copyright, patent, trademark, and |
||||
|
attribution notices from the Source form of the Work, |
||||
|
excluding those notices that do not pertain to any part of |
||||
|
the Derivative Works; and |
||||
|
|
||||
|
(d) If the Work includes a "NOTICE" text file as part of its |
||||
|
distribution, then any Derivative Works that You distribute must |
||||
|
include a readable copy of the attribution notices contained |
||||
|
within such NOTICE file, excluding those notices that do not |
||||
|
pertain to any part of the Derivative Works, in at least one |
||||
|
of the following places: within a NOTICE text file distributed |
||||
|
as part of the Derivative Works; within the Source form or |
||||
|
documentation, if provided along with the Derivative Works; or, |
||||
|
within a display generated by the Derivative Works, if and |
||||
|
wherever such third-party notices normally appear. The contents |
||||
|
of the NOTICE file are for informational purposes only and |
||||
|
do not modify the License. You may add Your own attribution |
||||
|
notices within Derivative Works that You distribute, alongside |
||||
|
or as an addendum to the NOTICE text from the Work, provided |
||||
|
that such additional attribution notices cannot be construed |
||||
|
as modifying the License. |
||||
|
|
||||
|
You may add Your own copyright statement to Your modifications and |
||||
|
may provide additional or different license terms and conditions |
||||
|
for use, reproduction, or distribution of Your modifications, or |
||||
|
for any such Derivative Works as a whole, provided Your use, |
||||
|
reproduction, and distribution of the Work otherwise complies with |
||||
|
the conditions stated in this License. |
||||
|
|
||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
|
any Contribution intentionally submitted for inclusion in the Work |
||||
|
by You to the Licensor shall be under the terms and conditions of |
||||
|
this License, without any additional terms or conditions. |
||||
|
Notwithstanding the above, nothing herein shall supersede or modify |
||||
|
the terms of any separate license agreement you may have executed |
||||
|
with Licensor regarding such Contributions. |
||||
|
|
||||
|
6. Trademarks. This License does not grant permission to use the trade |
||||
|
names, trademarks, service marks, or product names of the Licensor, |
||||
|
except as required for reasonable and customary use in describing the |
||||
|
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
|
||||
|
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
|
agreed to in writing, Licensor provides the Work (and each |
||||
|
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
|
implied, including, without limitation, any warranties or conditions |
||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
|
appropriateness of using or redistributing the Work and assume any |
||||
|
risks associated with Your exercise of permissions under this License. |
||||
|
|
||||
|
8. Limitation of Liability. In no event and under no legal theory, |
||||
|
whether in tort (including negligence), contract, or otherwise, |
||||
|
unless required by applicable law (such as deliberate and grossly |
||||
|
negligent acts) or agreed to in writing, shall any Contributor be |
||||
|
liable to You for damages, including any direct, indirect, special, |
||||
|
incidental, or consequential damages of any character arising as a |
||||
|
result of this License or out of the use or inability to use the |
||||
|
Work (including but not limited to damages for loss of goodwill, |
||||
|
work stoppage, computer failure or malfunction, or any and all |
||||
|
other commercial damages or losses), even if such Contributor |
||||
|
has been advised of the possibility of such damages. |
||||
|
|
||||
|
9. Accepting Warranty or Additional Liability. While redistributing |
||||
|
the Work or Derivative Works thereof, You may choose to offer, |
||||
|
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
|
or other liability obligations and/or rights consistent with this |
||||
|
License. However, in accepting such obligations, You may act only |
||||
|
on Your own behalf and on Your sole responsibility, not on behalf |
||||
|
of any other Contributor, and only if You agree to indemnify, |
||||
|
defend, and hold each Contributor harmless for any liability |
||||
|
incurred by, or claims asserted against, such Contributor by reason |
||||
|
of your accepting any such warranty or additional liability. |
||||
|
|
||||
|
END OF TERMS AND CONDITIONS |
||||
|
|
||||
|
APPENDIX: How to apply the Apache License to your work. |
||||
|
|
||||
|
To apply the Apache License to your work, attach the following |
||||
|
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
|
replaced with your own identifying information. (Don't include |
||||
|
the brackets!) The text should be enclosed in the appropriate |
||||
|
comment syntax for the file format. We also recommend that a |
||||
|
file or class name and description of purpose be included on the |
||||
|
same "printed page" as the copyright notice for easier |
||||
|
identification within third-party archives. |
||||
|
|
||||
|
Copyright [yyyy] [name of copyright owner] |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
@ -0,0 +1,203 @@ |
|||||
|
|
||||
|
Apache License |
||||
|
Version 2.0, January 2004 |
||||
|
http://www.apache.org/licenses/ |
||||
|
|
||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
|
||||
|
1. Definitions. |
||||
|
|
||||
|
"License" shall mean the terms and conditions for use, reproduction, |
||||
|
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
|
||||
|
"Licensor" shall mean the copyright owner or entity authorized by |
||||
|
the copyright owner that is granting the License. |
||||
|
|
||||
|
"Legal Entity" shall mean the union of the acting entity and all |
||||
|
other entities that control, are controlled by, or are under common |
||||
|
control with that entity. For the purposes of this definition, |
||||
|
"control" means (i) the power, direct or indirect, to cause the |
||||
|
direction or management of such entity, whether by contract or |
||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
|
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
|
||||
|
"You" (or "Your") shall mean an individual or Legal Entity |
||||
|
exercising permissions granted by this License. |
||||
|
|
||||
|
"Source" form shall mean the preferred form for making modifications, |
||||
|
including but not limited to software source code, documentation |
||||
|
source, and configuration files. |
||||
|
|
||||
|
"Object" form shall mean any form resulting from mechanical |
||||
|
transformation or translation of a Source form, including but |
||||
|
not limited to compiled object code, generated documentation, |
||||
|
and conversions to other media types. |
||||
|
|
||||
|
"Work" shall mean the work of authorship, whether in Source or |
||||
|
Object form, made available under the License, as indicated by a |
||||
|
copyright notice that is included in or attached to the work |
||||
|
(an example is provided in the Appendix below). |
||||
|
|
||||
|
"Derivative Works" shall mean any work, whether in Source or Object |
||||
|
form, that is based on (or derived from) the Work and for which the |
||||
|
editorial revisions, annotations, elaborations, or other modifications |
||||
|
represent, as a whole, an original work of authorship. For the purposes |
||||
|
of this License, Derivative Works shall not include works that remain |
||||
|
separable from, or merely link (or bind by name) to the interfaces of, |
||||
|
the Work and Derivative Works thereof. |
||||
|
|
||||
|
"Contribution" shall mean any work of authorship, including |
||||
|
the original version of the Work and any modifications or additions |
||||
|
to that Work or Derivative Works thereof, that is intentionally |
||||
|
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
|
or by an individual or Legal Entity authorized to submit on behalf of |
||||
|
the copyright owner. For the purposes of this definition, "submitted" |
||||
|
means any form of electronic, verbal, or written communication sent |
||||
|
to the Licensor or its representatives, including but not limited to |
||||
|
communication on electronic mailing lists, source code control systems, |
||||
|
and issue tracking systems that are managed by, or on behalf of, the |
||||
|
Licensor for the purpose of discussing and improving the Work, but |
||||
|
excluding communication that is conspicuously marked or otherwise |
||||
|
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
|
||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
|
on behalf of whom a Contribution has been received by Licensor and |
||||
|
subsequently incorporated within the Work. |
||||
|
|
||||
|
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
|
copyright license to reproduce, prepare Derivative Works of, |
||||
|
publicly display, publicly perform, sublicense, and distribute the |
||||
|
Work and such Derivative Works in Source or Object form. |
||||
|
|
||||
|
3. Grant of Patent License. Subject to the terms and conditions of |
||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
|
(except as stated in this section) patent license to make, have made, |
||||
|
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
|
where such license applies only to those patent claims licensable |
||||
|
by such Contributor that are necessarily infringed by their |
||||
|
Contribution(s) alone or by combination of their Contribution(s) |
||||
|
with the Work to which such Contribution(s) was submitted. If You |
||||
|
institute patent litigation against any entity (including a |
||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
|
or a Contribution incorporated within the Work constitutes direct |
||||
|
or contributory patent infringement, then any patent licenses |
||||
|
granted to You under this License for that Work shall terminate |
||||
|
as of the date such litigation is filed. |
||||
|
|
||||
|
4. Redistribution. You may reproduce and distribute copies of the |
||||
|
Work or Derivative Works thereof in any medium, with or without |
||||
|
modifications, and in Source or Object form, provided that You |
||||
|
meet the following conditions: |
||||
|
|
||||
|
(a) You must give any other recipients of the Work or |
||||
|
Derivative Works a copy of this License; and |
||||
|
|
||||
|
(b) You must cause any modified files to carry prominent notices |
||||
|
stating that You changed the files; and |
||||
|
|
||||
|
(c) You must retain, in the Source form of any Derivative Works |
||||
|
that You distribute, all copyright, patent, trademark, and |
||||
|
attribution notices from the Source form of the Work, |
||||
|
excluding those notices that do not pertain to any part of |
||||
|
the Derivative Works; and |
||||
|
|
||||
|
(d) If the Work includes a "NOTICE" text file as part of its |
||||
|
distribution, then any Derivative Works that You distribute must |
||||
|
include a readable copy of the attribution notices contained |
||||
|
within such NOTICE file, excluding those notices that do not |
||||
|
pertain to any part of the Derivative Works, in at least one |
||||
|
of the following places: within a NOTICE text file distributed |
||||
|
as part of the Derivative Works; within the Source form or |
||||
|
documentation, if provided along with the Derivative Works; or, |
||||
|
within a display generated by the Derivative Works, if and |
||||
|
wherever such third-party notices normally appear. The contents |
||||
|
of the NOTICE file are for informational purposes only and |
||||
|
do not modify the License. You may add Your own attribution |
||||
|
notices within Derivative Works that You distribute, alongside |
||||
|
or as an addendum to the NOTICE text from the Work, provided |
||||
|
that such additional attribution notices cannot be construed |
||||
|
as modifying the License. |
||||
|
|
||||
|
You may add Your own copyright statement to Your modifications and |
||||
|
may provide additional or different license terms and conditions |
||||
|
for use, reproduction, or distribution of Your modifications, or |
||||
|
for any such Derivative Works as a whole, provided Your use, |
||||
|
reproduction, and distribution of the Work otherwise complies with |
||||
|
the conditions stated in this License. |
||||
|
|
||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
|
any Contribution intentionally submitted for inclusion in the Work |
||||
|
by You to the Licensor shall be under the terms and conditions of |
||||
|
this License, without any additional terms or conditions. |
||||
|
Notwithstanding the above, nothing herein shall supersede or modify |
||||
|
the terms of any separate license agreement you may have executed |
||||
|
with Licensor regarding such Contributions. |
||||
|
|
||||
|
6. Trademarks. This License does not grant permission to use the trade |
||||
|
names, trademarks, service marks, or product names of the Licensor, |
||||
|
except as required for reasonable and customary use in describing the |
||||
|
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
|
||||
|
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
|
agreed to in writing, Licensor provides the Work (and each |
||||
|
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
|
implied, including, without limitation, any warranties or conditions |
||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
|
appropriateness of using or redistributing the Work and assume any |
||||
|
risks associated with Your exercise of permissions under this License. |
||||
|
|
||||
|
8. Limitation of Liability. In no event and under no legal theory, |
||||
|
whether in tort (including negligence), contract, or otherwise, |
||||
|
unless required by applicable law (such as deliberate and grossly |
||||
|
negligent acts) or agreed to in writing, shall any Contributor be |
||||
|
liable to You for damages, including any direct, indirect, special, |
||||
|
incidental, or consequential damages of any character arising as a |
||||
|
result of this License or out of the use or inability to use the |
||||
|
Work (including but not limited to damages for loss of goodwill, |
||||
|
work stoppage, computer failure or malfunction, or any and all |
||||
|
other commercial damages or losses), even if such Contributor |
||||
|
has been advised of the possibility of such damages. |
||||
|
|
||||
|
9. Accepting Warranty or Additional Liability. While redistributing |
||||
|
the Work or Derivative Works thereof, You may choose to offer, |
||||
|
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
|
or other liability obligations and/or rights consistent with this |
||||
|
License. However, in accepting such obligations, You may act only |
||||
|
on Your own behalf and on Your sole responsibility, not on behalf |
||||
|
of any other Contributor, and only if You agree to indemnify, |
||||
|
defend, and hold each Contributor harmless for any liability |
||||
|
incurred by, or claims asserted against, such Contributor by reason |
||||
|
of your accepting any such warranty or additional liability. |
||||
|
|
||||
|
END OF TERMS AND CONDITIONS |
||||
|
|
||||
|
APPENDIX: How to apply the Apache License to your work. |
||||
|
|
||||
|
To apply the Apache License to your work, attach the following |
||||
|
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
|
replaced with your own identifying information. (Don't include |
||||
|
the brackets!) The text should be enclosed in the appropriate |
||||
|
comment syntax for the file format. We also recommend that a |
||||
|
file or class name and description of purpose be included on the |
||||
|
same "printed page" as the copyright notice for easier |
||||
|
identification within third-party archives. |
||||
|
|
||||
|
Copyright [yyyy] [name of copyright owner] |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
@ -0,0 +1 @@ |
|||||
|
uid://u32t56vkch04 |
||||
@ -1,35 +1,38 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<!-- Additional manifest entries for Quest 3 body tracking. |
|
||||
These are merged into the final AndroidManifest.xml by Godot's build system |
|
||||
when using the Meta OpenXR Vendors plugin. |
|
||||
|
|
||||
Key extensions enabled: |
|
||||
- XR_FB_body_tracking (70 joints including chest) |
|
||||
- XR_META_body_tracking_full_body (v4.1.1+) |
|
||||
- XR_EXT_hand_tracking (fallback, if body tracking unavailable) |
|
||||
- Passthrough (mixed reality) |
|
||||
--> |
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> |
|
||||
|
|
||||
<!-- Required permissions --> |
|
||||
|
<!-- User manifest overlay - merged with the build template manifest. |
||||
|
Adds Quest-specific features that the Godot 4.6.1 export plugin |
||||
|
does not inject automatically. --> |
||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
xmlns:tools="http://schemas.android.com/tools"> |
||||
|
|
||||
|
<!-- Network permissions for WebSocket connection to robot server --> |
||||
<uses-permission android:name="android.permission.INTERNET" /> |
<uses-permission android:name="android.permission.INTERNET" /> |
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> |
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> |
||||
|
|
||||
<!-- Meta Quest features --> |
|
||||
<uses-feature android:name="com.oculus.feature.PASSTHROUGH" android:required="true" /> |
|
||||
<uses-feature android:name="oculus.software.body_tracking" android:required="true" /> |
|
||||
|
<!-- Hand tracking support - prevents "need controllers" dialog --> |
||||
<uses-feature android:name="oculus.software.handtracking" android:required="false" /> |
<uses-feature android:name="oculus.software.handtracking" android:required="false" /> |
||||
|
|
||||
|
<!-- Passthrough AR mode --> |
||||
|
<uses-feature android:name="com.oculus.feature.PASSTHROUGH" android:required="true" /> |
||||
|
|
||||
|
<!-- Body tracking --> |
||||
|
<uses-feature android:name="oculus.software.body_tracking" android:required="false" /> |
||||
|
|
||||
|
<!-- VR head tracking - override template's required=false --> |
||||
|
<uses-feature |
||||
|
android:name="android.hardware.vr.headtracking" |
||||
|
android:required="true" |
||||
|
android:version="1" |
||||
|
tools:replace="android:required" /> |
||||
|
|
||||
<application> |
<application> |
||||
<!-- Body tracking metadata --> |
|
||||
<meta-data android:name="com.oculus.supportedDevices" android:value="quest3|quest3s|questpro" /> |
|
||||
<meta-data android:name="com.oculus.intent.category.VR" android:value="vr_only" /> |
|
||||
|
<!-- Hand tracking configuration --> |
||||
|
<meta-data android:name="com.oculus.handtracking.frequency" android:value="HIGH" /> |
||||
|
<meta-data android:name="com.oculus.handtracking.version" android:value="V2.0" /> |
||||
|
|
||||
<!-- Enable body tracking API --> |
|
||||
|
<!-- Body tracking configuration --> |
||||
<meta-data android:name="com.oculus.bodytracking.enabled" android:value="true" /> |
<meta-data android:name="com.oculus.bodytracking.enabled" android:value="true" /> |
||||
|
|
||||
<!-- Request full body tracking (includes upper body by default) --> |
|
||||
<meta-data android:name="com.oculus.bodytracking.full_body" android:value="true" /> |
<meta-data android:name="com.oculus.bodytracking.full_body" android:value="true" /> |
||||
</application> |
</application> |
||||
|
|
||||
|
|||||
@ -0,0 +1,32 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Quest-specific manifest entries merged via Android Gradle Plugin's |
||||
|
manifest merger. Placed in 'standard' product flavor so Godot's export |
||||
|
(which only overwrites src/main/AndroidManifest.xml) won't touch it. --> |
||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
|
|
||||
|
<!-- Hand tracking permission - required for camera-based hand tracking access --> |
||||
|
<uses-permission android:name="com.oculus.permission.HAND_TRACKING" /> |
||||
|
|
||||
|
<!-- Body tracking permission --> |
||||
|
<uses-permission android:name="com.oculus.permission.BODY_TRACKING" /> |
||||
|
|
||||
|
<!-- Hand tracking support - prevents Quest "need controllers" dialog --> |
||||
|
<uses-feature android:name="oculus.software.handtracking" android:required="false" /> |
||||
|
|
||||
|
<!-- Passthrough AR mode --> |
||||
|
<uses-feature android:name="com.oculus.feature.PASSTHROUGH" android:required="true" /> |
||||
|
|
||||
|
<!-- Body tracking --> |
||||
|
<uses-feature android:name="oculus.software.body_tracking" android:required="false" /> |
||||
|
|
||||
|
<application> |
||||
|
<!-- Hand tracking configuration --> |
||||
|
<meta-data android:name="com.oculus.handtracking.frequency" android:value="HIGH" /> |
||||
|
<meta-data android:name="com.oculus.handtracking.version" android:value="V2.0" /> |
||||
|
|
||||
|
<!-- Body tracking configuration --> |
||||
|
<meta-data android:name="com.oculus.bodytracking.enabled" android:value="true" /> |
||||
|
<meta-data android:name="com.oculus.bodytracking.full_body" android:value="true" /> |
||||
|
</application> |
||||
|
|
||||
|
</manifest> |
||||
@ -0,0 +1,2 @@ |
|||||
|
@echo off |
||||
|
"C:\git\g1-teleop\Godot4_6_1\Godot_v4.6.1-stable_win64_console.exe" --headless --quit --path "C:\git\g1-teleop" --export-debug "Quest 3" "C:\git\g1-teleop\build\g1-teleop.apk" |
||||
@ -0,0 +1,57 @@ |
|||||
|
"""Install Godot 4.6.1 export templates and OpenXR vendors plugin v4.3.0.""" |
||||
|
import zipfile |
||||
|
import os |
||||
|
import shutil |
||||
|
|
||||
|
# 1. Install OpenXR vendors plugin v4.3.0 |
||||
|
plugin_zip = r"C:\git\g1-teleop\build\godotopenxrvendors_v4.3.0.zip" |
||||
|
addons_dir = r"C:\git\g1-teleop\addons" |
||||
|
old_plugin = os.path.join(addons_dir, "godotopenxrvendors") |
||||
|
|
||||
|
if os.path.exists(plugin_zip): |
||||
|
print(f"[1/2] Installing OpenXR vendors plugin v4.3.0...") |
||||
|
# Remove old plugin |
||||
|
if os.path.exists(old_plugin): |
||||
|
print(f" Removing old plugin at {old_plugin}") |
||||
|
shutil.rmtree(old_plugin) |
||||
|
# Extract new plugin - the zip contains addons/godotopenxrvendors/ |
||||
|
with zipfile.ZipFile(plugin_zip, 'r') as z: |
||||
|
# List top-level to understand structure |
||||
|
names = z.namelist() |
||||
|
print(f" Zip contains {len(names)} files") |
||||
|
if names[0].startswith("addons/"): |
||||
|
# Extract directly to project root |
||||
|
z.extractall(r"C:\git\g1-teleop") |
||||
|
print(" Extracted to project root (addons/ prefix)") |
||||
|
else: |
||||
|
# Extract to addons dir |
||||
|
z.extractall(addons_dir) |
||||
|
print(f" Extracted to {addons_dir}") |
||||
|
print(" Done!") |
||||
|
else: |
||||
|
print(f"[1/2] Plugin zip not found: {plugin_zip}") |
||||
|
|
||||
|
# 2. Install export templates |
||||
|
tpz_file = r"C:\Users\John\AppData\Roaming\Godot\export_templates\godot461_templates.tpz" |
||||
|
templates_dir = r"C:\Users\John\AppData\Roaming\Godot\export_templates\4.6.1.stable" |
||||
|
|
||||
|
if os.path.exists(tpz_file): |
||||
|
print(f"[2/2] Installing export templates...") |
||||
|
os.makedirs(templates_dir, exist_ok=True) |
||||
|
with zipfile.ZipFile(tpz_file, 'r') as z: |
||||
|
names = z.namelist() |
||||
|
print(f" TPZ contains {len(names)} files") |
||||
|
# TPZ typically has templates/ prefix |
||||
|
for name in names: |
||||
|
if name.startswith("templates/") and not name.endswith("/"): |
||||
|
# Strip the templates/ prefix |
||||
|
dest_name = name[len("templates/"):] |
||||
|
dest_path = os.path.join(templates_dir, dest_name) |
||||
|
os.makedirs(os.path.dirname(dest_path), exist_ok=True) |
||||
|
with z.open(name) as src, open(dest_path, 'wb') as dst: |
||||
|
shutil.copyfileobj(src, dst) |
||||
|
print(f" Extracted to {templates_dir}") |
||||
|
print(" Done!") |
||||
|
else: |
||||
|
print(f"[2/2] Export templates not yet downloaded: {tpz_file}") |
||||
|
print(" Run this script again after download completes.") |
||||
@ -0,0 +1,34 @@ |
|||||
|
"""Manually install android build template from export templates.""" |
||||
|
import zipfile |
||||
|
import os |
||||
|
|
||||
|
source_zip = r"C:\Users\John\AppData\Roaming\Godot\export_templates\4.6.1.stable\android_source.zip" |
||||
|
dest_dir = r"C:\git\g1-teleop\android\build" |
||||
|
|
||||
|
if not os.path.exists(source_zip): |
||||
|
print(f"ERROR: android_source.zip not found at {source_zip}") |
||||
|
exit(1) |
||||
|
|
||||
|
os.makedirs(dest_dir, exist_ok=True) |
||||
|
|
||||
|
with zipfile.ZipFile(source_zip, 'r') as z: |
||||
|
names = z.namelist() |
||||
|
print(f"android_source.zip contains {len(names)} files") |
||||
|
# Show top-level structure |
||||
|
top = set() |
||||
|
for n in names: |
||||
|
parts = n.split('/') |
||||
|
if parts[0]: |
||||
|
top.add(parts[0]) |
||||
|
print(f"Top-level entries: {sorted(top)}") |
||||
|
|
||||
|
z.extractall(dest_dir) |
||||
|
print(f"Extracted to {dest_dir}") |
||||
|
|
||||
|
# Update build version |
||||
|
version_file = r"C:\git\g1-teleop\android\.build_version" |
||||
|
with open(version_file, 'w') as f: |
||||
|
f.write("4.6.1.stable\n") |
||||
|
print(f"Updated {version_file} to 4.6.1.stable") |
||||
|
|
||||
|
print("\nDone!") |
||||
@ -0,0 +1,45 @@ |
|||||
|
"""Copy OpenXR vendors plugin files to the gradle build directory.""" |
||||
|
import shutil |
||||
|
import os |
||||
|
|
||||
|
project = r"C:\git\g1-teleop" |
||||
|
build_dir = os.path.join(project, "android", "build") |
||||
|
addons = os.path.join(project, "addons", "godotopenxrvendors") |
||||
|
|
||||
|
# 1. Copy Meta AAR to libs/debug and libs/plugins/debug |
||||
|
for d in ["libs/debug", "libs/plugins/debug"]: |
||||
|
dest = os.path.join(build_dir, d) |
||||
|
os.makedirs(dest, exist_ok=True) |
||||
|
src = os.path.join(addons, ".bin", "android", "debug", "godotopenxr-meta-debug.aar") |
||||
|
if os.path.exists(src): |
||||
|
shutil.copy2(src, dest) |
||||
|
print(f"Copied meta debug AAR to {dest}") |
||||
|
|
||||
|
for d in ["libs/release", "libs/plugins/release"]: |
||||
|
dest = os.path.join(build_dir, d) |
||||
|
os.makedirs(dest, exist_ok=True) |
||||
|
src = os.path.join(addons, ".bin", "android", "release", "godotopenxr-meta-release.aar") |
||||
|
if os.path.exists(src): |
||||
|
shutil.copy2(src, dest) |
||||
|
print(f"Copied meta release AAR to {dest}") |
||||
|
|
||||
|
# 2. Copy libgodotopenxrvendors.so to libs/debug/arm64-v8a |
||||
|
for build_type in ["debug", "release"]: |
||||
|
template = "template_debug" if build_type == "debug" else "template_release" |
||||
|
so_dir = os.path.join(build_dir, "libs", build_type, "arm64-v8a") |
||||
|
os.makedirs(so_dir, exist_ok=True) |
||||
|
src = os.path.join(addons, ".bin", "android", template, "arm64", "libgodotopenxrvendors.so") |
||||
|
if os.path.exists(src): |
||||
|
shutil.copy2(src, so_dir) |
||||
|
print(f"Copied libgodotopenxrvendors.so to {so_dir}") |
||||
|
|
||||
|
# 3. Copy the gdextension file to assets so it's included in the APK |
||||
|
assets_addons = os.path.join(build_dir, "src", "main", "assets", "addons", "godotopenxrvendors") |
||||
|
os.makedirs(assets_addons, exist_ok=True) |
||||
|
for f in ["plugin.gdextension", "plugin.gdextension.uid"]: |
||||
|
src = os.path.join(addons, f) |
||||
|
if os.path.exists(src): |
||||
|
shutil.copy2(src, assets_addons) |
||||
|
print(f"Copied {f} to assets") |
||||
|
|
||||
|
print("\nDone! Now rebuild the APK.") |
||||
@ -1,3 +1,307 @@ |
|||||
[gd_resource type="OpenXRActionMap" format=3 uid="uid://openxr_actions"] |
[gd_resource type="OpenXRActionMap" format=3 uid="uid://openxr_actions"] |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_aim"] |
||||
|
resource_name = "aim_pose" |
||||
|
localized_name = "Aim Pose" |
||||
|
action_type = 3 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_grip"] |
||||
|
resource_name = "grip_pose" |
||||
|
localized_name = "Grip Pose" |
||||
|
action_type = 3 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_palm"] |
||||
|
resource_name = "palm_pose" |
||||
|
localized_name = "Palm Pose" |
||||
|
action_type = 3 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_trigger"] |
||||
|
resource_name = "trigger" |
||||
|
localized_name = "Trigger" |
||||
|
action_type = 0 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_trigger_click"] |
||||
|
resource_name = "trigger_click" |
||||
|
localized_name = "Trigger Click" |
||||
|
action_type = 1 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_trigger_touch"] |
||||
|
resource_name = "trigger_touch" |
||||
|
localized_name = "Trigger Touch" |
||||
|
action_type = 1 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_grip_val"] |
||||
|
resource_name = "grip" |
||||
|
localized_name = "Grip" |
||||
|
action_type = 0 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_grip_click"] |
||||
|
resource_name = "grip_click" |
||||
|
localized_name = "Grip Click" |
||||
|
action_type = 1 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_menu"] |
||||
|
resource_name = "menu_button" |
||||
|
localized_name = "Menu Button" |
||||
|
action_type = 1 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_select"] |
||||
|
resource_name = "select_button" |
||||
|
localized_name = "Select Button" |
||||
|
action_type = 1 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_ax"] |
||||
|
resource_name = "ax_button" |
||||
|
localized_name = "A/X Button" |
||||
|
action_type = 1 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_by"] |
||||
|
resource_name = "by_button" |
||||
|
localized_name = "B/Y Button" |
||||
|
action_type = 1 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_ax_touch"] |
||||
|
resource_name = "ax_touch" |
||||
|
localized_name = "A/X Touch" |
||||
|
action_type = 1 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_by_touch"] |
||||
|
resource_name = "by_touch" |
||||
|
localized_name = "B/Y Touch" |
||||
|
action_type = 1 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_thumbstick"] |
||||
|
resource_name = "thumbstick" |
||||
|
localized_name = "Thumbstick" |
||||
|
action_type = 2 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_thumbstick_click"] |
||||
|
resource_name = "thumbstick_click" |
||||
|
localized_name = "Thumbstick Click" |
||||
|
action_type = 1 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_thumbstick_touch"] |
||||
|
resource_name = "thumbstick_touch" |
||||
|
localized_name = "Thumbstick Touch" |
||||
|
action_type = 1 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRAction" id="OpenXRAction_haptic"] |
||||
|
resource_name = "haptic" |
||||
|
localized_name = "Haptic" |
||||
|
action_type = 4 |
||||
|
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") |
||||
|
|
||||
|
[sub_resource type="OpenXRActionSet" id="OpenXRActionSet_godot"] |
||||
|
resource_name = "godot" |
||||
|
localized_name = "Godot Action Set" |
||||
|
priority = 0 |
||||
|
actions = [SubResource("OpenXRAction_aim"), SubResource("OpenXRAction_grip"), SubResource("OpenXRAction_palm"), SubResource("OpenXRAction_trigger"), SubResource("OpenXRAction_trigger_click"), SubResource("OpenXRAction_trigger_touch"), SubResource("OpenXRAction_grip_val"), SubResource("OpenXRAction_grip_click"), SubResource("OpenXRAction_menu"), SubResource("OpenXRAction_select"), SubResource("OpenXRAction_ax"), SubResource("OpenXRAction_by"), SubResource("OpenXRAction_ax_touch"), SubResource("OpenXRAction_by_touch"), SubResource("OpenXRAction_thumbstick"), SubResource("OpenXRAction_thumbstick_click"), SubResource("OpenXRAction_thumbstick_touch"), SubResource("OpenXRAction_haptic")] |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="simple_aim"] |
||||
|
action = SubResource("OpenXRAction_aim") |
||||
|
paths = PackedStringArray("/user/hand/left/input/aim/pose", "/user/hand/right/input/aim/pose") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="simple_grip"] |
||||
|
action = SubResource("OpenXRAction_grip") |
||||
|
paths = PackedStringArray("/user/hand/left/input/grip/pose", "/user/hand/right/input/grip/pose") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="simple_select"] |
||||
|
action = SubResource("OpenXRAction_select") |
||||
|
paths = PackedStringArray("/user/hand/left/input/select/click", "/user/hand/right/input/select/click") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="simple_menu"] |
||||
|
action = SubResource("OpenXRAction_menu") |
||||
|
paths = PackedStringArray("/user/hand/left/input/menu/click", "/user/hand/right/input/menu/click") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="simple_haptic"] |
||||
|
action = SubResource("OpenXRAction_haptic") |
||||
|
paths = PackedStringArray("/user/hand/left/output/haptic", "/user/hand/right/output/haptic") |
||||
|
|
||||
|
[sub_resource type="OpenXRInteractionProfile" id="profile_simple"] |
||||
|
interaction_profile_path = "/interaction_profiles/khr/simple_controller" |
||||
|
bindings = [SubResource("simple_aim"), SubResource("simple_grip"), SubResource("simple_select"), SubResource("simple_menu"), SubResource("simple_haptic")] |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_aim"] |
||||
|
action = SubResource("OpenXRAction_aim") |
||||
|
paths = PackedStringArray("/user/hand/left/input/aim/pose", "/user/hand/right/input/aim/pose") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_grip"] |
||||
|
action = SubResource("OpenXRAction_grip") |
||||
|
paths = PackedStringArray("/user/hand/left/input/grip/pose", "/user/hand/right/input/grip/pose") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_trigger"] |
||||
|
action = SubResource("OpenXRAction_trigger") |
||||
|
paths = PackedStringArray("/user/hand/left/input/trigger/value", "/user/hand/right/input/trigger/value") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_trigger_click"] |
||||
|
action = SubResource("OpenXRAction_trigger_click") |
||||
|
paths = PackedStringArray("/user/hand/left/input/trigger/value", "/user/hand/right/input/trigger/value") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_trigger_touch"] |
||||
|
action = SubResource("OpenXRAction_trigger_touch") |
||||
|
paths = PackedStringArray("/user/hand/left/input/trigger/touch", "/user/hand/right/input/trigger/touch") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_grip_val"] |
||||
|
action = SubResource("OpenXRAction_grip_val") |
||||
|
paths = PackedStringArray("/user/hand/left/input/squeeze/value", "/user/hand/right/input/squeeze/value") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_grip_click"] |
||||
|
action = SubResource("OpenXRAction_grip_click") |
||||
|
paths = PackedStringArray("/user/hand/left/input/squeeze/value", "/user/hand/right/input/squeeze/value") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_menu"] |
||||
|
action = SubResource("OpenXRAction_menu") |
||||
|
paths = PackedStringArray("/user/hand/left/input/menu/click") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_ax"] |
||||
|
action = SubResource("OpenXRAction_ax") |
||||
|
paths = PackedStringArray("/user/hand/left/input/x/click", "/user/hand/right/input/a/click") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_by"] |
||||
|
action = SubResource("OpenXRAction_by") |
||||
|
paths = PackedStringArray("/user/hand/left/input/y/click", "/user/hand/right/input/b/click") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_ax_touch"] |
||||
|
action = SubResource("OpenXRAction_ax_touch") |
||||
|
paths = PackedStringArray("/user/hand/left/input/x/touch", "/user/hand/right/input/a/touch") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_by_touch"] |
||||
|
action = SubResource("OpenXRAction_by_touch") |
||||
|
paths = PackedStringArray("/user/hand/left/input/y/touch", "/user/hand/right/input/b/touch") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_thumbstick"] |
||||
|
action = SubResource("OpenXRAction_thumbstick") |
||||
|
paths = PackedStringArray("/user/hand/left/input/thumbstick", "/user/hand/right/input/thumbstick") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_thumbstick_click"] |
||||
|
action = SubResource("OpenXRAction_thumbstick_click") |
||||
|
paths = PackedStringArray("/user/hand/left/input/thumbstick/click", "/user/hand/right/input/thumbstick/click") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_thumbstick_touch"] |
||||
|
action = SubResource("OpenXRAction_thumbstick_touch") |
||||
|
paths = PackedStringArray("/user/hand/left/input/thumbstick/touch", "/user/hand/right/input/thumbstick/touch") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_haptic"] |
||||
|
action = SubResource("OpenXRAction_haptic") |
||||
|
paths = PackedStringArray("/user/hand/left/output/haptic", "/user/hand/right/output/haptic") |
||||
|
|
||||
|
[sub_resource type="OpenXRInteractionProfile" id="profile_touch"] |
||||
|
interaction_profile_path = "/interaction_profiles/oculus/touch_controller" |
||||
|
bindings = [SubResource("touch_aim"), SubResource("touch_grip"), SubResource("touch_trigger"), SubResource("touch_trigger_click"), SubResource("touch_trigger_touch"), SubResource("touch_grip_val"), SubResource("touch_grip_click"), SubResource("touch_menu"), SubResource("touch_ax"), SubResource("touch_by"), SubResource("touch_ax_touch"), SubResource("touch_by_touch"), SubResource("touch_thumbstick"), SubResource("touch_thumbstick_click"), SubResource("touch_thumbstick_touch"), SubResource("touch_haptic")] |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_aim"] |
||||
|
action = SubResource("OpenXRAction_aim") |
||||
|
paths = PackedStringArray("/user/hand/left/input/aim/pose", "/user/hand/right/input/aim/pose") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_grip"] |
||||
|
action = SubResource("OpenXRAction_grip") |
||||
|
paths = PackedStringArray("/user/hand/left/input/grip/pose", "/user/hand/right/input/grip/pose") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_trigger"] |
||||
|
action = SubResource("OpenXRAction_trigger") |
||||
|
paths = PackedStringArray("/user/hand/left/input/trigger/value", "/user/hand/right/input/trigger/value") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_trigger_click"] |
||||
|
action = SubResource("OpenXRAction_trigger_click") |
||||
|
paths = PackedStringArray("/user/hand/left/input/trigger/value", "/user/hand/right/input/trigger/value") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_trigger_touch"] |
||||
|
action = SubResource("OpenXRAction_trigger_touch") |
||||
|
paths = PackedStringArray("/user/hand/left/input/trigger/touch", "/user/hand/right/input/trigger/touch") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_grip_val"] |
||||
|
action = SubResource("OpenXRAction_grip_val") |
||||
|
paths = PackedStringArray("/user/hand/left/input/squeeze/value", "/user/hand/right/input/squeeze/value") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_grip_click"] |
||||
|
action = SubResource("OpenXRAction_grip_click") |
||||
|
paths = PackedStringArray("/user/hand/left/input/squeeze/value", "/user/hand/right/input/squeeze/value") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_menu"] |
||||
|
action = SubResource("OpenXRAction_menu") |
||||
|
paths = PackedStringArray("/user/hand/left/input/menu/click") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_ax"] |
||||
|
action = SubResource("OpenXRAction_ax") |
||||
|
paths = PackedStringArray("/user/hand/left/input/x/click", "/user/hand/right/input/a/click") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_by"] |
||||
|
action = SubResource("OpenXRAction_by") |
||||
|
paths = PackedStringArray("/user/hand/left/input/y/click", "/user/hand/right/input/b/click") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_ax_touch"] |
||||
|
action = SubResource("OpenXRAction_ax_touch") |
||||
|
paths = PackedStringArray("/user/hand/left/input/x/touch", "/user/hand/right/input/a/touch") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_by_touch"] |
||||
|
action = SubResource("OpenXRAction_by_touch") |
||||
|
paths = PackedStringArray("/user/hand/left/input/y/touch", "/user/hand/right/input/b/touch") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_thumbstick"] |
||||
|
action = SubResource("OpenXRAction_thumbstick") |
||||
|
paths = PackedStringArray("/user/hand/left/input/thumbstick", "/user/hand/right/input/thumbstick") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_thumbstick_click"] |
||||
|
action = SubResource("OpenXRAction_thumbstick_click") |
||||
|
paths = PackedStringArray("/user/hand/left/input/thumbstick/click", "/user/hand/right/input/thumbstick/click") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_thumbstick_touch"] |
||||
|
action = SubResource("OpenXRAction_thumbstick_touch") |
||||
|
paths = PackedStringArray("/user/hand/left/input/thumbstick/touch", "/user/hand/right/input/thumbstick/touch") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="touch_plus_haptic"] |
||||
|
action = SubResource("OpenXRAction_haptic") |
||||
|
paths = PackedStringArray("/user/hand/left/output/haptic", "/user/hand/right/output/haptic") |
||||
|
|
||||
|
[sub_resource type="OpenXRInteractionProfile" id="profile_touch_plus"] |
||||
|
interaction_profile_path = "/interaction_profiles/meta/touch_controller_plus" |
||||
|
bindings = [SubResource("touch_plus_aim"), SubResource("touch_plus_grip"), SubResource("touch_plus_trigger"), SubResource("touch_plus_trigger_click"), SubResource("touch_plus_trigger_touch"), SubResource("touch_plus_grip_val"), SubResource("touch_plus_grip_click"), SubResource("touch_plus_menu"), SubResource("touch_plus_ax"), SubResource("touch_plus_by"), SubResource("touch_plus_ax_touch"), SubResource("touch_plus_by_touch"), SubResource("touch_plus_thumbstick"), SubResource("touch_plus_thumbstick_click"), SubResource("touch_plus_thumbstick_touch"), SubResource("touch_plus_haptic")] |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="hand_aim"] |
||||
|
action = SubResource("OpenXRAction_aim") |
||||
|
paths = PackedStringArray("/user/hand/left/input/aim/pose", "/user/hand/right/input/aim/pose") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="hand_grip"] |
||||
|
action = SubResource("OpenXRAction_grip") |
||||
|
paths = PackedStringArray("/user/hand/left/input/grip/pose", "/user/hand/right/input/grip/pose") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="hand_trigger"] |
||||
|
action = SubResource("OpenXRAction_trigger") |
||||
|
paths = PackedStringArray("/user/hand/left/input/pinch_ext/value", "/user/hand/right/input/pinch_ext/value") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="hand_trigger_click"] |
||||
|
action = SubResource("OpenXRAction_trigger_click") |
||||
|
paths = PackedStringArray("/user/hand/left/input/pinch_ext/ready_ext", "/user/hand/right/input/pinch_ext/ready_ext") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="hand_grip_val"] |
||||
|
action = SubResource("OpenXRAction_grip_val") |
||||
|
paths = PackedStringArray("/user/hand/left/input/grasp_ext/value", "/user/hand/right/input/grasp_ext/value") |
||||
|
|
||||
|
[sub_resource type="OpenXRIPBinding" id="hand_grip_click"] |
||||
|
action = SubResource("OpenXRAction_grip_click") |
||||
|
paths = PackedStringArray("/user/hand/left/input/grasp_ext/ready_ext", "/user/hand/right/input/grasp_ext/ready_ext") |
||||
|
|
||||
|
[sub_resource type="OpenXRInteractionProfile" id="profile_hand_interaction"] |
||||
|
interaction_profile_path = "/interaction_profiles/ext/hand_interaction_ext" |
||||
|
bindings = [SubResource("hand_aim"), SubResource("hand_grip"), SubResource("hand_trigger"), SubResource("hand_trigger_click"), SubResource("hand_grip_val"), SubResource("hand_grip_click")] |
||||
|
|
||||
[resource] |
[resource] |
||||
|
action_sets = [SubResource("OpenXRActionSet_godot")] |
||||
|
interaction_profiles = [SubResource("profile_simple"), SubResource("profile_touch"), SubResource("profile_touch_plus"), SubResource("profile_hand_interaction")] |
||||
@ -0,0 +1,112 @@ |
|||||
|
[gd_scene load_steps=6 format=3 uid="uid://start_screen_01"] |
||||
|
|
||||
|
[ext_resource type="Script" path="res://scripts/start_screen.gd" id="1"] |
||||
|
|
||||
|
[sub_resource type="QuadMesh" id="QuadMesh_1"] |
||||
|
size = Vector2(0.8, 0.6) |
||||
|
|
||||
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"] |
||||
|
shading_mode = 0 |
||||
|
albedo_color = Color(0.15, 0.15, 0.2, 1) |
||||
|
|
||||
|
[sub_resource type="Theme" id="Theme_1"] |
||||
|
default_font_size = 28 |
||||
|
|
||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1"] |
||||
|
bg_color = Color(0.12, 0.12, 0.18, 1) |
||||
|
corner_radius_top_left = 12 |
||||
|
corner_radius_top_right = 12 |
||||
|
corner_radius_bottom_right = 12 |
||||
|
corner_radius_bottom_left = 12 |
||||
|
|
||||
|
[node name="StartScreen" type="Node3D"] |
||||
|
script = ExtResource("1") |
||||
|
|
||||
|
[node name="UIMesh" type="MeshInstance3D" parent="."] |
||||
|
mesh = SubResource("QuadMesh_1") |
||||
|
material_override = SubResource("StandardMaterial3D_1") |
||||
|
|
||||
|
[node name="SubViewport" type="SubViewport" parent="UIMesh"] |
||||
|
transparent_bg = false |
||||
|
handle_input_locally = true |
||||
|
size = Vector2i(1024, 768) |
||||
|
render_target_update_mode = 3 |
||||
|
|
||||
|
[node name="PanelContainer" type="PanelContainer" parent="UIMesh/SubViewport"] |
||||
|
anchors_preset = 15 |
||||
|
anchor_right = 1.0 |
||||
|
anchor_bottom = 1.0 |
||||
|
theme = SubResource("Theme_1") |
||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_1") |
||||
|
|
||||
|
[node name="MarginContainer" type="MarginContainer" parent="UIMesh/SubViewport/PanelContainer"] |
||||
|
layout_mode = 2 |
||||
|
theme_override_constants/margin_left = 60 |
||||
|
theme_override_constants/margin_top = 40 |
||||
|
theme_override_constants/margin_right = 60 |
||||
|
theme_override_constants/margin_bottom = 40 |
||||
|
|
||||
|
[node name="VBox" type="VBoxContainer" parent="UIMesh/SubViewport/PanelContainer/MarginContainer"] |
||||
|
layout_mode = 2 |
||||
|
theme_override_constants/separation = 20 |
||||
|
|
||||
|
[node name="Title" type="Label" parent="UIMesh/SubViewport/PanelContainer/MarginContainer/VBox"] |
||||
|
layout_mode = 2 |
||||
|
theme_override_font_sizes/font_size = 48 |
||||
|
text = "G1 Teleop" |
||||
|
horizontal_alignment = 1 |
||||
|
|
||||
|
[node name="HSeparator" type="HSeparator" parent="UIMesh/SubViewport/PanelContainer/MarginContainer/VBox"] |
||||
|
layout_mode = 2 |
||||
|
|
||||
|
[node name="ServerRow" type="HBoxContainer" parent="UIMesh/SubViewport/PanelContainer/MarginContainer/VBox"] |
||||
|
layout_mode = 2 |
||||
|
theme_override_constants/separation = 12 |
||||
|
|
||||
|
[node name="Label" type="Label" parent="UIMesh/SubViewport/PanelContainer/MarginContainer/VBox/ServerRow"] |
||||
|
layout_mode = 2 |
||||
|
custom_minimum_size = Vector2(180, 0) |
||||
|
text = "Server:" |
||||
|
|
||||
|
[node name="HostInput" type="LineEdit" parent="UIMesh/SubViewport/PanelContainer/MarginContainer/VBox/ServerRow"] |
||||
|
layout_mode = 2 |
||||
|
size_flags_horizontal = 3 |
||||
|
text = "10.0.0.64" |
||||
|
placeholder_text = "IP address or hostname" |
||||
|
virtual_keyboard_enabled = true |
||||
|
|
||||
|
[node name="PortRow" type="HBoxContainer" parent="UIMesh/SubViewport/PanelContainer/MarginContainer/VBox"] |
||||
|
layout_mode = 2 |
||||
|
theme_override_constants/separation = 12 |
||||
|
|
||||
|
[node name="Label" type="Label" parent="UIMesh/SubViewport/PanelContainer/MarginContainer/VBox/PortRow"] |
||||
|
layout_mode = 2 |
||||
|
custom_minimum_size = Vector2(180, 0) |
||||
|
text = "Port:" |
||||
|
|
||||
|
[node name="PortInput" type="LineEdit" parent="UIMesh/SubViewport/PanelContainer/MarginContainer/VBox/PortRow"] |
||||
|
layout_mode = 2 |
||||
|
size_flags_horizontal = 3 |
||||
|
text = "8765" |
||||
|
placeholder_text = "Port number" |
||||
|
virtual_keyboard_enabled = true |
||||
|
|
||||
|
[node name="ConnectButton" type="Button" parent="UIMesh/SubViewport/PanelContainer/MarginContainer/VBox"] |
||||
|
layout_mode = 2 |
||||
|
custom_minimum_size = Vector2(0, 60) |
||||
|
text = "Connect to Server" |
||||
|
|
||||
|
[node name="StatusLabel" type="Label" parent="UIMesh/SubViewport/PanelContainer/MarginContainer/VBox"] |
||||
|
layout_mode = 2 |
||||
|
theme_override_colors/font_color = Color(0.7, 0.7, 0.7, 1) |
||||
|
text = "Not connected" |
||||
|
horizontal_alignment = 1 |
||||
|
|
||||
|
[node name="HSeparator2" type="HSeparator" parent="UIMesh/SubViewport/PanelContainer/MarginContainer/VBox"] |
||||
|
layout_mode = 2 |
||||
|
|
||||
|
[node name="LaunchARButton" type="Button" parent="UIMesh/SubViewport/PanelContainer/MarginContainer/VBox"] |
||||
|
layout_mode = 2 |
||||
|
custom_minimum_size = Vector2(0, 70) |
||||
|
theme_override_font_sizes/font_size = 36 |
||||
|
text = "Launch AR" |
||||
@ -0,0 +1 @@ |
|||||
|
uid://1xnuuli2itfk |
||||
@ -0,0 +1,74 @@ |
|||||
|
extends Node3D |
||||
|
## VR start screen UI panel. |
||||
|
## Renders a 2D UI in a SubViewport on a QuadMesh in VR space. |
||||
|
## Allows user to enter server URL/port, connect, and launch AR mode. |
||||
|
|
||||
|
signal connect_requested(host: String, port: int) |
||||
|
signal launch_ar_requested() |
||||
|
|
||||
|
@onready var ui_mesh: MeshInstance3D = $UIMesh |
||||
|
@onready var viewport: SubViewport = $UIMesh/SubViewport |
||||
|
@onready var host_input: LineEdit = $UIMesh/SubViewport/PanelContainer/MarginContainer/VBox/ServerRow/HostInput |
||||
|
@onready var port_input: LineEdit = $UIMesh/SubViewport/PanelContainer/MarginContainer/VBox/PortRow/PortInput |
||||
|
@onready var connect_button: Button = $UIMesh/SubViewport/PanelContainer/MarginContainer/VBox/ConnectButton |
||||
|
@onready var status_label: Label = $UIMesh/SubViewport/PanelContainer/MarginContainer/VBox/StatusLabel |
||||
|
@onready var launch_ar_button: Button = $UIMesh/SubViewport/PanelContainer/MarginContainer/VBox/LaunchARButton |
||||
|
|
||||
|
var _is_connected: bool = false |
||||
|
|
||||
|
|
||||
|
func _ready() -> void: |
||||
|
add_to_group("start_screen") |
||||
|
connect_button.pressed.connect(_on_connect_pressed) |
||||
|
launch_ar_button.pressed.connect(_on_launch_ar_pressed) |
||||
|
|
||||
|
# Set up the mesh material to display the SubViewport |
||||
|
var material := StandardMaterial3D.new() |
||||
|
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED |
||||
|
material.albedo_texture = viewport.get_texture() |
||||
|
material.transparency = BaseMaterial3D.TRANSPARENCY_DISABLED |
||||
|
ui_mesh.material_override = material |
||||
|
|
||||
|
print("[StartScreen] Ready") |
||||
|
|
||||
|
|
||||
|
func _on_connect_pressed() -> void: |
||||
|
var host := host_input.text.strip_edges() |
||||
|
if host.is_empty(): |
||||
|
update_status("Please enter a server address") |
||||
|
return |
||||
|
|
||||
|
var port := int(port_input.text.strip_edges()) |
||||
|
if port <= 0 or port > 65535: |
||||
|
update_status("Invalid port number") |
||||
|
return |
||||
|
|
||||
|
update_status("Connecting to %s:%d..." % [host, port]) |
||||
|
connect_requested.emit(host, port) |
||||
|
|
||||
|
|
||||
|
func _on_launch_ar_pressed() -> void: |
||||
|
launch_ar_requested.emit() |
||||
|
|
||||
|
|
||||
|
func update_status(text: String) -> void: |
||||
|
status_label.text = text |
||||
|
|
||||
|
|
||||
|
func set_connected(connected: bool) -> void: |
||||
|
_is_connected = connected |
||||
|
if connected: |
||||
|
update_status("Connected!") |
||||
|
connect_button.text = "Disconnect" |
||||
|
else: |
||||
|
if connect_button.text == "Disconnect": |
||||
|
update_status("Disconnected") |
||||
|
connect_button.text = "Connect to Server" |
||||
|
|
||||
|
|
||||
|
func show_screen() -> void: |
||||
|
visible = true |
||||
|
|
||||
|
|
||||
|
func hide_screen() -> void: |
||||
|
visible = false |
||||
@ -0,0 +1 @@ |
|||||
|
uid://cd50pdphkb1do |
||||
@ -0,0 +1 @@ |
|||||
|
uid://cejwgl45w03x7 |
||||
@ -0,0 +1,469 @@ |
|||||
|
extends Node3D |
||||
|
## VR UI pointer with hand tracking visualization, controller ray-pointing, |
||||
|
## and finger poke interaction. Renders hand joints as spheres and shows |
||||
|
## controller placeholder meshes. |
||||
|
|
||||
|
@export var ray_length: float = 5.0 |
||||
|
@export var poke_threshold: float = 0.03 |
||||
|
@export var hover_distance: float = 0.15 |
||||
|
@export var laser_color: Color = Color(0.3, 0.6, 1.0, 0.6) |
||||
|
|
||||
|
var _xr_origin: XROrigin3D |
||||
|
var _camera: XRCamera3D |
||||
|
var _left_ctrl: XRController3D |
||||
|
var _right_ctrl: XRController3D |
||||
|
|
||||
|
var _laser_right: MeshInstance3D |
||||
|
var _laser_left: MeshInstance3D |
||||
|
var _reticle: MeshInstance3D |
||||
|
|
||||
|
# Hand joint visualization: _hand_joints[hand_idx][joint_idx] = MeshInstance3D |
||||
|
var _hand_joints: Array = [[], []] |
||||
|
var _ctrl_mesh_left: MeshInstance3D |
||||
|
var _ctrl_mesh_right: MeshInstance3D |
||||
|
|
||||
|
var _current_viewport: SubViewport = null |
||||
|
var _last_viewport_pos: Vector2 = Vector2.ZERO |
||||
|
var _is_pressing: bool = false |
||||
|
var _active_method: String = "" |
||||
|
var _log_timer: float = 0.0 |
||||
|
|
||||
|
const JOINT_COUNT := 26 |
||||
|
# Fingertip joint indices for XRHandTracker (thumb=5, index=10, middle=15, ring=20, pinky=25) |
||||
|
const TIP_JOINTS := [5, 10, 15, 20, 25] |
||||
|
const HAND_COLORS := [Color(0.3, 0.6, 1.0, 1.0), Color(0.3, 1.0, 0.6, 1.0)] |
||||
|
|
||||
|
# XRBodyTracker fallback: hand joint start indices and count |
||||
|
const BODY_LEFT_HAND_START := 18 |
||||
|
const BODY_RIGHT_HAND_START := 43 |
||||
|
const BODY_HAND_JOINT_COUNT := 25 |
||||
|
# Fingertip indices within 25-joint body tracker hand block |
||||
|
const BODY_TIP_JOINTS := [4, 9, 14, 19, 24] |
||||
|
# Index finger tip within body tracker hand block (for poke interaction) |
||||
|
const BODY_INDEX_TIP := 9 |
||||
|
|
||||
|
|
||||
|
func setup(xr_origin: XROrigin3D, camera: XRCamera3D, left_ctrl: XRController3D, right_ctrl: XRController3D) -> void: |
||||
|
_xr_origin = xr_origin |
||||
|
_camera = camera |
||||
|
_left_ctrl = left_ctrl |
||||
|
_right_ctrl = right_ctrl |
||||
|
|
||||
|
_left_ctrl.button_pressed.connect(_on_left_button_pressed) |
||||
|
_left_ctrl.button_released.connect(_on_left_button_released) |
||||
|
_right_ctrl.button_pressed.connect(_on_right_button_pressed) |
||||
|
_right_ctrl.button_released.connect(_on_right_button_released) |
||||
|
|
||||
|
# Create lasers (start hidden until controllers are active) |
||||
|
_laser_right = _create_laser() |
||||
|
_laser_right.visible = false |
||||
|
_right_ctrl.add_child(_laser_right) |
||||
|
_laser_left = _create_laser() |
||||
|
_laser_left.visible = false |
||||
|
_left_ctrl.add_child(_laser_left) |
||||
|
|
||||
|
# Reticle dot where pointer intersects UI |
||||
|
_reticle = MeshInstance3D.new() |
||||
|
var sphere := SphereMesh.new() |
||||
|
sphere.radius = 0.01 |
||||
|
sphere.height = 0.02 |
||||
|
_reticle.mesh = sphere |
||||
|
var rmat := StandardMaterial3D.new() |
||||
|
rmat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED |
||||
|
rmat.albedo_color = Color(1, 1, 1, 0.9) |
||||
|
rmat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA |
||||
|
rmat.no_depth_test = true |
||||
|
rmat.render_priority = 10 |
||||
|
_reticle.material_override = rmat |
||||
|
_reticle.visible = false |
||||
|
add_child(_reticle) |
||||
|
|
||||
|
# Hand joint spheres |
||||
|
_create_hand_visuals() |
||||
|
|
||||
|
# Controller placeholder meshes |
||||
|
_ctrl_mesh_left = _create_controller_mesh(_left_ctrl) |
||||
|
_ctrl_mesh_right = _create_controller_mesh(_right_ctrl) |
||||
|
|
||||
|
print("[VRUIPointer] Setup complete") |
||||
|
|
||||
|
|
||||
|
func _create_laser() -> MeshInstance3D: |
||||
|
var laser := MeshInstance3D.new() |
||||
|
var cyl := CylinderMesh.new() |
||||
|
cyl.top_radius = 0.002 |
||||
|
cyl.bottom_radius = 0.002 |
||||
|
cyl.height = ray_length |
||||
|
laser.mesh = cyl |
||||
|
laser.position = Vector3(0, 0, -ray_length / 2.0) |
||||
|
laser.rotation.x = deg_to_rad(90) |
||||
|
var mat := StandardMaterial3D.new() |
||||
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED |
||||
|
mat.albedo_color = laser_color |
||||
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA |
||||
|
laser.material_override = mat |
||||
|
return laser |
||||
|
|
||||
|
|
||||
|
func _create_hand_visuals() -> void: |
||||
|
# Shared meshes - larger for visibility in VR |
||||
|
var joint_mesh := SphereMesh.new() |
||||
|
joint_mesh.radius = 0.01 |
||||
|
joint_mesh.height = 0.02 |
||||
|
var tip_mesh := SphereMesh.new() |
||||
|
tip_mesh.radius = 0.013 |
||||
|
tip_mesh.height = 0.026 |
||||
|
|
||||
|
for hand_idx in [0, 1]: |
||||
|
var mat := StandardMaterial3D.new() |
||||
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED |
||||
|
mat.albedo_color = HAND_COLORS[hand_idx] |
||||
|
|
||||
|
for joint_idx in range(JOINT_COUNT): |
||||
|
var s := MeshInstance3D.new() |
||||
|
s.mesh = tip_mesh if joint_idx in TIP_JOINTS else joint_mesh |
||||
|
s.material_override = mat |
||||
|
s.visible = false |
||||
|
add_child(s) |
||||
|
_hand_joints[hand_idx].append(s) |
||||
|
|
||||
|
|
||||
|
func _create_controller_mesh(ctrl: XRController3D) -> MeshInstance3D: |
||||
|
var mesh_inst := MeshInstance3D.new() |
||||
|
var box := BoxMesh.new() |
||||
|
box.size = Vector3(0.05, 0.03, 0.12) |
||||
|
mesh_inst.mesh = box |
||||
|
var mat := StandardMaterial3D.new() |
||||
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED |
||||
|
mat.albedo_color = Color(0.5, 0.5, 0.6, 0.7) |
||||
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA |
||||
|
mesh_inst.material_override = mat |
||||
|
mesh_inst.visible = false |
||||
|
ctrl.add_child(mesh_inst) |
||||
|
return mesh_inst |
||||
|
|
||||
|
|
||||
|
func _process(delta: float) -> void: |
||||
|
if _xr_origin == null: |
||||
|
return |
||||
|
|
||||
|
# Debug logging every 5 seconds |
||||
|
_log_timer += delta |
||||
|
if _log_timer > 5.0: |
||||
|
_log_timer = 0.0 |
||||
|
var l := _left_ctrl.get_is_active() if _left_ctrl else false |
||||
|
var r := _right_ctrl.get_is_active() if _right_ctrl else false |
||||
|
var panels := get_tree().get_nodes_in_group("start_screen").size() |
||||
|
print("[VRUIPointer] ctrl=L:%s/R:%s panels=%d" % [l, r, panels]) |
||||
|
# List all XR trackers |
||||
|
var trackers := XRServer.get_trackers(0xFF) |
||||
|
var tracker_names := [] |
||||
|
for key in trackers: |
||||
|
tracker_names.append(str(key)) |
||||
|
print("[VRUIPointer] all_trackers=%s" % [", ".join(tracker_names)]) |
||||
|
# Hand tracker diagnostics |
||||
|
for hand_idx in [0, 1]: |
||||
|
var side := "L" if hand_idx == 0 else "R" |
||||
|
var tn := &"/user/hand_tracker/left" if hand_idx == 0 else &"/user/hand_tracker/right" |
||||
|
var tr = XRServer.get_tracker(tn) |
||||
|
if tr == null: |
||||
|
print("[VRUIPointer] hand_%s: tracker=NULL" % side) |
||||
|
else: |
||||
|
var ht = tr as XRHandTracker |
||||
|
if ht: |
||||
|
var src = ht.hand_tracking_source |
||||
|
print("[VRUIPointer] hand_%s: has_data=%s source=%d type=%s" % [side, ht.has_tracking_data, src, ht.get_class()]) |
||||
|
# Sample joint data even if has_data=false |
||||
|
var wrist := ht.get_hand_joint_transform(0) |
||||
|
var index_tip := ht.get_hand_joint_transform(10) |
||||
|
print("[VRUIPointer] hand_%s: wrist=%s idx_tip=%s" % [side, wrist.origin, index_tip.origin]) |
||||
|
else: |
||||
|
print("[VRUIPointer] hand_%s: tracker exists but NOT XRHandTracker, class=%s" % [side, tr.get_class()]) |
||||
|
# Body tracker diagnostics (fallback for hand data) |
||||
|
var bt = XRServer.get_tracker(&"/user/body_tracker") as XRBodyTracker |
||||
|
if bt == null: |
||||
|
print("[VRUIPointer] body_tracker=NULL") |
||||
|
else: |
||||
|
var bt_data := bt.get_has_tracking_data() |
||||
|
var vis_l := 0 |
||||
|
var vis_r := 0 |
||||
|
for s in _hand_joints[0]: |
||||
|
if s.visible: |
||||
|
vis_l += 1 |
||||
|
for s in _hand_joints[1]: |
||||
|
if s.visible: |
||||
|
vis_r += 1 |
||||
|
if bt_data: |
||||
|
var lw := bt.get_joint_transform(BODY_LEFT_HAND_START) |
||||
|
var rw := bt.get_joint_transform(BODY_RIGHT_HAND_START) |
||||
|
print("[VRUIPointer] body: has_data=true L_wrist=%s R_wrist=%s vis=L:%d/R:%d" % [lw.origin, rw.origin, vis_l, vis_r]) |
||||
|
else: |
||||
|
print("[VRUIPointer] body: has_data=false vis=L:%d/R:%d" % [vis_l, vis_r]) |
||||
|
|
||||
|
# Update visuals every frame |
||||
|
_update_hand_visuals() |
||||
|
|
||||
|
# Controller visibility and laser defaults |
||||
|
var l_active := _left_ctrl.get_is_active() |
||||
|
var r_active := _right_ctrl.get_is_active() |
||||
|
_ctrl_mesh_left.visible = l_active |
||||
|
_ctrl_mesh_right.visible = r_active |
||||
|
_laser_left.visible = l_active |
||||
|
_laser_right.visible = r_active |
||||
|
if l_active: |
||||
|
_reset_laser_length(_laser_left) |
||||
|
if r_active: |
||||
|
_reset_laser_length(_laser_right) |
||||
|
|
||||
|
# UI interaction |
||||
|
var panels := _get_ui_panels() |
||||
|
if panels.is_empty(): |
||||
|
_reticle.visible = false |
||||
|
return |
||||
|
|
||||
|
var hit := false |
||||
|
|
||||
|
# 1. Hand tracking poke (priority over controller ray) |
||||
|
for hand_idx in [0, 1]: |
||||
|
var tip_pos := _get_fingertip_world_position(hand_idx) |
||||
|
if tip_pos == Vector3.ZERO: |
||||
|
continue |
||||
|
for panel in panels: |
||||
|
var result := _check_point_against_panel(tip_pos, panel[0], panel[1]) |
||||
|
if result.size() > 0 and abs(result[0]) < hover_distance: |
||||
|
hit = true |
||||
|
_reticle.global_position = result[2] |
||||
|
_reticle.visible = true |
||||
|
_current_viewport = panel[1] |
||||
|
_last_viewport_pos = result[1] |
||||
|
_send_mouse_motion(panel[1], result[1]) |
||||
|
|
||||
|
var method := "poke_%d" % hand_idx |
||||
|
if result[0] < poke_threshold and not _is_pressing: |
||||
|
_is_pressing = true |
||||
|
_active_method = method |
||||
|
_send_mouse_button(panel[1], result[1], true) |
||||
|
elif result[0] >= poke_threshold and _is_pressing and _active_method == method: |
||||
|
_is_pressing = false |
||||
|
_send_mouse_button(panel[1], result[1], false) |
||||
|
break |
||||
|
if hit: |
||||
|
break |
||||
|
|
||||
|
# 2. Controller ray pointing |
||||
|
if not hit: |
||||
|
for ctrl_data in [[_right_ctrl, _laser_right, "ray_right"], [_left_ctrl, _laser_left, "ray_left"]]: |
||||
|
var ctrl: XRController3D = ctrl_data[0] |
||||
|
var laser: MeshInstance3D = ctrl_data[1] |
||||
|
var method: String = ctrl_data[2] |
||||
|
if not ctrl.get_is_active(): |
||||
|
continue |
||||
|
var ray_origin := ctrl.global_position |
||||
|
var ray_dir := -ctrl.global_transform.basis.z.normalized() |
||||
|
|
||||
|
for panel in panels: |
||||
|
var result := _ray_intersect_panel(ray_origin, ray_dir, panel[0], panel[1]) |
||||
|
if result.size() > 0: |
||||
|
hit = true |
||||
|
_reticle.global_position = result[2] |
||||
|
_reticle.visible = true |
||||
|
_current_viewport = panel[1] |
||||
|
_last_viewport_pos = result[1] |
||||
|
_send_mouse_motion(panel[1], result[1]) |
||||
|
var dist := ray_origin.distance_to(result[2]) |
||||
|
_update_laser_length(laser, dist) |
||||
|
break |
||||
|
if hit: |
||||
|
break |
||||
|
|
||||
|
if not hit: |
||||
|
_reticle.visible = false |
||||
|
if _is_pressing: |
||||
|
_is_pressing = false |
||||
|
if _current_viewport: |
||||
|
_send_mouse_button(_current_viewport, _last_viewport_pos, false) |
||||
|
_current_viewport = null |
||||
|
|
||||
|
|
||||
|
func _has_hand_tracking(hand: int) -> bool: |
||||
|
var tracker_name := &"/user/hand_tracker/left" if hand == 0 else &"/user/hand_tracker/right" |
||||
|
var tracker = XRServer.get_tracker(tracker_name) as XRHandTracker |
||||
|
return tracker != null and tracker.has_tracking_data |
||||
|
|
||||
|
|
||||
|
func _update_hand_visuals() -> void: |
||||
|
for hand_idx in [0, 1]: |
||||
|
# Try XRHandTracker first (works with controllers) |
||||
|
var tracker_name := &"/user/hand_tracker/left" if hand_idx == 0 else &"/user/hand_tracker/right" |
||||
|
var tracker = XRServer.get_tracker(tracker_name) |
||||
|
var hand_tracker: XRHandTracker = tracker as XRHandTracker if tracker else null |
||||
|
|
||||
|
if hand_tracker and hand_tracker.has_tracking_data: |
||||
|
for joint_idx in range(JOINT_COUNT): |
||||
|
var xform := hand_tracker.get_hand_joint_transform(joint_idx) |
||||
|
if xform.origin == Vector3.ZERO: |
||||
|
_hand_joints[hand_idx][joint_idx].visible = false |
||||
|
continue |
||||
|
var world_pos := _xr_origin.global_transform * xform.origin |
||||
|
_hand_joints[hand_idx][joint_idx].global_position = world_pos |
||||
|
_hand_joints[hand_idx][joint_idx].visible = true |
||||
|
continue |
||||
|
|
||||
|
# Fallback: XRBodyTracker (Meta FB body tracking, works without controllers) |
||||
|
var body_tracker = XRServer.get_tracker(&"/user/body_tracker") as XRBodyTracker |
||||
|
if body_tracker and body_tracker.get_has_tracking_data(): |
||||
|
var start_idx := BODY_LEFT_HAND_START if hand_idx == 0 else BODY_RIGHT_HAND_START |
||||
|
for i in range(BODY_HAND_JOINT_COUNT): |
||||
|
var xform := body_tracker.get_joint_transform(start_idx + i) |
||||
|
if xform.origin == Vector3.ZERO: |
||||
|
if i < JOINT_COUNT: |
||||
|
_hand_joints[hand_idx][i].visible = false |
||||
|
continue |
||||
|
var world_pos := _xr_origin.global_transform * xform.origin |
||||
|
if i < JOINT_COUNT: |
||||
|
_hand_joints[hand_idx][i].global_position = world_pos |
||||
|
_hand_joints[hand_idx][i].visible = true |
||||
|
# Hide the 26th sphere (body tracker has 25 joints, not 26) |
||||
|
_hand_joints[hand_idx][25].visible = false |
||||
|
continue |
||||
|
|
||||
|
# No tracking data from either source |
||||
|
for s in _hand_joints[hand_idx]: |
||||
|
s.visible = false |
||||
|
|
||||
|
|
||||
|
func _update_laser_length(laser: MeshInstance3D, length: float) -> void: |
||||
|
var cyl := laser.mesh as CylinderMesh |
||||
|
if cyl: |
||||
|
cyl.height = length |
||||
|
laser.position = Vector3(0, 0, -length / 2.0) |
||||
|
|
||||
|
|
||||
|
func _reset_laser_length(laser: MeshInstance3D) -> void: |
||||
|
var cyl := laser.mesh as CylinderMesh |
||||
|
if cyl: |
||||
|
cyl.height = ray_length |
||||
|
laser.position = Vector3(0, 0, -ray_length / 2.0) |
||||
|
|
||||
|
|
||||
|
func _get_fingertip_world_position(hand: int) -> Vector3: |
||||
|
# Try XRHandTracker first |
||||
|
var tracker_name := &"/user/hand_tracker/left" if hand == 0 else &"/user/hand_tracker/right" |
||||
|
var tracker = XRServer.get_tracker(tracker_name) |
||||
|
if tracker: |
||||
|
var hand_tracker := tracker as XRHandTracker |
||||
|
if hand_tracker and hand_tracker.has_tracking_data: |
||||
|
var tip_xform := hand_tracker.get_hand_joint_transform(XRHandTracker.HAND_JOINT_INDEX_FINGER_TIP) |
||||
|
if tip_xform.origin != Vector3.ZERO: |
||||
|
return _xr_origin.global_transform * tip_xform.origin |
||||
|
|
||||
|
# Fallback: XRBodyTracker |
||||
|
var body_tracker = XRServer.get_tracker(&"/user/body_tracker") as XRBodyTracker |
||||
|
if body_tracker and body_tracker.get_has_tracking_data(): |
||||
|
var start_idx := BODY_LEFT_HAND_START if hand == 0 else BODY_RIGHT_HAND_START |
||||
|
var tip_xform := body_tracker.get_joint_transform(start_idx + BODY_INDEX_TIP) |
||||
|
if tip_xform.origin != Vector3.ZERO: |
||||
|
return _xr_origin.global_transform * tip_xform.origin |
||||
|
|
||||
|
return Vector3.ZERO |
||||
|
|
||||
|
|
||||
|
func _get_ui_panels() -> Array: |
||||
|
var results := [] |
||||
|
for screen in get_tree().get_nodes_in_group("start_screen"): |
||||
|
if not screen.visible: |
||||
|
continue |
||||
|
var ui_mesh: MeshInstance3D = screen.get_node_or_null("UIMesh") |
||||
|
var vp: SubViewport = screen.get_node_or_null("UIMesh/SubViewport") |
||||
|
if ui_mesh and vp: |
||||
|
results.append([ui_mesh, vp]) |
||||
|
return results |
||||
|
|
||||
|
|
||||
|
func _check_point_against_panel(point: Vector3, mesh: MeshInstance3D, vp: SubViewport) -> Array: |
||||
|
var mx := mesh.global_transform |
||||
|
var normal := mx.basis.z.normalized() |
||||
|
var signed_dist := normal.dot(point - mx.origin) |
||||
|
var projected := point - normal * signed_dist |
||||
|
var local_hit := mx.affine_inverse() * projected |
||||
|
var qm := mesh.mesh as QuadMesh |
||||
|
if qm == null: |
||||
|
return [] |
||||
|
var qs := qm.size |
||||
|
if abs(local_hit.x) > qs.x / 2.0 or abs(local_hit.y) > qs.y / 2.0: |
||||
|
return [] |
||||
|
var u := (local_hit.x / qs.x) + 0.5 |
||||
|
var v := 0.5 - (local_hit.y / qs.y) |
||||
|
return [signed_dist, Vector2(u * vp.size.x, v * vp.size.y), projected] |
||||
|
|
||||
|
|
||||
|
func _ray_intersect_panel(ray_origin: Vector3, ray_dir: Vector3, mesh: MeshInstance3D, vp: SubViewport) -> Array: |
||||
|
var mx := mesh.global_transform |
||||
|
var normal := mx.basis.z.normalized() |
||||
|
var denom := normal.dot(ray_dir) |
||||
|
if abs(denom) < 0.0001: |
||||
|
return [] |
||||
|
var t := normal.dot(mx.origin - ray_origin) / denom |
||||
|
if t < 0 or t > ray_length: |
||||
|
return [] |
||||
|
var hit := ray_origin + ray_dir * t |
||||
|
var local_hit := mx.affine_inverse() * hit |
||||
|
var qm := mesh.mesh as QuadMesh |
||||
|
if qm == null: |
||||
|
return [] |
||||
|
var qs := qm.size |
||||
|
if abs(local_hit.x) > qs.x / 2.0 or abs(local_hit.y) > qs.y / 2.0: |
||||
|
return [] |
||||
|
var u := (local_hit.x / qs.x) + 0.5 |
||||
|
var v := 0.5 - (local_hit.y / qs.y) |
||||
|
return [0.0, Vector2(u * vp.size.x, v * vp.size.y), hit] |
||||
|
|
||||
|
|
||||
|
func _on_right_button_pressed(button_name: String) -> void: |
||||
|
if button_name in ["trigger_click", "ax_button", "primary_click"]: |
||||
|
if _current_viewport and not _is_pressing: |
||||
|
_is_pressing = true |
||||
|
_active_method = "ray_right" |
||||
|
_send_mouse_button(_current_viewport, _last_viewport_pos, true) |
||||
|
|
||||
|
|
||||
|
func _on_right_button_released(button_name: String) -> void: |
||||
|
if button_name in ["trigger_click", "ax_button", "primary_click"]: |
||||
|
if _is_pressing and _active_method == "ray_right": |
||||
|
_is_pressing = false |
||||
|
if _current_viewport: |
||||
|
_send_mouse_button(_current_viewport, _last_viewport_pos, false) |
||||
|
|
||||
|
|
||||
|
func _on_left_button_pressed(button_name: String) -> void: |
||||
|
if button_name in ["trigger_click", "ax_button", "primary_click"]: |
||||
|
if _current_viewport and not _is_pressing: |
||||
|
_is_pressing = true |
||||
|
_active_method = "ray_left" |
||||
|
_send_mouse_button(_current_viewport, _last_viewport_pos, true) |
||||
|
|
||||
|
|
||||
|
func _on_left_button_released(button_name: String) -> void: |
||||
|
if button_name in ["trigger_click", "ax_button", "primary_click"]: |
||||
|
if _is_pressing and _active_method == "ray_left": |
||||
|
_is_pressing = false |
||||
|
if _current_viewport: |
||||
|
_send_mouse_button(_current_viewport, _last_viewport_pos, false) |
||||
|
|
||||
|
|
||||
|
func _send_mouse_motion(vp: SubViewport, pos: Vector2) -> void: |
||||
|
var event := InputEventMouseMotion.new() |
||||
|
event.position = pos |
||||
|
event.global_position = pos |
||||
|
vp.push_input(event) |
||||
|
|
||||
|
|
||||
|
func _send_mouse_button(vp: SubViewport, pos: Vector2, pressed: bool) -> void: |
||||
|
var event := InputEventMouseButton.new() |
||||
|
event.position = pos |
||||
|
event.global_position = pos |
||||
|
event.button_index = MOUSE_BUTTON_LEFT |
||||
|
event.pressed = pressed |
||||
|
if pressed: |
||||
|
event.button_mask = MOUSE_BUTTON_MASK_LEFT |
||||
|
vp.push_input(event) |
||||
@ -0,0 +1 @@ |
|||||
|
uid://c7w0y2lapybff |
||||
@ -0,0 +1 @@ |
|||||
|
uid://df6sw3ko66dyi |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue