diff --git a/deps/cloudxr/Dockerfile.wss.proxy b/deps/cloudxr/Dockerfile.wss.proxy new file mode 100644 index 0000000..0c32898 --- /dev/null +++ b/deps/cloudxr/Dockerfile.wss.proxy @@ -0,0 +1,13 @@ +FROM haproxy:3.2 +USER root +RUN apt-get update && apt-get install -y bash gettext-base openssl && rm -rf /var/lib/apt/lists/* +RUN mkdir -p /usr/local/etc/haproxy/certs && chown -R haproxy:haproxy /usr/local/etc/haproxy + +RUN printf '#!/bin/bash\ncd /usr/local/etc/haproxy/certs\nopenssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes -subj "/CN=localhost" -quiet\ncat server.crt server.key > server.pem\nchown haproxy:haproxy server.key server.crt server.pem\nchmod 600 server.key server.pem\nchmod 644 server.crt\n' > /usr/local/bin/generate-cert.sh && chmod +x /usr/local/bin/generate-cert.sh + +RUN printf 'global\n log stdout local0 info\n stats timeout 30s\n user haproxy\ndefaults\n log global\n option httplog\n option dontlognull\n timeout connect 5s\n timeout client 3600s\n timeout server 3600s\n timeout tunnel 3600s\nfrontend websocket_frontend\n bind *:${PROXY_PORT} ${PROXY_SSL_BIND_OPTIONS}\n mode http\n http-response set-header Access-Control-Allow-Origin "*"\n http-response set-header Access-Control-Allow-Headers "*"\n http-response set-header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"\n http-request return status 200 content-type "text/plain" string "OK" if METH_OPTIONS\n default_backend websocket_backend\nbackend websocket_backend\n mode http\n server local_websocket ${BACKEND_HOST}:${BACKEND_PORT} check inter 2s rise 2 fall 3 on-marked-down shutdown-sessions\n' > /usr/local/etc/haproxy/haproxy.cfg.template && chown haproxy:haproxy /usr/local/etc/haproxy/haproxy.cfg.template + +RUN printf '#!/bin/bash\nexport BACKEND_HOST=${BACKEND_HOST:-localhost}\nexport BACKEND_PORT=${BACKEND_PORT:-49100}\nexport PROXY_PORT=${PROXY_PORT:-48322}\n/usr/local/bin/generate-cert.sh\nexport PROXY_SSL_BIND_OPTIONS="ssl crt /usr/local/etc/haproxy/certs/server.pem"\nenvsubst < /usr/local/etc/haproxy/haproxy.cfg.template > /usr/local/etc/haproxy/haproxy.cfg\necho "WSS Proxy: wss://0.0.0.0:${PROXY_PORT} -> ws://${BACKEND_HOST}:${BACKEND_PORT}"\nexec haproxy -f /usr/local/etc/haproxy/haproxy.cfg\n' > /entrypoint.sh && chmod +x /entrypoint.sh + +USER haproxy +ENTRYPOINT ["/entrypoint.sh"] diff --git a/deps/cloudxr/INSTALL.md b/deps/cloudxr/INSTALL.md new file mode 100644 index 0000000..1344eef --- /dev/null +++ b/deps/cloudxr/INSTALL.md @@ -0,0 +1,261 @@ +# CloudXR VR Teleoperation Setup Guide + +将 Isaac Lab 仿真画面通过 NVIDIA CloudXR 流式传输到 PICO 4 Ultra,实现沉浸式 VR 遥操作。 + +## 架构 + +``` +Isaac Sim (本地) ──OpenXR──► CloudXR Runtime (Docker) + │ + WebSocket (port 49100) + │ + HAProxy WSS Proxy (port 48322) + │ + PICO 4 Ultra 浏览器 + https://:8080 (Web App) +``` + +## 系统要求 + +- Ubuntu 22.04 / 24.04 +- NVIDIA GPU (RTX 系列) +- NVIDIA Driver 最新版 +- Docker + NVIDIA Container Toolkit +- Isaac Lab 本地安装(`~/IsaacLab`) +- PICO 4 Ultra (OS 15.4.4U 或更高) +- CloudXR Early Access 资格([申请地址](https://developer.nvidia.com/cloudxr-sdk)) +- Node.js 20 LTS + +--- + +## 一、系统环境准备 + +### 1.1 安装 NVIDIA Container Toolkit + +```bash +curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg +curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ + sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ + sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list +sudo apt-get update +sudo apt-get install -y nvidia-container-toolkit +sudo nvidia-ctk runtime configure --runtime=docker +sudo systemctl restart docker +``` + +### 1.2 配置 Docker(国内镜像 + NVIDIA runtime) + +```bash +sudo tee /etc/docker/daemon.json << 'EOF' +{ + "registry-mirrors": [ + "https://docker.1ms.run", + "https://docker.1panel.live", + "https://hub.rat.dev" + ], + "runtimes": { + "nvidia": { + "path": "nvidia-container-runtime", + "runtimeArgs": [] + } + } +} +EOF +sudo systemctl restart docker +``` + +### 1.3 开放防火墙端口 + +```bash +sudo ufw allow 47998:48000/udp +sudo ufw allow 48005/udp +sudo ufw allow 48008/udp +sudo ufw allow 48012/udp +sudo ufw allow 48010/tcp +sudo ufw allow 49100/tcp +sudo ufw allow 48322/tcp +sudo ufw allow 8080/tcp +``` + +### 1.4 安装 Node.js 20 LTS(通过 nvm) + +```bash +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash +source ~/.bashrc +nvm install 20 +nvm use 20 +``` + +--- + +## 二、获取 CloudXR Early Access SDK + +1. 前往 [ngc.nvidia.com](https://ngc.nvidia.com) 获取 API Key +2. 登录 NGC Docker registry: + +```bash +sudo docker login nvcr.io +# Username: $oauthtoken +# Password: <你的 NGC API Key> +``` + +3. 下载 CloudXR.js SDK(从 NGC Early Access 页面获取),解压到: + ``` + deps/cloudxr/ + ├── nvidia-cloudxr-6.0.0-beta.tgz + ├── isaac/ # React Web App + ├── docs/ + └── Dockerfile.wss.proxy + ``` + +--- + +## 三、配置 Isaac Lab Docker + +修改 `~/IsaacLab/docker/.env.cloudxr-runtime`: + +```ini +CLOUDXR_RUNTIME_BASE_IMAGE_ARG=nvcr.io/nvidia/cloudxr-runtime-early-access +CLOUDXR_RUNTIME_VERSION_ARG=6.0.1-webrtc +ACCEPT_EULA=yes +``` + +修改 `~/IsaacLab/docker/docker-compose.cloudxr-runtime.patch.yaml`,将 cloudxr-runtime service 的 `ports` 替换为 `network_mode: host`: + +```yaml +services: + cloudxr-runtime: + image: ${CLOUDXR_RUNTIME_BASE_IMAGE_ARG}:${CLOUDXR_RUNTIME_VERSION_ARG} + network_mode: host + # ... 其余保持不变 +``` + +--- + +## 四、构建 WSS 代理(HAProxy) + +```bash +cd deps/cloudxr + +# 构建镜像 +sudo docker build -t websocket-ssl-proxy -f Dockerfile.wss.proxy . + +# 启动代理(监听 48322,转发到 CloudXR Runtime 的 49100) +sudo docker run -d --name wss-proxy \ + --network host \ + -e BACKEND_HOST=localhost \ + -e BACKEND_PORT=49100 \ + -e PROXY_PORT=48322 \ + websocket-ssl-proxy + +# 验证 +sudo docker logs wss-proxy +``` + +--- + +## 五、构建 Web 客户端 + +```bash +cd deps/cloudxr/isaac + +# 安装依赖(需要先安装本地 CloudXR SDK) +npm install ../nvidia-cloudxr-6.0.0-beta.tgz +npm install + +# 启动 HTTPS 开发服务器(端口 8080) +npm run dev-server:https +``` + +> **注意**:首次运行会下载 WebXR controller profile 资源,需要网络连接。 + +--- + +## 六、启动流程 + +每次使用前按以下顺序启动: + +### 6.1 启动 CloudXR Runtime + Isaac Lab + +```bash +cd ~/IsaacLab +./docker/container.py start \ + --files docker-compose.cloudxr-runtime.patch.yaml \ + --env-file .env.cloudxr-runtime + +# 进入 Isaac Lab 容器 +./docker/container.py enter base +``` + +容器内运行 MindBot 遥操作: + +```bash +~/IsaacLab/isaaclab.sh -p scripts/environments/teleoperation/teleop_xr_agent.py \ + --task Isaac-MindRobot-2i-DualArm-IK-Abs-v0 \ + --cloudxr +``` + +Isaac Sim 启动后:**AR 面板 → OpenXR → System OpenXR Runtime → 点击 Start AR** + +### 6.2 启动 WSS 代理 + +```bash +sudo docker start wss-proxy +``` + +### 6.3 启动 Web 服务 + +```bash +cd deps/cloudxr/isaac +npm run dev-server:https +``` + +--- + +## 七、PICO 4 Ultra 配置 + +### 7.1 启用手部追踪 + +设置 → Interaction → 选择 **Auto Switch between Hands & Controllers** + +### 7.2 接受证书 + +1. 打开 PICO 浏览器,访问 `https://<工作站IP>:48322/` + - 点击 **Advanced** → **Proceed (unsafe)** + - 看到 "501 Not Implemented" 是正常的 +2. 访问 `https://<工作站IP>:8080/` + - 点击 **Advanced** → **Proceed (unsafe)** + - Web App 加载成功 + +### 7.3 连接 + +1. 在 Web App 中输入工作站 IP +2. 点击 **CONNECT** +3. 点击 **Enter XR** 进入沉浸式模式 + +--- + +## 八、验证端口状态 + +```bash +sudo ss -tlnp | grep -E "49100|48322|8080" +``` + +预期输出: +``` +LISTEN 0.0.0.0:49100 cloudxr-service # CloudXR Runtime +LISTEN 0.0.0.0:48322 haproxy # WSS Proxy +LISTEN 0.0.0.0:8080 node # Web App +``` + +--- + +## 常见问题 + +| 症状 | 原因 | 解决 | +|---|---|---| +| 503 Service Unavailable (48322) | CloudXR Runtime 未运行 | 重启 docker-cloudxr-runtime-1 容器 | +| 501 Not Implemented (48322) | 正常 — HAProxy 只处理 WebSocket | 直接接受证书即可 | +| Stream start failed 0xC0F22202 | HTTPS + ws:// 混合内容 | 必须先接受 48322 代理证书 | +| EMFILE: too many open files | webpack 文件监听超限 | 已通过 `watchOptions.ignored` 修复 | +| Isaac Sim AR 面板看不到 | 未传 --cloudxr 或 --xr flag | 使用 `--cloudxr` 启动脚本 | diff --git a/deps/cloudxr/LICENSE b/deps/cloudxr/LICENSE new file mode 100644 index 0000000..5e53852 --- /dev/null +++ b/deps/cloudxr/LICENSE @@ -0,0 +1,307 @@ +NVIDIA SOFTWARE EVALUATION LICENSE AGREEMENT + +IMPORTANT NOTICE – PLEASE READ AND AGREE BEFORE USING THE SOFTWARE + +This software evaluation license agreement (“Agreement”) is a legal agreement between you, whether an +individual or entity, (“you”) and NVIDIA Corporation and its affiliates (“NVIDIA”) and governs the use of certain +NVIDIA CloudXR software and documentation that NVIDIA delivers to you under this Agreement (“Software”). +NVIDIA and you are each a “party” and collectively the “parties.” +This Agreement can be accepted only by an adult of legal age of majority in the country in which the Software is +used. If you don’t have the required age or authority to accept this Agreement, or if you don’t accept all the +terms and conditions of this Agreement, do not use the Software. + +1. License Grants. + +1.1 License Grant to You. The Software is licensed, not sold. Subject to the terms of this Agreement, +NVIDIA grants you a limited, non-exclusive, revocable, non-transferable, non-sublicensable (except +as expressly granted in this Agreement), license to: +(a) access, install and use copies of the Software, +(b) configure the Software using configuration files provided (if applicable), +(c) modify and create derivative works of any source code NVIDIA delivers to you as part of the +Software (“Derivatives”) (if applicable). + +All the foregoing grants are only for internal test and evaluation purposes and, as applicable, for use (a) in +client systems, or (b) in server systems with NVIDIA GPUs (“Purpose”). + +1.2 License Grant to NVIDIA. Subject to the terms of this Agreement, you grant NVIDIA a non-exclusive, +perpetual, irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, +under your intellectual property rights, to publicly perform, publicly display, reproduce, use, make, +have made, sell, offer for sale, distribute (through multiple tiers of distribution), import, create +derivative works of and otherwise commercialize and exploit at NVIDIA’s discretion any Derivatives +created by or for you. You may, but are not required to, deliver any Derivatives to NVIDIA. + +2. License Restrictions. Your license to use the Software and Derivatives is restricted as stated in this “License +Restrictions” Section. You will cooperate with NVIDIA and, upon NVIDIA’s written request, you will confirm +in writing and provide reasonably requested information to verify your compliance with the terms of this +Agreement. You may not: + +2.1 Use the Software or Derivatives for any purpose other than the Purpose, including but not limited to +in production; + +2.2 Sell, rent, sublicense, transfer, distribute or otherwise make available to others (except Authorized +Users as stated in the “Authorized Users” Section) any portion of the Software or Derivatives, except +as expressly granted in Section 1.1 (“License Grant to You”); + +2.3 Reverse engineer, decompile, or disassemble the Software components provided in binary form, nor +attempt in any other manner to obtain source code of such Software; + +2.4 Modify or create derivative works of the Software, except as expressly granted in Section 1.1 +(“License Grant to You”); + +2.5 Change or remove copyright or other proprietary notices in the Software; + +2.6 Bypass, disable, or circumvent any technical limitation, encryption, security, digital rights +management or authentication mechanism in the Software; + +2.7 Use the Software or Derivatives in any manner that would cause them to become subject to an open +source software license; subject to the terms in Section 7 (“Components Under Other Licenses”); + +2.8 Use the Software or Derivatives for the purpose of developing competing products or technologies +or assist a third party in such activities; + +2.9 Replace any Software components governed by this Agreement with other software that +implements NVIDIA APIs; + +2.10 Use the Software or Derivatives in violation of any applicable law or regulation in the relevant +jurisdictions; or + +2.11 Use the Software in or with any system or application where the use or failure of such system or +application developed or deployed with Software could result in injury, death or catastrophic +damage (“Mission Critical Applications”). NVIDIA will not be liable to you or any third party, in whole +or in part, for any claims or damages arising from uses in Mission Critical Applications. + +2.12 Disclose any evaluation or test results regarding the Software or Derivatives without NVIDIA’s prior +written consent. + +3. Authorized Users. You may allow employees and contractors of your entity or of your subsidiary(ies), and +for educational institutions also enrolled students, to internally access and use the Software as authorized +by this Agreement from your secure network to perform the work authorized by this Agreement on your +behalf. You are responsible for the compliance with the terms of this Agreement by your authorized users. +Any act or omission that if committed by you would constitute a breach of this Agreement will be deemed to +constitute a breach of this Agreement if committed by your authorized users. + +4. Pre-Release. Software versions identified as alpha, beta, preview, early access or otherwise as pre-release +may not be fully functional, may contain errors or design flaws, and may have reduced or different security, +privacy, availability and reliability standards relative to NVIDIA commercial offerings. You use pre-release +Software at your own risk. NVIDIA did not design or test the Software for use in production or business +critical systems. NVIDIA may choose not to make available a commercial version of pre-release Software. +NVIDIA may also choose to abandon development and terminate the availability of pre-release Software at +any time without liability. + +5. Your Privacy: Collection and Use of Information. + +5.1 Privacy Policy. Please review the NVIDIA Privacy Policy, located at https://www.nvidia.com/enus/about-nvidia/privacy-policy, which explains NVIDIA’s policy for collecting and using data, as well +as visit the NVIDIA Privacy Center, located at https://www.nvidia.com/en-us/privacy-center, to +manage your consent and privacy preferences. + +5.2 Collection Purposes. You also acknowledge that the Software collects data for the following +purposes: (a) properly configure and optimize products for use with Software; and (b) improve +NVIDIA products and services. Information collected by the Software includes: (i) application +configuration; (ii) browser version; (iii) and session metadata (i.e. performance and usage +statistics). Additionally, NVIDIA may collect certain personal information, such as your name +and email address or those of your authorized users, and other information necessary to +authenticate and enable you or your authorized users’ access to the Software. Where appropriate +you will disclose to, and obtain any necessary consent from, your authorized users to allow NVIDIA +to collect such information. + +5.3 Third Party Privacy Practices. The Software may contain links to third party websites and services. +NVIDIA encourages you to review the privacy statements on those sites and services that you choose +to visit to understand how they may collect, use and share your data. NVIDIA is not responsible for +the privacy statements or practices of third-party sites or services. + +6. Updates. NVIDIA may at any time and at its option, change, discontinue, or deprecate any part, or all, of the +Software, or change or remove features or functionality, or make available patches, workarounds or other +updates to the Software. Unless the updates are provided with their separate governing terms, they are +deemed part of the Software licensed to you under this Agreement, and your continued use of the Software +is deemed acceptance of such changes. + +7. Components Under Other Licenses. The Software may include or be distributed with components provided +with separate legal notices or terms that accompany the components, such as open source software licenses +and other license terms (“Other Licenses”). The components are subject to the applicable Other Licenses, +including any proprietary notices, disclaimers, requirements and extended use rights; except that this +Agreement will prevail regarding the use of third-party open source software, unless a third-party open +source software license requires its license terms to prevail. Open source software license means any +software, data or documentation subject to any license identified as an open source license by the Open +Source Initiative (http://opensource.org), Free Software Foundation (http://www.fsf.org) or other similar +open source organization or listed by the Software Package Data Exchange (SPDX) Workgroup under the +Linux Foundation (http://www.spdx.org). + +8. Ownership. + +8.1 NVIDIA Ownership. The Software, including all intellectual property rights, is and will remain the sole +and exclusive property of NVIDIA or its licensors. Except as expressly granted in this Agreement, +(a) NVIDIA reserves all rights, interests and remedies in connection with the Software, and (b) no +other license or right is granted to you by implication, estoppel or otherwise. + +8.2 Your Ownership. Subject to the rights of NVIDIA and its suppliers in the Software, which continue to +be licensed as stated in this Agreement, even when incorporated in your products, and the extent +permitted by applicable law, as between you and NVIDIA, you hold all rights, title and interest in and +to your services, applications and Derivatives you develop as permitted in this Agreement including +their respective intellectual property rights. + +9. Feedback. You may, but are not obligated to, provide suggestions, requests, fixes, modifications, +enhancements or other feedback regarding your use of the Software (“Feedback”). Feedback, even if +designated as confidential by you, will not create any confidentiality obligation for NVIDIA or its affiliates. If +you provide Feedback, you hereby grant NVIDIA, its affiliates and its designees a nonexclusive, perpetual, +irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, under your +intellectual property rights, to publicly perform, publicly display, reproduce, use, make, have made, sell, +offer for sale, distribute (through multiple tiers of distribution), import, create derivative works of and +otherwise commercialize and exploit the Feedback at NVIDIA’s discretion. + +10. Confidentiality. You may use confidential information only to exercise your rights and perform your +obligations under this Agreement. You will not disclose, nor authorize others to disclose NVIDIA Confidential +Information to any third party, except as expressly authorized in this Agreement and as necessary for the +Purpose, without obtaining NVIDIA’s prior written approval. Each recipient of confidential information must +be subject to a written agreement that includes confidentiality obligations consistent with these terms and +must have a need to know for the Purpose. You will protect the NVIDIA Confidential Information with at +least the same degree of care that you use to protect your own similar confidential and proprietary +information, but no less than a reasonable degree of care. Confidential information includes, but is not +limited to, the Software, including its features and functionality, Derivatives, and any results of +benchmarking or other competitive analysis or regression or performance data relating to the Software. +No Publicity. You may not issue any public statements about this Agreement, disclose the Software or +Derivatives, or any information or results related to your use of the Software, without prior written approval +of NVIDIA. + +11. Term and Termination. + +11.1 Term. This Agreement has a duration of twelve (12) months starting from the date of initial +download (even if you download the same version or updates of the Software later and it is +accompanied by this Agreement or another Agreement), unless terminated earlier in accordance +with this Agreement. + +11.2 Termination for Convenience. Either party may terminate this Agreement at any time with thirty (30) +days’ advance written notice to the other party. + +11.3 Termination for Cause. If you commence or participate in any legal proceeding against NVIDIA with +respect to the Software, this Agreement will terminate immediately without notice. Either party may +terminate this Agreement upon notice for cause if: +(a) the other party fails to cure a material breach of this Agreement within ten (10) days of the +non-breaching party’s notice of the breach; or +(b) the other party breaches its confidentiality obligations or license rights under this +Agreement, which termination will be effective immediately upon written notice. + +11.4 Effect of Termination. Upon any expiration or termination of this Agreement, you will promptly +(a) stop using and return, delete or destroy NVIDIA confidential information and all Software +received under this Agreement, and (b) delete or destroy Derivatives created under this Agreement, +unless an authorized NVIDIA representative provides prior written approval that you may keep a +copy of the Derivatives solely for archival purposes. Upon written request, you will certify in writing +that you have complied with your obligations under this “Effect of Termination” Section. + +11.5 Survival. The “License Grant to NVIDIA”, “Updates”, “Components Under Other Licenses”, +“Ownership”, “Feedback”, “Confidentiality”, “No Publicity”, “Effect of Termination”, “Survival”, +“Disclaimer of Warranties”, “Limitation of Liability”, “Indemnity” and all “General” Sections of this +Agreement will survive any expiration or termination of this Agreement. + +12. Disclaimer of Warranties. THE SOFTWARE IS PROVIDED BY NVIDIA AS-IS AND WITH ALL FAULTS. TO THE +MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, NVIDIA DISCLAIMS ALL WARRANTIES AND +REPRESENTATIONS OF ANY KIND, WHETHER EXPRESS, IMPLIED OR STATUTORY, RELATING TO OR ARISING +UNDER THIS AGREEMENT, INCLUDING, WITHOUT LIMITATION, THE WARRANTIES OF TITLE, +NONINFRINGEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, USAGE OF TRADE AND +COURSE OF DEALING. NVIDIA DOES NOT WARRANT OR ASSUME RESPONSIBILITY FOR THE ACCURACY OR +COMPLETENESS OF ANY THIRD-PARTY INFORMATION, TEXT, GRAPHICS, LINKS CONTAINED IN THE +SOFTWARE. WITHOUT LIMITING THE FOREGOING, NVIDIA DOES NOT WARRANT THAT THE SOFTWARE WILL +MEET YOUR REQUIREMENTS, ANY DEFECTS OR ERRORS WILL BE CORRECTED, ANY CERTAIN CONTENT WILL +BE AVAILABLE; OR THAT THE SOFTWARE IS FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS. NO +INFORMATION OR ADVICE GIVEN BY NVIDIA WILL IN ANY WAY INCREASE THE SCOPE OF ANY WARRANTY +EXPRESSLY PROVIDED IN THIS AGREEMENT. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE +APPROPRIATENESS OF USING THE SOFTWARE OR DERIVATIVES AND ASSUME ANY RISKS ASSOCIATED WITH +YOUR USE OF THE SOFTWARE OR DERIVATIVES. + +13. Limitations of Liability. + +13.1 EXCLUSIONS. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL +NVIDIA BE LIABLE FOR ANY (A) INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL OR CONSEQUENTIAL +DAMAGES, OR (B) DAMAGES FOR THE (I) COST OF PROCURING SUBSTITUTE GOODS OR (II) LOSS OF +PROFITS, REVENUES, USE, DATA OR GOODWILL ARISING OUT OF OR RELATED TO THIS AGREEMENT, +WHETHER BASED ON BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, OR +OTHERWISE, AND EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND +EVEN IF A PARTY’S REMEDIES FAIL THEIR ESSENTIAL PURPOSE. + +13.2 DAMAGES CAP. ADDITIONALLY, TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, +NVIDIA’S TOTAL CUMULATIVE AGGREGATE LIABILITY FOR ANY AND ALL LIABILITIES, OBLIGATIONS OR +CLAIMS ARISING OUT OF OR RELATED TO THIS AGREEMENT WILL NOT EXCEED ONE HUNDRED U.S. +DOLLARS (US$100). + +14. Indemnity. You will defend, indemnify and hold harmless NVIDIA and its affiliates, and their respective +employees, contractors, agents, officers and directors, from and against any and all third party claims, +damages, obligations, losses, liabilities, costs or debt, fines, restitutions and expenses (including but not +limited to attorney’s fees and costs incident to establishing the right of indemnification) arising out of use of +the Software and Derivatives outside of the scope of this Agreement or in breach of the terms of this +Agreement. + +15. General. + +15.1 Governing Law and Jurisdiction. This Agreement will be governed in all respects by the laws of the +United States and the laws of the State of Delaware, without regard to conflict of laws principles or +the United Nations Convention on Contracts for the International Sale of Goods. The state and +federal courts residing in Santa Clara County, California will have exclusive jurisdiction over any +dispute or claim arising out of or related to this Agreement, and the parties irrevocably consent to +personal jurisdiction and venue in those courts; except that either party may apply for injunctive +remedies or an equivalent type of urgent legal relief in any jurisdiction. + +15.2 Independent Contractors. The parties are independent contractors, and this Agreement does not +create a joint venture, partnership, agency or other form of business association between the +parties. Neither party will have the power to bind the other party or incur any obligation on its +behalf without the other party’s prior written consent. Nothing in this Agreement prevents either +party from participating in similar arrangements with third parties. + +15.3 No Assignment. NVIDIA may assign, delegate or transfer its rights or obligations under this +Agreement by any means or operation of law. You may not, without NVIDIA’s prior written consent, +assign, delegate or transfer any of your rights or obligations under this Agreement by any means or +operation of law, and any attempt to do so is null and void. + +15.4 No Waiver. No failure or delay by a party to enforce any term or obligation of this Agreement will +operate as a waiver by that party, or prevent the enforcement of such term or obligation later. + +15.5 Trade Compliance. You agree to comply with all applicable export, import, trade and economic +sanctions laws and regulations, as amended, including without limitation U.S. Export Administration +Regulations and Office of Foreign Assets Control regulations. You confirm (a) your understanding +that export or reexport of certain NVIDIA products or technologies may require a license or other +approval from appropriate authorities and (b) that you will not export or reexport any products or +technology, directly or indirectly, without first obtaining any required license or other approval from +appropriate authorities, (i) to any countries that are subject to any U.S. or local export restrictions +(currently including, but not necessarily limited to, Belarus, Cuba, Iran, North Korea, Russia, Syria, +the Region of Crimea, Donetsk People’s Republic Region and Luhansk People’s Republic Region); (ii) +to any end-user who you know or have reason to know will utilize them in the design, development +or production of nuclear, chemical or biological weapons, missiles, rocket systems, unmanned air +vehicles capable of a maximum range of at least 300 kilometers, regardless of payload, or intended +for military end-use, or any weapons of mass destruction; (iii) to any end-user who has been +prohibited from participating in the U.S. or local export transactions by any governing authority; or +(iv) to any known military or military-intelligence end-user or for any known military or military- +intelligence end-use in accordance with U.S. trade compliance laws and regulations. + +15.6 Government Rights. The Software, documentation and technology (“Protected Items”) are +“Commercial products” as this term is defined at 48 C.F.R. 2.101, consisting of “commercial +computer software” and “commercial computer software documentation” as such terms are used +in, respectively, 48 C.F.R. 12.212 and 48 C.F.R. 227.7202 & 252.227-7014(a)(1). Before any Protected +Items are supplied to the U.S. Government, you will (i) inform the U.S. Government in writing that +the Protected Items are and must be treated as commercial computer software and commercial +computer software documentation developed at private expense; (ii) inform the U.S. Government +that the Protected Items are provided subject to the terms of the Agreement; and (iii) mark the +Protected Items as commercial computer software and commercial computer software +documentation developed at private expense. In no event will you permit the U.S. Government to +acquire rights in Protected Items beyond those specified in 48 C.F.R. 52.227-19(b)(1)-(2) or 252.227- +7013(c) except as expressly approved by NVIDIA in writing. + +15.7 Notices. Please direct your legal notices or other correspondence to legalnotices@nvidia.com with a +copy mailed to NVIDIA Corporation, 2788 San Tomas Expressway, Santa Clara, California 95051, +United States of America, Attention: Legal Department. If NVIDIA needs to contact you about the +Software, you consent to receive the notices by email and agree that such notices will satisfy any +legal communication requirements. + +15.8 Severability. If a court of competent jurisdiction rules that a provision of this Agreement is +unenforceable, that provision will be deemed modified to the extent necessary to make it +enforceable and the remainder of this Agreement will continue in full force and effect. + +15.9 Amendment. Any amendment to this Agreement must be in writing and signed by authorized +representatives of both parties. + +15.10 Entire Agreement. Regarding the subject matter of this Agreement, the parties agree that (a) this +Agreement constitutes the entire and exclusive agreement between the parties and supersedes all +prior and contemporaneous communications and (b) any additional or different terms or conditions, +whether contained in purchase orders, order acknowledgments, invoices or otherwise, will not be +binding and are null and void. + +(v. February 25, 2025) + +NVIDIA Confidential \ No newline at end of file diff --git a/deps/cloudxr/README.md b/deps/cloudxr/README.md new file mode 100644 index 0000000..9de9619 --- /dev/null +++ b/deps/cloudxr/README.md @@ -0,0 +1,71 @@ +# NVIDIA CloudXR.js SDK - Getting Started + +> **Note:** For detailed documentation, see the `docs/` folder (open `docs/index.html` in browser). + +[The NVIDIA CloudXR.js SDK](#) enables developers to build web clients that stream high-quality spatial content from CloudXR servers with powerful GPUs to web browsers on XR devices. It consists of: + - **CloudXR.js SDK** - a JavaScript client library + - **WebGL-based simple sample client** that uses core Web APIs (WebGL, WebXR) + - **React-based sample client** that uses the R3F (React-Three-Fiber) framework stack + + + +It is *strongly recommended* that you work through this guide if you have never run CloudXR.js before. + +## The Pieces of a CloudXR.js Deployment + +Even for development, you'll need all the pieces of a CloudXR.js deployment in order to build and test a client. These are: + +- a CloudXR Server + - with a _top-end_ NVIDIA GPU or 2 (e.g. dual RTX 6000 Ada) + - which will run + - the CloudXR Runtime + - an OpenXR application (the thing you want to render on the server but see on the client) +- a CloudXR.js development workstation + - with Node.js and `npm` + - which will run + - the CloudXR.js sample client build + - a Node-based development web server +- a CloudXR.js client + - which is one of: + - a Meta Quest 3 with its built-in Browser app + - a desktop browser: [Google Chrome](https://www.google.com/chrome) or Edge (IWER automatically loads for WebXR emulation) + - which will run... + - the CloudXR.js sample client *served from the development web server*. + +We *recommend* that for your first experience, all above run on *the same computer* to eliminate networking related issues. + +## High Level Workflow + +You need both a working client and a working server in order to test. Typically we follow a startup flow where server starts before the client: + +1. CloudXR Runtime +2. Server XR application +3. Sample client build + web server +4. Test from the same computer +5. Test from Quest 3 or a different computer + + + +Please refer to [the CloudXR.js SDK documentations](#sdk-documentation) for details. + +## SDK Documentation + +The `docs/` folder contains comprehensive documentation including: +- Getting Started guides with example server applications +- API reference for building custom clients +- Common issues and solutions + +Open `docs/index.html` in your browser for organized navigation with table of contents. + +## Support + +For technical support and questions about this early access release: +- Review the troubleshooting sections in the SDK documentation +- Study the example implementations for integration patterns +- Contact the NVIDIA CloudXR team for additional assistance + +## License + +> **Important**: This is an [evaluation license](LICENSE) for internal test and evaluation purposes only. For commercial use, please contact NVIDIA for appropriate licensing terms. + +> **Note**: This is an early access release of CloudXR.js. Features and APIs may change in future releases. diff --git a/deps/cloudxr/docs/.nojekyll b/deps/cloudxr/docs/.nojekyll new file mode 100644 index 0000000..e2ac661 --- /dev/null +++ b/deps/cloudxr/docs/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/deps/cloudxr/docs/assets/hierarchy.js b/deps/cloudxr/docs/assets/hierarchy.js new file mode 100644 index 0000000..fb85f0a --- /dev/null +++ b/deps/cloudxr/docs/assets/hierarchy.js @@ -0,0 +1 @@ +window.hierarchyData = "eJyrVirKzy8pVrKKjtVRKkpNy0lNLsnMzytWsqqurQUAmx4Kpg==" \ No newline at end of file diff --git a/deps/cloudxr/docs/assets/highlight.css b/deps/cloudxr/docs/assets/highlight.css new file mode 100644 index 0000000..0573c66 --- /dev/null +++ b/deps/cloudxr/docs/assets/highlight.css @@ -0,0 +1,120 @@ +:root { + --light-hl-0: #795E26; + --dark-hl-0: #DCDCAA; + --light-hl-1: #000000; + --dark-hl-1: #D4D4D4; + --light-hl-2: #A31515; + --dark-hl-2: #CE9178; + --light-hl-3: #008000; + --dark-hl-3: #6A9955; + --light-hl-4: #001080; + --dark-hl-4: #9CDCFE; + --light-hl-5: #0070C1; + --dark-hl-5: #4FC1FF; + --light-hl-6: #0000FF; + --dark-hl-6: #569CD6; + --light-hl-7: #098658; + --dark-hl-7: #B5CEA8; + --light-hl-8: #000000; + --dark-hl-8: #C8C8C8; + --light-hl-9: #AF00DB; + --dark-hl-9: #C586C0; + --light-hl-10: #EE0000; + --dark-hl-10: #D7BA7D; + --light-hl-11: #800000; + --dark-hl-11: #569CD6; + --light-hl-12: #0000FF; + --dark-hl-12: #CE9178; + --light-hl-13: #267F99; + --dark-hl-13: #4EC9B0; + --light-code-background: #FFFFFF; + --dark-code-background: #1E1E1E; +} + +@media (prefers-color-scheme: light) { :root { + --hl-0: var(--light-hl-0); + --hl-1: var(--light-hl-1); + --hl-2: var(--light-hl-2); + --hl-3: var(--light-hl-3); + --hl-4: var(--light-hl-4); + --hl-5: var(--light-hl-5); + --hl-6: var(--light-hl-6); + --hl-7: var(--light-hl-7); + --hl-8: var(--light-hl-8); + --hl-9: var(--light-hl-9); + --hl-10: var(--light-hl-10); + --hl-11: var(--light-hl-11); + --hl-12: var(--light-hl-12); + --hl-13: var(--light-hl-13); + --code-background: var(--light-code-background); +} } + +@media (prefers-color-scheme: dark) { :root { + --hl-0: var(--dark-hl-0); + --hl-1: var(--dark-hl-1); + --hl-2: var(--dark-hl-2); + --hl-3: var(--dark-hl-3); + --hl-4: var(--dark-hl-4); + --hl-5: var(--dark-hl-5); + --hl-6: var(--dark-hl-6); + --hl-7: var(--dark-hl-7); + --hl-8: var(--dark-hl-8); + --hl-9: var(--dark-hl-9); + --hl-10: var(--dark-hl-10); + --hl-11: var(--dark-hl-11); + --hl-12: var(--dark-hl-12); + --hl-13: var(--dark-hl-13); + --code-background: var(--dark-code-background); +} } + +:root[data-theme='light'] { + --hl-0: var(--light-hl-0); + --hl-1: var(--light-hl-1); + --hl-2: var(--light-hl-2); + --hl-3: var(--light-hl-3); + --hl-4: var(--light-hl-4); + --hl-5: var(--light-hl-5); + --hl-6: var(--light-hl-6); + --hl-7: var(--light-hl-7); + --hl-8: var(--light-hl-8); + --hl-9: var(--light-hl-9); + --hl-10: var(--light-hl-10); + --hl-11: var(--light-hl-11); + --hl-12: var(--light-hl-12); + --hl-13: var(--light-hl-13); + --code-background: var(--light-code-background); +} + +:root[data-theme='dark'] { + --hl-0: var(--dark-hl-0); + --hl-1: var(--dark-hl-1); + --hl-2: var(--dark-hl-2); + --hl-3: var(--dark-hl-3); + --hl-4: var(--dark-hl-4); + --hl-5: var(--dark-hl-5); + --hl-6: var(--dark-hl-6); + --hl-7: var(--dark-hl-7); + --hl-8: var(--dark-hl-8); + --hl-9: var(--dark-hl-9); + --hl-10: var(--dark-hl-10); + --hl-11: var(--dark-hl-11); + --hl-12: var(--dark-hl-12); + --hl-13: var(--dark-hl-13); + --code-background: var(--dark-code-background); +} + +.hl-0 { color: var(--hl-0); } +.hl-1 { color: var(--hl-1); } +.hl-2 { color: var(--hl-2); } +.hl-3 { color: var(--hl-3); } +.hl-4 { color: var(--hl-4); } +.hl-5 { color: var(--hl-5); } +.hl-6 { color: var(--hl-6); } +.hl-7 { color: var(--hl-7); } +.hl-8 { color: var(--hl-8); } +.hl-9 { color: var(--hl-9); } +.hl-10 { color: var(--hl-10); } +.hl-11 { color: var(--hl-11); } +.hl-12 { color: var(--hl-12); } +.hl-13 { color: var(--hl-13); } +pre, code { background: var(--code-background); } diff --git a/deps/cloudxr/docs/assets/icons.js b/deps/cloudxr/docs/assets/icons.js new file mode 100644 index 0000000..58882d7 --- /dev/null +++ b/deps/cloudxr/docs/assets/icons.js @@ -0,0 +1,18 @@ +(function() { + addIcons(); + function addIcons() { + if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); + const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); + svg.innerHTML = `MMNEPVFCICPMFPCPTTAAATR`; + svg.style.display = "none"; + if (location.protocol === "file:") updateUseElements(); + } + + function updateUseElements() { + document.querySelectorAll("use").forEach(el => { + if (el.getAttribute("href").includes("#icon-")) { + el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); + } + }); + } +})() \ No newline at end of file diff --git a/deps/cloudxr/docs/assets/icons.svg b/deps/cloudxr/docs/assets/icons.svg new file mode 100644 index 0000000..50ad579 --- /dev/null +++ b/deps/cloudxr/docs/assets/icons.svg @@ -0,0 +1 @@ +MMNEPVFCICPMFPCPTTAAATR \ No newline at end of file diff --git a/deps/cloudxr/docs/assets/main.js b/deps/cloudxr/docs/assets/main.js new file mode 100644 index 0000000..2363f64 --- /dev/null +++ b/deps/cloudxr/docs/assets/main.js @@ -0,0 +1,60 @@ +"use strict"; +window.translations={"copy":"Copy","copied":"Copied!","normally_hidden":"This member is normally hidden due to your filter settings.","hierarchy_expand":"Expand","hierarchy_collapse":"Collapse","folder":"Folder","kind_1":"Project","kind_2":"Module","kind_4":"Namespace","kind_8":"Enumeration","kind_16":"Enumeration Member","kind_32":"Variable","kind_64":"Function","kind_128":"Class","kind_256":"Interface","kind_512":"Constructor","kind_1024":"Property","kind_2048":"Method","kind_4096":"Call Signature","kind_8192":"Index Signature","kind_16384":"Constructor Signature","kind_32768":"Parameter","kind_65536":"Type Literal","kind_131072":"Type Parameter","kind_262144":"Accessor","kind_524288":"Get Signature","kind_1048576":"Set Signature","kind_2097152":"Type Alias","kind_4194304":"Reference","kind_8388608":"Document"}; +"use strict";(()=>{var De=Object.create;var le=Object.defineProperty;var Fe=Object.getOwnPropertyDescriptor;var Ne=Object.getOwnPropertyNames;var Ve=Object.getPrototypeOf,Be=Object.prototype.hasOwnProperty;var qe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var je=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Ne(e))!Be.call(t,i)&&i!==n&&le(t,i,{get:()=>e[i],enumerable:!(r=Fe(e,i))||r.enumerable});return t};var $e=(t,e,n)=>(n=t!=null?De(Ve(t)):{},je(e||!t||!t.__esModule?le(n,"default",{value:t,enumerable:!0}):n,t));var pe=qe((de,he)=>{(function(){var t=function(e){var n=new t.Builder;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),n.searchPipeline.add(t.stemmer),e.call(n,n),n.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(n){e.console&&console.warn&&console.warn(n)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var n=Object.create(null),r=Object.keys(e),i=0;i0){var d=t.utils.clone(n)||{};d.position=[a,c],d.index=s.length,s.push(new t.Token(r.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. +`,e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(r){var i=t.Pipeline.registeredFunctions[r];if(i)n.add(i);else throw new Error("Cannot load unregistered function: "+r)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(n){t.Pipeline.warnIfFunctionNotRegistered(n),this._stack.push(n)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");r=r+1,this._stack.splice(r,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");this._stack.splice(r,0,n)},t.Pipeline.prototype.remove=function(e){var n=this._stack.indexOf(e);n!=-1&&this._stack.splice(n,1)},t.Pipeline.prototype.run=function(e){for(var n=this._stack.length,r=0;r1&&(oe&&(r=s),o!=e);)i=r-n,s=n+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ol?d+=2:a==l&&(n+=r[c+1]*i[d+1],c+=2,d+=2);return n},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),n=1,r=0;n0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}if(s.str.length==0&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}s.str.length==1&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),m=s.str.charAt(1),p;m in s.node.edges?p=s.node.edges[m]:(p=new t.TokenSet,s.node.edges[m]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return r},t.TokenSet.fromString=function(e){for(var n=new t.TokenSet,r=n,i=0,s=e.length;i=e;n--){var r=this.uncheckedNodes[n],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(n){var r=new t.QueryParser(e,n);r.parse()})},t.Index.prototype.query=function(e){for(var n=new t.Query(this.fields),r=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),l=0;l1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,n){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=n||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,n;do e=this.next(),n=e.charCodeAt(0);while(n>47&&n<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var n=e.next();if(n==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(n.charCodeAt(0)==92){e.escapeCharacter();continue}if(n==":")return t.QueryLexer.lexField;if(n=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(n=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(n=="+"&&e.width()===1||n=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(n.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,n){this.lexer=new t.QueryLexer(e),this.query=n,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var n=e.peekLexeme();if(n!=null)switch(n.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+n.type;throw n.str.length>=1&&(r+=" with value '"+n.str+"'"),new t.QueryParseError(r,n.start,n.end)}},t.QueryParser.parsePresence=function(e){var n=e.consumeLexeme();if(n!=null){switch(n.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+n.str+"'";throw new t.QueryParseError(r,n.start,n.end)}var i=e.peekLexeme();if(i==null){var r="expecting term or field, found nothing";throw new t.QueryParseError(r,n.start,n.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(r,i.start,i.end)}}},t.QueryParser.parseField=function(e){var n=e.consumeLexeme();if(n!=null){if(e.query.allFields.indexOf(n.str)==-1){var r=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+n.str+"', possible fields: "+r;throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.fields=[n.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,n.start,n.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var n=e.consumeLexeme();if(n!=null){e.currentClause.term=n.str.toLowerCase(),n.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(r==null){e.nextClause();return}switch(r.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new t.QueryParseError(i,r.start,r.end)}}},t.QueryParser.parseEditDistance=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.editDistance=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="boost must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.boost=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,n){typeof define=="function"&&define.amd?define(n):typeof de=="object"?he.exports=n():e.lunr=n()}(this,function(){return t})})()});window.translations||={copy:"Copy",copied:"Copied!",normally_hidden:"This member is normally hidden due to your filter settings.",hierarchy_expand:"Expand",hierarchy_collapse:"Collapse",folder:"Folder",kind_1:"Project",kind_2:"Module",kind_4:"Namespace",kind_8:"Enumeration",kind_16:"Enumeration Member",kind_32:"Variable",kind_64:"Function",kind_128:"Class",kind_256:"Interface",kind_512:"Constructor",kind_1024:"Property",kind_2048:"Method",kind_4096:"Call Signature",kind_8192:"Index Signature",kind_16384:"Constructor Signature",kind_32768:"Parameter",kind_65536:"Type Literal",kind_131072:"Type Parameter",kind_262144:"Accessor",kind_524288:"Get Signature",kind_1048576:"Set Signature",kind_2097152:"Type Alias",kind_4194304:"Reference",kind_8388608:"Document"};var ce=[];function G(t,e){ce.push({selector:e,constructor:t})}var J=class{alwaysVisibleMember=null;constructor(){this.createComponents(document.body),this.ensureFocusedElementVisible(),this.listenForCodeCopies(),window.addEventListener("hashchange",()=>this.ensureFocusedElementVisible()),document.body.style.display||(this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}createComponents(e){ce.forEach(n=>{e.querySelectorAll(n.selector).forEach(r=>{r.dataset.hasInstance||(new n.constructor({el:r,app:this}),r.dataset.hasInstance=String(!0))})})}filterChanged(){this.ensureFocusedElementVisible()}showPage(){document.body.style.display&&(document.body.style.removeProperty("display"),this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}scrollToHash(){if(location.hash){let e=document.getElementById(location.hash.substring(1));if(!e)return;e.scrollIntoView({behavior:"instant",block:"start"})}}ensureActivePageVisible(){let e=document.querySelector(".tsd-navigation .current"),n=e?.parentElement;for(;n&&!n.classList.contains(".tsd-navigation");)n instanceof HTMLDetailsElement&&(n.open=!0),n=n.parentElement;if(e&&!ze(e)){let r=e.getBoundingClientRect().top-document.documentElement.clientHeight/4;document.querySelector(".site-menu").scrollTop=r,document.querySelector(".col-sidebar").scrollTop=r}}updateIndexVisibility(){let e=document.querySelector(".tsd-index-content"),n=e?.open;e&&(e.open=!0),document.querySelectorAll(".tsd-index-section").forEach(r=>{r.style.display="block";let i=Array.from(r.querySelectorAll(".tsd-index-link")).every(s=>s.offsetParent==null);r.style.display=i?"none":"block"}),e&&(e.open=n)}ensureFocusedElementVisible(){if(this.alwaysVisibleMember&&(this.alwaysVisibleMember.classList.remove("always-visible"),this.alwaysVisibleMember.firstElementChild.remove(),this.alwaysVisibleMember=null),!location.hash)return;let e=document.getElementById(location.hash.substring(1));if(!e)return;let n=e.parentElement;for(;n&&n.tagName!=="SECTION";)n=n.parentElement;if(!n)return;let r=n.offsetParent==null,i=n;for(;i!==document.body;)i instanceof HTMLDetailsElement&&(i.open=!0),i=i.parentElement;if(n.offsetParent==null){this.alwaysVisibleMember=n,n.classList.add("always-visible");let s=document.createElement("p");s.classList.add("warning"),s.textContent=window.translations.normally_hidden,n.prepend(s)}r&&e.scrollIntoView()}listenForCodeCopies(){document.querySelectorAll("pre > button").forEach(e=>{let n;e.addEventListener("click",()=>{e.previousElementSibling instanceof HTMLElement&&navigator.clipboard.writeText(e.previousElementSibling.innerText.trim()),e.textContent=window.translations.copied,e.classList.add("visible"),clearTimeout(n),n=setTimeout(()=>{e.classList.remove("visible"),n=setTimeout(()=>{e.textContent=window.translations.copy},100)},1e3)})})}};function ze(t){let e=t.getBoundingClientRect(),n=Math.max(document.documentElement.clientHeight,window.innerHeight);return!(e.bottom<0||e.top-n>=0)}var ue=(t,e=100)=>{let n;return()=>{clearTimeout(n),n=setTimeout(()=>t(),e)}};var ge=$e(pe(),1);async function A(t){let e=Uint8Array.from(atob(t),s=>s.charCodeAt(0)),r=new Blob([e]).stream().pipeThrough(new DecompressionStream("deflate")),i=await new Response(r).text();return JSON.parse(i)}async function fe(t,e){if(!window.searchData)return;let n=await A(window.searchData);t.data=n,t.index=ge.Index.load(n.index),e.classList.remove("loading"),e.classList.add("ready")}function ve(){let t=document.getElementById("tsd-search");if(!t)return;let e={base:document.documentElement.dataset.base+"/"},n=document.getElementById("tsd-search-script");t.classList.add("loading"),n&&(n.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),n.addEventListener("load",()=>{fe(e,t)}),fe(e,t));let r=document.querySelector("#tsd-search input"),i=document.querySelector("#tsd-search .results");if(!r||!i)throw new Error("The input field or the result list wrapper was not found");i.addEventListener("mouseup",()=>{re(t)}),r.addEventListener("focus",()=>t.classList.add("has-focus")),We(t,i,r,e)}function We(t,e,n,r){n.addEventListener("input",ue(()=>{Ue(t,e,n,r)},200)),n.addEventListener("keydown",i=>{i.key=="Enter"?Je(e,t):i.key=="ArrowUp"?(me(e,n,-1),i.preventDefault()):i.key==="ArrowDown"&&(me(e,n,1),i.preventDefault())}),document.body.addEventListener("keypress",i=>{i.altKey||i.ctrlKey||i.metaKey||!n.matches(":focus")&&i.key==="/"&&(i.preventDefault(),n.focus())}),document.body.addEventListener("keyup",i=>{t.classList.contains("has-focus")&&(i.key==="Escape"||!e.matches(":focus-within")&&!n.matches(":focus"))&&(n.blur(),re(t))})}function re(t){t.classList.remove("has-focus")}function Ue(t,e,n,r){if(!r.index||!r.data)return;e.textContent="";let i=n.value.trim(),s;if(i){let o=i.split(" ").map(a=>a.length?`*${a}*`:"").join(" ");s=r.index.search(o)}else s=[];for(let o=0;oa.score-o.score);for(let o=0,a=Math.min(10,s.length);o`,d=ye(l.name,i);globalThis.DEBUG_SEARCH_WEIGHTS&&(d+=` (score: ${s[o].score.toFixed(2)})`),l.parent&&(d=` + ${ye(l.parent,i)}.${d}`);let m=document.createElement("li");m.classList.value=l.classes??"";let p=document.createElement("a");p.href=r.base+l.url,p.innerHTML=c+d,m.append(p),p.addEventListener("focus",()=>{e.querySelector(".current")?.classList.remove("current"),m.classList.add("current")}),e.appendChild(m)}}function me(t,e,n){let r=t.querySelector(".current");if(!r)r=t.querySelector(n==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let i=r;if(n===1)do i=i.nextElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);else do i=i.previousElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);i?(r.classList.remove("current"),i.classList.add("current")):n===-1&&(r.classList.remove("current"),e.focus())}}function Je(t,e){let n=t.querySelector(".current");if(n||(n=t.querySelector("li:first-child")),n){let r=n.querySelector("a");r&&(window.location.href=r.href),re(e)}}function ye(t,e){if(e==="")return t;let n=t.toLocaleLowerCase(),r=e.toLocaleLowerCase(),i=[],s=0,o=n.indexOf(r);for(;o!=-1;)i.push(ne(t.substring(s,o)),`${ne(t.substring(o,o+r.length))}`),s=o+r.length,o=n.indexOf(r,s);return i.push(ne(t.substring(s))),i.join("")}var Ge={"&":"&","<":"<",">":">","'":"'",'"':"""};function ne(t){return t.replace(/[&<>"'"]/g,e=>Ge[e])}var I=class{el;app;constructor(e){this.el=e.el,this.app=e.app}};var H="mousedown",Ee="mousemove",B="mouseup",X={x:0,y:0},xe=!1,ie=!1,Xe=!1,D=!1,be=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(be?"is-mobile":"not-mobile");be&&"ontouchstart"in document.documentElement&&(Xe=!0,H="touchstart",Ee="touchmove",B="touchend");document.addEventListener(H,t=>{ie=!0,D=!1;let e=H=="touchstart"?t.targetTouches[0]:t;X.y=e.pageY||0,X.x=e.pageX||0});document.addEventListener(Ee,t=>{if(ie&&!D){let e=H=="touchstart"?t.targetTouches[0]:t,n=X.x-(e.pageX||0),r=X.y-(e.pageY||0);D=Math.sqrt(n*n+r*r)>10}});document.addEventListener(B,()=>{ie=!1});document.addEventListener("click",t=>{xe&&(t.preventDefault(),t.stopImmediatePropagation(),xe=!1)});var Y=class extends I{active;className;constructor(e){super(e),this.className=this.el.dataset.toggle||"",this.el.addEventListener(B,n=>this.onPointerUp(n)),this.el.addEventListener("click",n=>n.preventDefault()),document.addEventListener(H,n=>this.onDocumentPointerDown(n)),document.addEventListener(B,n=>this.onDocumentPointerUp(n))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let n=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(n),setTimeout(()=>document.documentElement.classList.remove(n),500)}onPointerUp(e){D||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-sidebar, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!D&&this.active&&e.target.closest(".col-sidebar")){let n=e.target.closest("a");if(n){let r=window.location.href;r.indexOf("#")!=-1&&(r=r.substring(0,r.indexOf("#"))),n.href.substring(0,r.length)==r&&setTimeout(()=>this.setActive(!1),250)}}}};var se;try{se=localStorage}catch{se={getItem(){return null},setItem(){}}}var C=se;var Le=document.head.appendChild(document.createElement("style"));Le.dataset.for="filters";var Z=class extends I{key;value;constructor(e){super(e),this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),Le.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } +`,this.app.updateIndexVisibility()}fromLocalStorage(){let e=C.getItem(this.key);return e?e==="true":this.el.checked}setLocalStorage(e){C.setItem(this.key,e.toString()),this.value=e,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),this.app.filterChanged(),this.app.updateIndexVisibility()}};var oe=new Map,ae=class{open;accordions=[];key;constructor(e,n){this.key=e,this.open=n}add(e){this.accordions.push(e),e.open=this.open,e.addEventListener("toggle",()=>{this.toggle(e.open)})}toggle(e){for(let n of this.accordions)n.open=e;C.setItem(this.key,e.toString())}},K=class extends I{constructor(e){super(e);let n=this.el.querySelector("summary"),r=n.querySelector("a");r&&r.addEventListener("click",()=>{location.assign(r.href)});let i=`tsd-accordion-${n.dataset.key??n.textContent.trim().replace(/\s+/g,"-").toLowerCase()}`,s;if(oe.has(i))s=oe.get(i);else{let o=C.getItem(i),a=o?o==="true":this.el.open;s=new ae(i,a),oe.set(i,s)}s.add(this.el)}};function Se(t){let e=C.getItem("tsd-theme")||"os";t.value=e,we(e),t.addEventListener("change",()=>{C.setItem("tsd-theme",t.value),we(t.value)})}function we(t){document.documentElement.dataset.theme=t}var ee;function Ce(){let t=document.getElementById("tsd-nav-script");t&&(t.addEventListener("load",Te),Te())}async function Te(){let t=document.getElementById("tsd-nav-container");if(!t||!window.navigationData)return;let e=await A(window.navigationData);ee=document.documentElement.dataset.base,ee.endsWith("/")||(ee+="/"),t.innerHTML="";for(let n of e)Ie(n,t,[]);window.app.createComponents(t),window.app.showPage(),window.app.ensureActivePageVisible()}function Ie(t,e,n){let r=e.appendChild(document.createElement("li"));if(t.children){let i=[...n,t.text],s=r.appendChild(document.createElement("details"));s.className=t.class?`${t.class} tsd-accordion`:"tsd-accordion";let o=s.appendChild(document.createElement("summary"));o.className="tsd-accordion-summary",o.dataset.key=i.join("$"),o.innerHTML='',ke(t,o);let a=s.appendChild(document.createElement("div"));a.className="tsd-accordion-details";let l=a.appendChild(document.createElement("ul"));l.className="tsd-nested-navigation";for(let c of t.children)Ie(c,l,i)}else ke(t,r,t.class)}function ke(t,e,n){if(t.path){let r=e.appendChild(document.createElement("a"));if(r.href=ee+t.path,n&&(r.className=n),location.pathname===r.pathname&&!r.href.includes("#")&&r.classList.add("current"),t.kind){let i=window.translations[`kind_${t.kind}`].replaceAll('"',""");r.innerHTML=``}r.appendChild(document.createElement("span")).textContent=t.text}else{let r=e.appendChild(document.createElement("span")),i=window.translations.folder.replaceAll('"',""");r.innerHTML=``,r.appendChild(document.createElement("span")).textContent=t.text}}var te=document.documentElement.dataset.base;te.endsWith("/")||(te+="/");function Pe(){document.querySelector(".tsd-full-hierarchy")?Ye():document.querySelector(".tsd-hierarchy")&&Ze()}function Ye(){document.addEventListener("click",r=>{let i=r.target;for(;i.parentElement&&i.parentElement.tagName!="LI";)i=i.parentElement;i.dataset.dropdown&&(i.dataset.dropdown=String(i.dataset.dropdown!=="true"))});let t=new Map,e=new Set;for(let r of document.querySelectorAll(".tsd-full-hierarchy [data-refl]")){let i=r.querySelector("ul");t.has(r.dataset.refl)?e.add(r.dataset.refl):i&&t.set(r.dataset.refl,i)}for(let r of e)n(r);function n(r){let i=t.get(r).cloneNode(!0);i.querySelectorAll("[id]").forEach(s=>{s.removeAttribute("id")}),i.querySelectorAll("[data-dropdown]").forEach(s=>{s.dataset.dropdown="false"});for(let s of document.querySelectorAll(`[data-refl="${r}"]`)){let o=tt(),a=s.querySelector("ul");s.insertBefore(o,a),o.dataset.dropdown=String(!!a),a||s.appendChild(i.cloneNode(!0))}}}function Ze(){let t=document.getElementById("tsd-hierarchy-script");t&&(t.addEventListener("load",Qe),Qe())}async function Qe(){let t=document.querySelector(".tsd-panel.tsd-hierarchy:has(h4 a)");if(!t||!window.hierarchyData)return;let e=+t.dataset.refl,n=await A(window.hierarchyData),r=t.querySelector("ul"),i=document.createElement("ul");if(i.classList.add("tsd-hierarchy"),Ke(i,n,e),r.querySelectorAll("li").length==i.querySelectorAll("li").length)return;let s=document.createElement("span");s.classList.add("tsd-hierarchy-toggle"),s.textContent=window.translations.hierarchy_expand,t.querySelector("h4 a")?.insertAdjacentElement("afterend",s),s.insertAdjacentText("beforebegin",", "),s.addEventListener("click",()=>{s.textContent===window.translations.hierarchy_expand?(r.insertAdjacentElement("afterend",i),r.remove(),s.textContent=window.translations.hierarchy_collapse):(i.insertAdjacentElement("afterend",r),i.remove(),s.textContent=window.translations.hierarchy_expand)})}function Ke(t,e,n){let r=e.roots.filter(i=>et(e,i,n));for(let i of r)t.appendChild(_e(e,i,n))}function _e(t,e,n,r=new Set){if(r.has(e))return;r.add(e);let i=t.reflections[e],s=document.createElement("li");if(s.classList.add("tsd-hierarchy-item"),e===n){let o=s.appendChild(document.createElement("span"));o.textContent=i.name,o.classList.add("tsd-hierarchy-target")}else{for(let a of i.uniqueNameParents||[]){let l=t.reflections[a],c=s.appendChild(document.createElement("a"));c.textContent=l.name,c.href=te+l.url,c.className=l.class+" tsd-signature-type",s.append(document.createTextNode("."))}let o=s.appendChild(document.createElement("a"));o.textContent=t.reflections[e].name,o.href=te+i.url,o.className=i.class+" tsd-signature-type"}if(i.children){let o=s.appendChild(document.createElement("ul"));o.classList.add("tsd-hierarchy");for(let a of i.children){let l=_e(t,a,n,r);l&&o.appendChild(l)}}return r.delete(e),s}function et(t,e,n){if(e===n)return!0;let r=new Set,i=[t.reflections[e]];for(;i.length;){let s=i.pop();if(!r.has(s)){r.add(s);for(let o of s.children||[]){if(o===n)return!0;i.push(t.reflections[o])}}}return!1}function tt(){let t=document.createElementNS("http://www.w3.org/2000/svg","svg");return t.setAttribute("width","20"),t.setAttribute("height","20"),t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","none"),t.innerHTML='',t}G(Y,"a[data-toggle]");G(K,".tsd-accordion");G(Z,".tsd-filter-item input[type=checkbox]");var Oe=document.getElementById("tsd-theme");Oe&&Se(Oe);var nt=new J;Object.defineProperty(window,"app",{value:nt});ve();Ce();Pe();})(); +/*! Bundled license information: + +lunr/lunr.js: + (** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + *) + (*! + * lunr.utils + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Set + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.tokenizer + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Pipeline + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Vector + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.stemmer + * Copyright (C) 2020 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + *) + (*! + * lunr.stopWordFilter + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.trimmer + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.TokenSet + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Index + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Builder + * Copyright (C) 2020 Oliver Nightingale + *) +*/ diff --git a/deps/cloudxr/docs/assets/navigation.js b/deps/cloudxr/docs/assets/navigation.js new file mode 100644 index 0000000..e252c89 --- /dev/null +++ b/deps/cloudxr/docs/assets/navigation.js @@ -0,0 +1 @@ +window.navigationData = "eJydlG9LwzAQxr9KyOv5B6dj7N1QkMF00zoURI6sva3FNinpdVPE7y7NYtd2nW1923vu9zxJ7vr6xQk/iI/4bIN6E+CW93gsyOcj7ik3jVBScvZbO/UpCnmPvwfS46NhfzgcnA+/eznjFokCuWYOCU3o1aGsBKykltjjrh+EnkbJR6853BFRHCK7DgOUxE7YMy5vp20sdo2wa4QTMI1NR6m6PaJw6T9uprHe7a3oh0kSKMnG80mdiy3DeD5pSm4jO0hpXIeyyUy9iXWPtFX63bzpMd5e047p+GrLrkWCSe1BfbUFU+0wGpNECJdNxZI9YYiqNmYBbOQwFUvYybsMoZ3wRcwc1BvUbKW0dUUtKFCyq7lFwiKGHRJWSkMJ2XSlBbFcMyXZHZJgDykmxPpd85RgoCRkMDAw6Dclqb4Ee9IqXYaY+EplvK5hqh+gwju2WG+V24mQ9GedeV5svOPmg7TKVlxUlXovj3tSpLw0xGxDTaHcf3FsIM2PwSFBuCehTKM9p6iphDr8A91giGtBxe0MJKFeCRcPkLm4kvVqcAiexdkot8JaaQtoG9pfGFejIDyArVLpmgg5qyQsEweX2cD9API9f+w=" \ No newline at end of file diff --git a/deps/cloudxr/docs/assets/search.js b/deps/cloudxr/docs/assets/search.js new file mode 100644 index 0000000..ff66d9b --- /dev/null +++ b/deps/cloudxr/docs/assets/search.js @@ -0,0 +1 @@ +window.searchData = "eJy1fduO5LiR6K80yg/TC2g14lXSvNle7x7jeHd9PN4L0Bg0VFmqKnmyUmmlsqrbA//7QUSQTJFSSJnd7X6paCkVJIPBuJP85W7o3053P3z45e7n7vBw90OlqsoWVXZ3aF7aux/u/vO1HV679u0uuzsP+7sf7h763fmlPYyn7/2r/Hl82d9ld7t9czq1p7sf7u7+nnH4/q0dx+7w9O7HsRnG9mEBrfvFR/eLm7D/2Lwc9+273+679jC+++d3/9Pe/9sfrmiDvvtI333854/43ULL2d2xGdrDuDCQ6zv1p7bZjV/QKfzum3WqPZ26/vDu13/8/UJX3NuPv/7j72+ivxvjj+14Pi6gdUPB1zfh/Y92fOuHn3FoDO7LT74A/4/P/du73zbwuwViPPdvH/HlTTh/f2qa3bs/NPfv/tzu236pzxPM+OuPf2juP9Kv1+d50t+1KSZm+K/jux/b4bUd3j32g+tMOzRj1x9u7JPD+PG/jh8J48fHfvgYYby23/mMPPxAJg0cnt71h3f/3o7Nu/93bk/jO3XjECJcH/vDR8D1EXF9VP+Izqc/fffnoT/f79vTc99DH27sf/rgY4LuH0X/l3YcPi/0Nby7aW1s02B7WBfsciJ/+vPD//4p4HvpH877FgQPPl/vYyobfxybsQ2o2sP55YJo+pN1kvsuXRoS9sIch27smn33t4km3GjoV130zWqj0dfLPfhtfzi0u2gStjqwm37yjdq/Yfy7yRdf2/q/dKfd7QR4SL76dr24gQwP8Udf24ffDUM/XN146379Ra1KY9Ol9i/tvn1qxon67Q5jOzw2u3bWjfDbm9ddIXVoGLo1tM1Laode2+6v+sMJEZwCgquIcRnpZsf64/HrOuYRfKuOoVWMs/nb5+bw1P6mfeoOX9LBt/b+aX8CRDtEdO8Q/aM6+rvDF9Ex7WZ7+KbTjBbUv7enU/PU/qndtd3rF043InohRMMF0Rd3dGGB/ucRjLtrlqf75VctThrRrx8ehvZ0a5u/oo+b8PFVZPDjW+3QH/th/LLeHOnLb9GV86n9sd2dh9Zr7v6aJRj16XxqT4hiN0XxLTr3tL+1L0+bTHJl08d2+N3n9n+6h/H51j4c26H93L65T79dZ/5P2z0938wx1Jtn/+236M7QPrZDe9i1Px6bXXtrh8LXJ/f1t+GU33SHh6nVdTXD3IcPv0VHdv1Du7u1E/6jb9GBh/a127X/OjQv7Z+mDseVXaHPH+HzgT7/Fp16aT6RGdIdnn7TjYD5/94fb5bFL82nk0dzT2h+JjTfopPjzC+9slvTD79FR9pDc79v/9if2h9f+n58/gK2JhTH/tSeJii+iSzqT+0fh/ahQ0H/r81unBj618qk/tQeA45Hj+MLu7dgX1zRoa+zKMbrlpZT2uMNK2k6sEJXE8GCyvXqRi+//6pmL37h1S1Hn3xV46f28PDnodlBGBbt7z/3ZOZeT/r28DA6DDgNY3/yGL6qa0N7eLihH+HnX02PyM6/iQ6RYf8FXbETLTe0zdimi+3xfMAFfelD9LsrV9xP2V13eGg/3f3wy91rO2ALP9zJXOX1XXb32LX7B8gwUVeyu13/AiG+uyxE++5+cj/77xYEC/yYfv19cZd9KDJd5Frrn37KPviP8QU+8DguT/BDcZd9EJm0uSjKTGZS5VLICIOYYRARBnmXfVCZLPLamExnwuZVXWYmEyIvVRmhkjNUMkKlFlHZTOpcV3Gv1AyVilDpu+xDmdm8UjKrMlXkphARAj1DoCME5i77oDMp8sKIrHYUijCYGQYTYbB32Qf/YSaKpU7YGQoboShhdsL0CLmEo5zhKCMcFeAIVBU6QMZBEbZqhq2KsNWAzWaizI3RmSgzoXJl6kxUWZ2bymaiznQudJnJwv0qwl/P8NcxPwJ7SuEbkNI3IFV4pjNh8kKaTBoHxQw753mRML0gmgDCQgBNPGQ8JK2DYtQLiyFeDQI4WpYwUWUVfzvnfhGzvwAWljZTOrci5jUx53cRM7wA9pUVLrsqaXnO6yJmdgGcK+slAYKvykzkooa5LfLSlpmssiKvtc1Ukclc6kyJrMgrAGSm8lqqTKlM5MbAaACwJlMGsBRVpmymcqlNpkoPVB6oM5nrMtMOrxbuhZbQQFFlGvDWhcq0RnQ208b91mYyL8sq09Bda6pMVw5b7b4xRVbkRtSZEfSNkZnIpdKZAbTC1hkwGGIxhj42Hq0pYdAg26pM5GUpM+D7XFud0DYWAwKWNPSxzGuhYtpaT9uqJBLWRhKdVAlDFjAWmReGOmqkhV6I3MoKeiFzUYvMFpnOC6EyK2CYUmdW+icK+55Z7d8Y98ASWgszUBidWcCmjMoszHFlZFYW0JBQWSkcttKjLVWmcq1MVmr/xLjOlJaeJCSJxZoACaWXGb30JNES2AymGNlL5IVyzCRrRUQCQOMMlsAXMpdS0uzXypFMa0EkK0tBZKiEhUGKXBU19buwdVYCIcqqzsrKA7V7VRUOc0XMqLJKut9UwDWlyCrtOloZ/xPrgRInLiVILKMFCFmUmbk1dUwQeAWDrUyVAQdWtqKFWAvrKKQc8ziyKOnJUjsilJqIUAtJfFMBAwERSl27KZeKhiyEdeNRWQXEqEqTVSDVtZJZXbiPauFe1UAMJcqshhnQxmS19oDxr6z/MbCzLU1CjVjHCFAJulqSobXnDhDPmcxrXU9pAC8MiR4glycC8UapSCDAN/AZPonpU8JymlNDuGHQdOv6Qh8zG3uFYq/K6toNWRSFeycKEZ5JghI6xLpQgtaCnqq8FDFX4CvosRTWTX5paLzKSBieykWlaFSydNJCltKJgtqAKFB5qVEGaBgMDbxSsPJlLiqZCRCzudXQYVjpAiRMAUumKgGyAYLVY/BtRVBi5sX6V4LOhIVhc2njcQk3LlVYN6GiolUfcbUgNiJ5hoCftQrYQunJTIiizkwuStCNhZ8AEaZCeP4Vwk+iEDq8NQGyASoDVAWo9t/K0IYUS5MsE/MZbAAh5ZIwxHcSJTVa1LmsLcmAus7ALswVWDEwdKsq0solrFlYpJVXuaBla4cE1agtvfYUllQirGxcCQXIBYXspEhKFrAAgKZCCVjZIO8kjtdBUvp+wAShWBSS9ILOhESFU8DvbIDKAFUBqj2EJgVBwg1DKBkg5UYinCqwmVAmPLPhd6XvlQptqNCGDm1o4b/VElUhmMjKf6t1eBva0KQ6y2RWY1NOKrQw7ZLWx3cGxY7A2axhKaBpBdSqcH6NW9VgDsJvDSxAnKG6JBuogjkEg0SSBQTMiLrP2MrNai1oVoUtSRKAgkdJoAtL8l9VBuwG+FyCJJC5UhasAWiAGKDWoALh/5V2fUMtUFcVyD2R11qTYIcuwFx5yHqIdDLwHIh11KpC1/6tKfwzIwIEWKwsM2FUgHSAgCS2tJkwoQ1T0rfJvMRWstToUdWZNLmVsaWL71A6aonzYhW4NSI3ys2PtX5aSgFCqchrK2h+tJC0pgpTO+obEFOEDa1LWA9kolWKKGyNVzdlRaaINDWZDkbTqtNSkBoFb6uwDh+IKrDzlV+IACErAxuj6eQgFGlF7ViZnpkA2QCVHguozRwXkqlds2hg4iPklArWtZUeMdqY9EwHKDRhaTDJpMTehzToVJaLItDgYpE5UAiNQO3WSum8jto6wgvt7Huy62UuwW5Gux6NHZkXlSTC11IS4W1tiKvRI1boFcAckdmNDGaQMjBQeFYVMDygkQVn0db+ixKYWMIXJWDRICpLGd4q16pAkxm/LQ0qaYCsx4xmKGGpAlT7t2iJVkVKzdjfkGBrC7BZFqhpHTUBHZIT1iP6blo4MkpHReWpCOSsUQSVpG9BNxMZjTMey6J2ZCy1IyP8iMgI+pLIWEkimcqlrB2hDAgzJBR+QYSC9mjYNoOhOECibZGBYaNypUUmKh0gEyBwQSyoe7IFAC0asvS29lBdEJRQM3ZVJIZg6mVq4jt0BlVNRkpRld4x9XQE6wOZUJfOhSwrkgNGluQegAWrcH3ayvMeEA25S4AhUwMNQD2IWqIxA4EWFZ6hwQ0Rkpp4CtYI8hSowroMz4BvDXBhXTtIknUqIUAk3O8kWqcEqQBp1xqsKv+M1C34LWV4VgWo9m/R7MJnwrWR0Dz2hmSFMSC54ADgKyRi4eSABcGGHgCsHdTr4EyjxQhryDhAowqrvAXktaeR3gJyNqQBQVOg3guGR4V2h8oL4E5CDUNHzaTgJ7iAAcJe4++kUAHS4a3JTG4reIZWBL5Fa0gCcWB6CrBXUKhXVSZl4X8mhf8ZGlwEKf+B1P4DEz6w/lEZHlX+ESoNsMjQVCRI+F76QUsytuBL5fGrgF95/GRq4YceP9mb8CtPdkmGFj6SvkntCJhwQ+wNSnCKYIBLphSGI1GD6orYQCvnDYPbgp5gKd1cW68gSk1TrLWLXNhKk3NngfIFRX68RyBBpkHH0cOHnoBVj6QGB0pkUmuYVgigQ6Qqlwih4sORg3+FzrquwtvaQ6bwb9H4wS+MJCihS+wdKoyUykUTk96hieHkO7RAfrGwtAbAHCO6SLcGbOHCS8ZihIjCKBVOUjEJcpDjBzoMm3cQjFJh71VmUZJJA7pOQazJGBcxgZSmh8oAVQFyQZcktB+7j0rgyKvFkYvAEar28+6m3VZk6ipNXjA4ACWog0K7oAWsHomqwtIqUbmBRYD+dKGog0AWiOuTWw+hYpGZ3IAMsDhx+AxWfwkr36LnXJaZtMAcQCEyivBnZYCqANXuZ2hS4KNSBMi1kBAo9ikV+o0gDOdWLr4ztCxQzKBRV6Et4OJrF+k4XTE4NSCjcOnUpYsSwVfoasNiolgHLhh0A3ARoWZTwFhleHZZTgKlGQgfcqGBUAr1BRAPPFVaJ2Du0epAu6kA4wGDjbh20G6it5XrgSxrD1WFf1uJpZWlkjQUxuFhAAv8pTx/QRz8EvbG8dRO8UAUBAlYmUnI2UeYyfCECCPKRogRahdOR79KW00UVSCIkCog/TCyjtY2NgZWgqbYgHXMiDKy8o8qMBQUELHyv0drSSMdjAtqy8r6l2V4VPlHtX9UFwESAZLuZ7XHX+vwLuCvbYBK/zOPv3bRflV49CBnPETok5mK3TmFSQ+1qBvwHc0UyFOcKaHcTNkqstdAPpoFe42mqrRuqoRzgWtZualSJU2VrNxMgY0FHVI5hH9IXaJ1RXMFyh7nCn9HkwXPaIrgdzRHGPw23uKiOcK3ZXhWhWe1f0azhJAIkPS/q0MbdbDl6tAGzRNCZfhdaINmCtI6hcfsJgifqcyg66oK7Sw95a3EZP5iz09h3mk5TILv0FxEXwVmz0us0iUEhHKqjNJBRV7W3uquSJOBECEPpXQq31RObsH7AgWN8tEkMOOdaAKjCeMXplBO5KC4IqFS+4VTQgoMpBDYPoqsYKABWcEIgeqAoIVCK5ggPyyF5iJByqFDa1Hgz4xrX2HskX4WmhCuiYTAsTOowJtRol5yX5T1mqB08Y2qtE5uhVxZ7YIXCuxjtPhqFykGM5tIWruwHESMiY6AEFkV4q/kYlTSWbMAoJ2uNJkIKkfFgIsBn5W1dxcqhxZWBQIYFsbfOyIiVHtIFhllhhXGXkv4NVg3eQnOjVThrXZ9U2jE0beWvkjoGbuDCjwVXMsLmpVcReA64xNUQi7YIMoZDpg5AasCVyYsPYxBksUBfRWRxaHQLQObgvoPKJT0BoSSVYBqZ3EoVZApoZQ3IJQKSNCloWfa/8yERzZAoQUVWvCR1IRWsRun0I1Ty2Za5V1nMCtwNCCojANQWhsQLRTi1EBOAnDBg/whuirHqNoa8q6rWhOFi8LladBTwYBZBa458iW8xCVLFpyimETpqA19Kiv3Ack+cN1oaUKT6O3QMxXeYlC5BraEnGNeAMvhhFowJDVaMjpTGIijn5kA2QCV4YMqPKs9WUwRIOHfYpCGnqnwLLRhAklNaMOUAarC29o/s2EYNrSB0b5apFZn7KgpdNSACgvzXc/MJlO5RQK1AdoHj4K5hHModVYqZxBcTCKNgtSSU+kMGkpmohypgwXk7AwlvR2jnFWK06bzQms3RVixoL0FRHQmSAbIBKjyEFr5WLdgdYBMgEKdgw2FDhg6NKAgKQ6GVRCFs9RUKcIzGaDQRunaSGYhdgt1wRWg+Dc6hzUN8QkP4fIvE1NLx06XBhdDgSKcaxJNDhlQxftchRd4laVgV2kqp0BKr0BKr4pt7VwIcBxQEYKsljhtCJFbCAYHVTTUXnDDEi4vkEXGsJlCf4CgCnxzIDb0DZMWqioCJPzvMLhIkAqQDpAJUGijCm1U6NdDGxhdxL7UZFoknoaOHTWNxXPL0UV8RyGuytuvtfM0QBMgmSuX9C20IzwkHp1AFE4gVtYlGSCRo12slwKtFvI3GIKsUfyi8ENHFkcOfn6NcWiwbMTFx/F2e1hhlX+CJigCwmFQtcwsZhgV6jnojwJBjj6kqk2AbHhbhmdVeFY7SJM5gZAIEJSmIEsXKjzTATIBcm0ksxL7f1phkLGGXE48JcpFHyt1MZjQdzYUfCTfmeaqKGmOvJhD99//hKKQhU9dVCVNGtAR06+AGGcDcrUYrIBMCgUohYWoBSTEKxe0Kl2ZCZRlFO5BXbqmaqzVKr2NC3iEooISF5YHRJBIo99DWJ5eQm4VEkAU59UQ68GYLkLC0q+cUkQxG4IHBboQSmSaDGFw44vaPxNFgESAZIBUgHSAMOpYAeSa1RAfJ6DyrYo6UPdCZxEg6T6QKjwKHZahAQqbJjJWJ8Wl4MVAZtXkKqlL1doXZkibxasX0n5h9dZuLU1Xr8iLQk1Wr3DrEnUQdA4tcQPZLL88jXF4jHPjw5I3lVvLlOYtpMvmGufMUpUPCQNkGvgJ5vcrSTFQapyy8AhZpzzBXHaPMHpaF2S006fEBpiWxsgzrjtZBSisYxXWsQrrWIV1rMI6xvgRBFHJmU7VVOxRavB/YMHUuUoWr/GpA1grlUsgS/Le9dXpdqMm6XYc81KyHQMIkH40LqMe0u+oDLUvRkPAuqT9NOsOmZzSZ7nRpAHZ5fPwymXuKXukdIbyF3xLikYon4OFpl2axidkcA3RGy3KAFV+TGj5g0rWqgxQ5dBrtJTxGZq7+EyLpcy7jj1RjWWQerHORdtpVueSrvBASO+g1w+unXQpB5ojkKO4NOrS54IFTQ48ueR3hMvcWJ/CCYkepGglJRU0QHICCYk5INQ3VEgUEj64PCAvVFgPlOFd5SHt0jiUStcgYq1/VPnRUfYOIAqD4bCEy6QgwfBLrX2yR2vtEkBamwDZAJUBqgJUe8Rk2cIzIwIkA6QCpH1rJrRhbMBSBii0YSgMlcQjdOw/a6z8VMtWZOm4AKQFik7s46QeRmDYl0I+pvIiE8teCrQXSIn6ctmwEsFoo9SrVrDc6MeoTTHDgfJQClffp5xkNKa8LDC0a4AHqawMpCDOLgQfSIsWwIKuHKXGwgeRW0V5fvc7LIOEwBMtSYwQ4qJECJNyNUwpFJ7kMAUQHyIsZH2hW45qEXSJ8IhJ8yGEmo8g4boCmk9hlodUH73VATKuCVJ9OPOYyMNnxg9D27DeLa53ALCKEUQz9hhcRU31hQiZANkAlQGqAlR7iEqOoTG0QeiZDFBoowxtlKGN0nU+Yb84JKHBYwVtV+VSJ95R5VNm4Bs6beAsOOtrI6zPFoOexolTFbGPLl01MD7BWKRy8tlQ2SyUlJFVVrr4vkFpgcaQoEJEmUP9jbPKwGQzFO4Sjotg4BURpHYFAPgBms5oJZGpBgqDTDVwlbCa0YDYRDvKKscwUIoiCw8ID6Cv73gFH2gPWNcyqWQsFcLMd51pdLgqnAiQCNAa9oIgLFwhSPgvUQDSMxWggBeD38Kk7lMccdBUKGyX9i3o2kkTLEvFQHvpC8p9FReYr7Q7QVCAqTIuwGSl8uUwNVXVYREtzr2TKRRwEjl4Fy5Z6mUK1PXhFNrSmerWiRJQVa5y0QmQEurZaKmAUkFKYeUUGFC5rQUqCPeWLHKIPBcUCwjGufX6HSBa+xYyvdrjIx2AEEpvhGx4S+VLkBJGtxZ8ZXTuIPys0bpACPmYIBEgX22mkSHpmQ6QCZANUBmg0Ebt2zBFESDXRsIEccDDQJzCFIuGBb7zO0eMy+7gPIDUqyhRgKpGGpFwh4XYgMKZcqWmmmxDZInCVZZLNNa9Hwe1aMhz2gkKCB5SPS3UA5XOxETbHOwYg1qurMk0ccXZoMuRoUrjPffCgKkCmrEkzoKPq8IxqpM7JHYg4eD2HKDWIvy1dnW/VC+I1UBU04FbuIwjERXQgiJSZI7VWFboXqKxhX6USxJUWEzon1n/zEj/rdEBMkRsSAbQz8DK95D/FDIEHiKaY5mFf2T9o9I1ShUt+MgPiypagDtpjWOglypOqKLFfUmJCkklLf6ZdY6WDMOHIDHIcoVBYtcRXfhngTgQoHNf6MpDNvyuVP5Z7YkoqXa2oIwbYYEMEv0OMkgOEtota1L44GKhF2wxG+R/hBob/W16AtrAQ9Y1qYVvSAs/LFIFkkwH90iqAGkPlQFJ5emqKx2g0FjtJx+WJHQJVrJXnRclagJkA1QGCK1f2Izlasig4r/wb4UIkAxQaEOENkRoQ4Q2RGgDCUZQ7SEZ2pChDRnakKENGdqQoQ3p2ohllaEgKu78fW3h/Kvf0w7gDx/u7rJf7j66DcE67ED+5e+Xnb8//HInDD2S0v1V7q92f/176/6W7m/l/tb0VxXur3B/HT7l8CmHTzl8yuFTDp9y+JTDpx0e7fBoh0c7PNrh0Q6Pdni0w6MdHoP9+vtkjzP8F2hY5MWUQhAyXCYRdWEZhYmIXMibUYh4lmoOgZ0OaxFROhxze1+KGEXJDUfxvSjgX4xF3I5FxihMyaGoeBRlXuRFHtEXlPAyIsn3xcR9sdwUrQzHpEQBuc50JFkHPP/CnscJxlpx/UpW4ArGspYRTsXxUOjlNs4aq2FyMHxiEsgt5HKTBFFvIbqxTgFaRIuIUp7lZAE/yXKB89kpWcECLsh0CjgcYQrUFpVUvAQCvjvhP7lqRemY2NzsEUssIqhnLMCt6zC4Ymtw8eqEGOatUq+MuciyXMRSpo4pw/IOS5k6JoviREwgS7VFlibq0pbyF16pi0T5J8qeVepe6abK+mpl3Nz353HaZ9imxxCBnclmt4PzUu73bczxHE8ovjvuLL4JZ7E6aFs1A7bXqE8VN8PCU8ysI9t/jtBx2sRPrFjvWzdG6FjpJ1gB2uzGc7OPFRJL9oh7FrE9PHQwATFGxWoMb0yS0chgpFMopzqYGyZvDDSPj+1ujNBAmp5hDL43jyMcaTTh9ooztbzdHbiMlULNU9NFLCsu53ynSIsthdjs9/FqZPuX+g2RqFhEHXdSsAouFUbe06g2JxuOh50sXa7vQfzZWAz6RShci55coUfcmL0PZJKe3urDbIvLQ7RgBWuUcu7byswfu0i0XMwFv1wv6Je/P+67XZPKT9ght8bhG6i+i9Yc5EnWxruBLMbF26E8CwOuNmIyqB+51fIgNIk0YV1Int2Px6E/Dh0elTfV+qwA5mXIEKFgXWK/QvxK8F6/3vQHmmFoIt7VvN/Ni+FYkLOkX7ZTljF+PuymSCF+s4x0ZWyxBcM7m4rHMLYvRzrNfqKPOW4ngbWI6DV2fPWmG7GCqun2TWJUQUkFM2u8Vn9ruohCSrAcarb46L45PLiDgicIWct5Ocy0jPjUJZzA0W6ldxGxSs6U9YonKJg0qHZlEC0oFLtJtraNxLJlWSvtGsuz9+1jH4sN2P1yq3h2J89Plw/rAKx0ZUwMKyjN3eAInhO6QzNEsgq2FzN9YpdPOCR52inWx2R1oTu0N3ZVb48vODSxzmGZYAXPvt/9HGtAyUfpeHbs4wWs2RgOr0Xvx8N3/5T3h92+2/0cu2zr0dRFZLEXylJYrIavGcxjf+hiy5Q3GjbwHFNvBjKdN6OKjUg2dhJkgBc/m+7DrtnvzvtmTDiE7WMq4PjZ9phTM06xSzMWlgzOfazcWJPE9481JADXfRMzIlTfrPk4a3gSW5VV42sE28fTwErnIPM3ckB8cAIaS4QdFK59qRe6S3xFNugYONRnj/QVqA99bK/xwn1llg6viTFasjbNyhQBlvypHXf9YWw/je+/wxtP4mA8G+RZQ3xqEwa6uHNeY627c7tm3EUiGqpA1nTgxM317u1iam6xMbzdJe4xa2gGLzzlTtYABfSHNg5s1Cx3shp999zG65s19ENyNQllrHWx70+JG8dGvXghtG+bw2wdsmEQfj4Azz7REmzUjNXyiCbWVno9mrqMBe6sjLBcmNkbyvTHy6dp73iS7/vTjFbsjK7Qqj8/fBriJTvJtcyjJ7Pw+yzuxMSZZjn3NL701blxd63G1IGcSA41Fx2zdFRwVfhlBG3EOlyy9sEKlpeX82Eh4AS7OxgWY/2HXQ8XvcZWNhShMHg2szAeX6R+4XSpWyNhgGjwZ8ZP/V3WkVgh2IGC67HHxjpb/LrpD4/d03mYUV7yIV3PgJtpPY88zuSI9XzhKqJoDtiaBD7DES5mmBhSEwGkF2ROGlf2VkqIljHrdZUs/qLBCYeWEzFTLXSDCXN7sXJ92Hl62eKkfTtpf0E0CN5cnFx7NZkcNjnGyEmWnrywDg0nxhtrY/EmIKGKjRbNBkBWZM/h1O/bHG90fP+du9hxmsVgU9vpPK4thmkbj02XuAZQzss0cg1Np8jDdUMxYTatuc0Ism+nOzz2779bULtQW7reyNXIF/lTs2IoZcBr25ncY5pmndkqrjVBNUEdLi2ZmKusdbM9uYR0aJuH2Co0rFW4WZ4Soz7NtRybv/eCbcWcjnEvs+R6/ucWltz3T++/OzbDKYl78InYNTV9QTk0b7EeZLX1lfjC3ZTTTrIp3k2kb81wWBQnbCWBXsWZVIKwvOUniCtTScPkfnleo+jGxNlgHcWQC1NJK2tiGLz8WPOwsdxrZNbYHc5xCF6wsymu6d7Q7/ftEPtslpVFa5MJqJJwMxshWRE//fDQHdIAOBuKUCsmR2xTahbHmpswDIkpqNmg2gpxjufYmGcd/TUU3z+laFhO4tHgZVOxiGGDwd6G3xbsiDUOPbIxCF6dIZZTrMBuz8g5NAvaZSL3Lt/fjvO9+9sfZ2YlbJVYNxg22khUlmEj9Vt4kqGzLvIanm7sdnFhluD1EW82n4chkbCwBXG9iiWEdbct3fNp7F9iP5612nixg1gWTCjNV9ezyB6aMarKFGyB3CyQenUA9aF9bM77WLOwplkIH7Frz6GLfSQ+B8HOxkP72B2Sehk2CcGKbsIS6xI2s55ua4mk1jJ2un87DpffztgP4R7vyIbZMjK3JcFDe9z3n+FR4rOyMaCVPg6p+WfYNbzWo9M49LEjwIq6NYqNTRfbCIKtvllF0w4v3SEpRWDtoM1Y/EP72u7740sipiS7SUdG8QgGZbdLwvu315vPbhqedG6yri4YluK0m8HLh+6lPZxSXWbYLNkaplNaPwTnOK3Vua2lgB26WMWyaYlVPAtBPT2NdAcv+yrP+4IwcYMuDHPJkiwF6JJ60zguuN5iGpObph7qrSY3UCexYrasQm5mXS9Ik3QQp1NWUY3dIZm79UTcIhr3wF24ivdRH8b33y1kdit2pfIs5rE/taNDff+5e5hGfOJwD+tF8HToY4kHZ6zcjCNxHvlqwSgksoxqSAMsbFB1lrlllXObP8UimDWdWH5umySPzaZhZikslm5tNz7H9WYVG6sqtyjn+CPW7WxJFcty7f6UTOZm+GQzvkV3okfymy1z4OuUCUtiBbKhyA00yQXt055NdrgFLFdi3vVQq/f9Q0tA7FezdjTrVwPCM9wvnWQA+RKKlb6d0mwYGxvm6+3aw9gNyeYhzVbV8KqzPYxxZaRhkaxwVZrimHhoF0osraF0H0ayXe26ApRVhoeu5QuOn2CDYDLd1MFLotMJ5iH2oTVbhMkHkNrT2Nzvu1Mk10rWsdrGE7NpzWpT1jJtXxMJZlh/YIX4r6mPA/f93Vr31r62MYtqfmsFP55PDWTw4xFxE7Uyok/HmVEo+WI2fqI+xxKALU24XoU9NnDTe2yaswYES+zHJtE6kq27Dg6It/P933JrUT42p7S6m8308TL5sRvat2Yf+5uSNbx4WfrYDXGiQrMxfH4JP+77hPqs57u9J+Kx3+/7t9QTYHPRvD1IiJKo/c2m9WM8MjHxgtY8kmRrEVuMmhZDfenBI75klNukt82Xx5hSrPXMe/Toy8dswC7tdHMhEyRc6e8QR0YFW3QfCiyShNr2ZtTHc1JSzeeceaFy3ifbnFnVttKPw0L+fj3lcxMtHf5mn+yh1mw8jfdyntpYd7I7E/n+PLVjGgq4EP5uwabiM2VP8XbuSaj14n0syXXPK5vFTE/7HDbHIO/fnx8f2+H90z6f/Df2Iq/b4rrc0NImnKk8UgtxHl5nPg3Nrp1xp2adUl7QPkubHKvBHrlzdV3lc7N/jJfe7fr8Od1CzeZsedUGOPaJ8cy67ZGwXcYWCVnWDJ/tWxNb3PHcxGczCDaAEtfhLuJqm4Rut5sEz2339Bzvk2RnkD9C4blN9uSxu7P4krTn7ineXslbSZvbnwBXYryxG/yuwNYP3d+SWli2QGOFzfvTeEg0r2RL0/nYUhctOJZ/Zkmp1KTxJsmi9b5yfsDVBZwdeDNplEmwlYW8dxXv1WT3dYWI9pZxlp76dlUl+3LHdvtzKus1Ww3DK2MoGRteZjlzxaY/eX3RHbrU3WfrrflCCIel+1uSXJge6LSgxgLTRSTlG0iq39mQ+jaeuJusX3N7FbRvIM4hsB7KWk+P56SMh2UUVl53h7F9mlfBG7aCZm1gI6TcYn2k2IHxNgAgGh6bXVLqtF63yiDqYx+XncaVJfnaJ3uFBS9krzp/crGZYyzG2fDKtpSMM55s4d+tx8eo6RCuOC7my8X8qWmi7TuTHRl3vndzZ2ARVRKJY0NOvIH1l262K79iQ85sAPwvp2SXCb8RLaIThyvHOt33LsZ7GtO0leT3rbIs+HO37+8Tiil7+1mC++Y+Xi1fOnv7ZnhKQ2a377HaN59jJJpNhfJSYN89trvPu7QM4EpTia/S2PdJNZrkN19sLhxEBlZhHBxkq4D4Gez7SBwJtoh9Vja8aeLs+7dkTllpum1JvzSH5mlmEmp2CxevKhDTzPBizYcVPOmOY7Zyb/skuZfmU+xJsRt8rkHVvZxfYsKz7voVhP8U9iO4Uzl+vo8jiVJPYy4LMdPrD4t9SVArvmqM7/FCOqxe3zSwJoQdunlZ5O2bJVghLtgDNtaQxR2aGPxLJ6YyKJ772PzlE92bMcaX/iE592Wd6xgc3ePi8WesKuYVOiFLKhxZhuLP/HnpD12SfNLsQYO8BnhJSvoVi+P601pe+pnjx9fR8lQ/78cuyR9Kdslcf1Czx5tU5nGINxF1iT7hz5JhAwIv50RpskH29cNkF3F/5oLCbBUhz7yH5rV7asZ+yD8N+dD+9dyeRl/C/1338tIOqQMGR/TfSthD2z4kLg9bMRNntDhsyaplqcsPvB3f+iE6uIJNWIbjjdgF4rDNDk64yMp5LGIZT7y1jc1FBa9pMzxwiDxW9hB7PtJygMBPZFOy5f4hW8kyLyFLDtHg69eurslKTq9hQ+thKouYfiHUdrN3eehHRpfwSX9WbhySRKFgXQu5WVhzOL/cxwLCsJVW/PD6+7+kZZmsyN4mVn8/NuneB8ludIkTrYv4drtzvC+PlSs8g/ePsb7cCnD4QEZ67sjiqV3XnAKauPJy1cK6sCo/oEMS6mIdSz4Y2Ccn3Si2lpMPUvSHUzu8toMzSJf22YpJWdXFCr3KMoWdseAxnMZmSA8VmQQJLxmdJcGyuc/k0kp/PCatTHOmQeTN6hfnGY611vCoq9PYjC0d/zQ/CdJO6wKXzvZZmdMUe3tIDmOZzsZC+JzXp/2x+WtSN82qiZU5PbYUO04UDruoWSMhYErsctZDSyKrajPUAlsbX9JT3q8rQePQpeMW/H0XyU4uPt2ysANTsuU08ZE0i+gijcKK7tnpKsmx5XFd6OqSGOCgq3nmibX5trfM9FCn/tbFNXqaLcznkx39azu8drHNNrH5rnOPjw2Y8+kR+JbdJsByUECUTPb6zTIMpqSAkc2582v52MRncip2iwGvQY5tcoTYtUdm8qb6sR3az+28rEBOKtovy3TJU9uU4tTE7Dxmqaanhi1ppasvsTm2A+ZjE0UvWZG7LX+O3bHdp/sF2fwWr2SO3ac2KSdlgypX9KrvkvS8YSX4NtX6IQlls9nrFRzJgYRsbDiUB3pNwq+U/tQeh/ahw+K5pRrkiVrWC9VmvKg79qfZ8WaKXUXbotPj+54Ry5rN76wOv5ufYMsnVXhpgYiScAV72swan6QpBVYa89EuN6HJDh+2bm9lVIEzYp64/Xa949C9NOlOldsPPQA0Mzprvk6eF8VDv0sObmYj1PzWzOPQP5xn9JH8zXQrlO6fZidJs8eZ8v4XXEDRDkkFLHuaI29hHod+7Hd9XDrML941Cr12SdScFaPb0WCHLnbi+bMBIgt1VTwT3uQQTNaiTirX43KwJfx/Pc/qkhV7UA0//L+ek600ij3QmrfKMdAaZ7fWvX5e0MwOwmeLlNZxxHRn2X4FSdskJ0JOzJ3ZiBgMD2ngQbPX4vEzPTuRjS3VnFWt+AhmUqWyHcQZ2uaUHiPKGs0rPccYSRxHZI0wVtIvh1rWbfi1JPvQ0s/TVc/ficoKWUC1cCqA4D2MzYPRAeVr4j/xh6Ou9Wx28pPgT35awfNwTo614E/NXcHy2A5tYuArtgxnez9UQHg6JqVwchouU0shJS/Dr2lkaE9JnTSriVf47fAQT+gkt3Xh06XUg3cp0mgss5FpO+FMfcGc2/uxi8ujNav2bt88Q+2kh1ttlQSl42bjWOn4r+xPbAyxO/DXRMdM1xmWamu9+eu5SzLb/JbvTRvGoYulGX/MzuroAM9j24znIUlBs7bL5jBjXcwe+s8XqQ3tqd+fZ0YxGzTb9sQB43nYxV1jVcqs6tPj37xxb2hPY3qDEpt74OPRQzueh9g1ZYOVvGtKWJKzoVgZvDatgCdZS7cXVw/nQyqB2KBafNnpPF117fH4/KBOzWOSZmJdQJarTrMd5ZP6ouk1Bpu24ynduMSetsrHrk67JikjUWzqgXezT2DhxG4/u0GLl1CEJXHXFS+AWUPi1O7SszL4cMYKlvQa0fVaxlvO6QPcsxObWF5iZTGgSc5oZbXzZj0eIptmL+PeTQwmPS9F5BCOQ7ODIhHMwI094Y+X0PS4loV06MpKgr0S6WXPbK0Or9HgNI5YTn0NEYelPTp86mYNU2rhT870WioGTU5BCdsJrk48UZP5vKKaj0BvdH/p2uWp3y9Xilq3q7WpjVmMW05t+4XAwvWXyy8cFztJRUd29oaT/aU3C3O7YBL35NvdLOyGnC+dTsZ6htefe+/Rc+efsV4ja/Z4jGS8L/gr69fA3KT+XVMzSfmePSjW8BcariydSzOL8nNhkOvl+l80yDEJDLBzs30Mv8O5fDTqtPpHLh0ssGJ2cCcuy+lFZ3LBed6+ptwhT06Z3NrEuEnXGVknNXOXs1S2FOsFVb4YW2L9xHiu1naDLjWSmizs8TtXomUPzGQVzqYcSBGnXWb38qxhnp+pYddUMYPkHO0Imky7m5PrCmYR0cKtJ4YtuF7hyef+vI8jAqzjuXyeDoM1qg+Z7AFauHRwbX3j+X7pqQG3n67DnBO4nhNeRJTGERUrELYDh6dju4NtFMmNGaylyA9vbMBtiv0vdr/ZCpmgrDDytNlN6ukNUsxBeyuDn5cwTotAOS7ZQJkorOlBvmvX720ZaalRtnjhZEoFtotJtm9LUPNiaWzGc3LtFJuS5NfYmGxUZMsZ+BTOUqUouzPu+tJQQBuPj01qrfUNalljhXvddsL1ri3cJMSe8b7FaKzV742UxW1L3H1n2/1Ozl6+/Yqp03kHZQyzI5AEfzS67+qiQ7LcxhE8u6SahE2N8oHh0+fT2MbbNtmzuXilRsc3J6eeyunNez7tsFTAzk4IoE13Cl9w3rbZm3BhEXCylX16BNh1JZoTXOm2+1stn7F9OfZDEtZjC+TiIO4yPjjlf3b8B3vMMc/GcAkTHvXbDu//KSfoPbMtVbI7h/l60PE5vr9asTvTeaU8PjdxNSC7GTrkv/zf6DiJZdxJYd8Xyi82mrEVxdjYT5IejLF5VONX33s7PrdxBoU9LWFb2Y/PbXISNyu8ePU8PseHkbBpdXZOuCxpcnfwFu3jQskvySqPz0N/jg8UE+zuARllzdb0RHw+DVsD9w+PxG0cNzqj31ef5eVDQ3EgaP2gq2uOlxiH5rBQnsqeajq7xTrhvO1bLMAoiQ+8Y4X5iqAc+jPcCvLc9zP9N72g1s2DuC7HNg7nJJHEup+L5xkv44yNCPY2r9vOM2eaSmjBHoLNHw+e3DfEF7ck1xuzZutmzfX4+ZiU2rKRZ37on4/taTd0x7gQ4+oLo646h2rlTvYvPY54lVj/CJ137iKRzO6c5JXU+eDq0dJbIgS/7YqNsZwPlL6NYwPs6fh8/jYOt02q4xZPn66nZFxG95BWyrHGwQqpEpOA3YHP2krpUcxrTSWli+zCZe5LDTzIetfnU5LNZcNOMX9yuJr5Yc38sZ7s0j+fYt9A8kdwbYoiwIX8uHwZkpyUql54cSnRIDbX4WmmttZvdpsLFR75a7Pvkjupbr8E+rXZn5PC6/VCxzXCvnZN3J/bE2VQOx9Zf+wOERFFUpZxxZsaNVsxwnMw4Eii1eyZLGrTfwBsaW5bsfYdX3j/GqcJ2FQhzzxvTXItEcs720V1b01SsMLGu1nC4JbuaExJARVTJ7sRxr3+4HLsgAzVq4vXObMSh10QiHUJF+uXy02f6629P/W7n+Nz5SVfm8Uq07f2/lPC2SySm5MBb218BgibVuTvmnhLwiSavRmUV2ZvifPPSoBwxnhqI3obLnUFrj7Z9u25i8++0+yRKmvj6NKiqHUOWkQy28vLeg5hY+8CknhiJZtzY83kKJi13EbcTzYHEiz8qy+wfkusCLbz/LJJap/4q8/XUHz/PI7JyYD8EXAsomT9sl5nbAUuY1rbWcEGuTblwKdh6X5ulnmvQOSqgqgo4L45tQtHmbLpj+v7u9xMviDMDXvW+nXN4M6C5tDRweS0RWOyXSNOJa/f2XCNZJ41HU/8++9mtYKK3Ya0nRH/NKAOdJd1vF9kB8UScI1fEe987tkLVuJo2RLOz/05NolYW33TJPrcn+M6T7Z6h81A31bl9VN2Ofvghw8//f3v/x8Mf8VV"; \ No newline at end of file diff --git a/deps/cloudxr/docs/assets/style.css b/deps/cloudxr/docs/assets/style.css new file mode 100644 index 0000000..2ab8b83 --- /dev/null +++ b/deps/cloudxr/docs/assets/style.css @@ -0,0 +1,1611 @@ +@layer typedoc { + :root { + /* Light */ + --light-color-background: #f2f4f8; + --light-color-background-secondary: #eff0f1; + --light-color-warning-text: #222; + --light-color-background-warning: #e6e600; + --light-color-accent: #c5c7c9; + --light-color-active-menu-item: var(--light-color-accent); + --light-color-text: #222; + --light-color-text-aside: #6e6e6e; + + --light-color-icon-background: var(--light-color-background); + --light-color-icon-text: var(--light-color-text); + + --light-color-comment-tag-text: var(--light-color-text); + --light-color-comment-tag: var(--light-color-background); + + --light-color-link: #1f70c2; + --light-color-focus-outline: #3584e4; + + --light-color-ts-keyword: #056bd6; + --light-color-ts-project: #b111c9; + --light-color-ts-module: var(--light-color-ts-project); + --light-color-ts-namespace: var(--light-color-ts-project); + --light-color-ts-enum: #7e6f15; + --light-color-ts-enum-member: var(--light-color-ts-enum); + --light-color-ts-variable: #4760ec; + --light-color-ts-function: #572be7; + --light-color-ts-class: #1f70c2; + --light-color-ts-interface: #108024; + --light-color-ts-constructor: var(--light-color-ts-class); + --light-color-ts-property: #9f5f30; + --light-color-ts-method: #be3989; + --light-color-ts-reference: #ff4d82; + --light-color-ts-call-signature: var(--light-color-ts-method); + --light-color-ts-index-signature: var(--light-color-ts-property); + --light-color-ts-constructor-signature: var( + --light-color-ts-constructor + ); + --light-color-ts-parameter: var(--light-color-ts-variable); + /* type literal not included as links will never be generated to it */ + --light-color-ts-type-parameter: #a55c0e; + --light-color-ts-accessor: #c73c3c; + --light-color-ts-get-signature: var(--light-color-ts-accessor); + --light-color-ts-set-signature: var(--light-color-ts-accessor); + --light-color-ts-type-alias: #d51270; + /* reference not included as links will be colored with the kind that it points to */ + --light-color-document: #000000; + + --light-color-alert-note: #0969d9; + --light-color-alert-tip: #1a7f37; + --light-color-alert-important: #8250df; + --light-color-alert-warning: #9a6700; + --light-color-alert-caution: #cf222e; + + --light-external-icon: url("data:image/svg+xml;utf8,"); + --light-color-scheme: light; + + /* Dark */ + --dark-color-background: #2b2e33; + --dark-color-background-secondary: #1e2024; + --dark-color-background-warning: #bebe00; + --dark-color-warning-text: #222; + --dark-color-accent: #9096a2; + --dark-color-active-menu-item: #5d5d6a; + --dark-color-text: #f5f5f5; + --dark-color-text-aside: #dddddd; + + --dark-color-icon-background: var(--dark-color-background-secondary); + --dark-color-icon-text: var(--dark-color-text); + + --dark-color-comment-tag-text: var(--dark-color-text); + --dark-color-comment-tag: var(--dark-color-background); + + --dark-color-link: #00aff4; + --dark-color-focus-outline: #4c97f2; + + --dark-color-ts-keyword: #3399ff; + --dark-color-ts-project: #e358ff; + --dark-color-ts-module: var(--dark-color-ts-project); + --dark-color-ts-namespace: var(--dark-color-ts-project); + --dark-color-ts-enum: #f4d93e; + --dark-color-ts-enum-member: var(--dark-color-ts-enum); + --dark-color-ts-variable: #798dff; + --dark-color-ts-function: #a280ff; + --dark-color-ts-class: #8ac4ff; + --dark-color-ts-interface: #6cff87; + --dark-color-ts-constructor: var(--dark-color-ts-class); + --dark-color-ts-property: #ff984d; + --dark-color-ts-method: #ff4db8; + --dark-color-ts-reference: #ff4d82; + --dark-color-ts-call-signature: var(--dark-color-ts-method); + --dark-color-ts-index-signature: var(--dark-color-ts-property); + --dark-color-ts-constructor-signature: var(--dark-color-ts-constructor); + --dark-color-ts-parameter: var(--dark-color-ts-variable); + /* type literal not included as links will never be generated to it */ + --dark-color-ts-type-parameter: #e07d13; + --dark-color-ts-accessor: #ff6060; + --dark-color-ts-get-signature: var(--dark-color-ts-accessor); + --dark-color-ts-set-signature: var(--dark-color-ts-accessor); + --dark-color-ts-type-alias: #ff6492; + /* reference not included as links will be colored with the kind that it points to */ + --dark-color-document: #ffffff; + + --dark-color-alert-note: #0969d9; + --dark-color-alert-tip: #1a7f37; + --dark-color-alert-important: #8250df; + --dark-color-alert-warning: #9a6700; + --dark-color-alert-caution: #cf222e; + + --dark-external-icon: url("data:image/svg+xml;utf8,"); + --dark-color-scheme: dark; + } + + @media (prefers-color-scheme: light) { + :root { + --color-background: var(--light-color-background); + --color-background-secondary: var( + --light-color-background-secondary + ); + --color-background-warning: var(--light-color-background-warning); + --color-warning-text: var(--light-color-warning-text); + --color-accent: var(--light-color-accent); + --color-active-menu-item: var(--light-color-active-menu-item); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + + --color-icon-background: var(--light-color-icon-background); + --color-icon-text: var(--light-color-icon-text); + + --color-comment-tag-text: var(--light-color-text); + --color-comment-tag: var(--light-color-background); + + --color-link: var(--light-color-link); + --color-focus-outline: var(--light-color-focus-outline); + + --color-ts-keyword: var(--light-color-ts-keyword); + --color-ts-project: var(--light-color-ts-project); + --color-ts-module: var(--light-color-ts-module); + --color-ts-namespace: var(--light-color-ts-namespace); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-enum-member: var(--light-color-ts-enum-member); + --color-ts-variable: var(--light-color-ts-variable); + --color-ts-function: var(--light-color-ts-function); + --color-ts-class: var(--light-color-ts-class); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-constructor: var(--light-color-ts-constructor); + --color-ts-property: var(--light-color-ts-property); + --color-ts-method: var(--light-color-ts-method); + --color-ts-reference: var(--light-color-ts-reference); + --color-ts-call-signature: var(--light-color-ts-call-signature); + --color-ts-index-signature: var(--light-color-ts-index-signature); + --color-ts-constructor-signature: var( + --light-color-ts-constructor-signature + ); + --color-ts-parameter: var(--light-color-ts-parameter); + --color-ts-type-parameter: var(--light-color-ts-type-parameter); + --color-ts-accessor: var(--light-color-ts-accessor); + --color-ts-get-signature: var(--light-color-ts-get-signature); + --color-ts-set-signature: var(--light-color-ts-set-signature); + --color-ts-type-alias: var(--light-color-ts-type-alias); + --color-document: var(--light-color-document); + + --color-alert-note: var(--light-color-alert-note); + --color-alert-tip: var(--light-color-alert-tip); + --color-alert-important: var(--light-color-alert-important); + --color-alert-warning: var(--light-color-alert-warning); + --color-alert-caution: var(--light-color-alert-caution); + + --external-icon: var(--light-external-icon); + --color-scheme: var(--light-color-scheme); + } + } + + @media (prefers-color-scheme: dark) { + :root { + --color-background: var(--dark-color-background); + --color-background-secondary: var( + --dark-color-background-secondary + ); + --color-background-warning: var(--dark-color-background-warning); + --color-warning-text: var(--dark-color-warning-text); + --color-accent: var(--dark-color-accent); + --color-active-menu-item: var(--dark-color-active-menu-item); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + + --color-icon-background: var(--dark-color-icon-background); + --color-icon-text: var(--dark-color-icon-text); + + --color-comment-tag-text: var(--dark-color-text); + --color-comment-tag: var(--dark-color-background); + + --color-link: var(--dark-color-link); + --color-focus-outline: var(--dark-color-focus-outline); + + --color-ts-keyword: var(--dark-color-ts-keyword); + --color-ts-project: var(--dark-color-ts-project); + --color-ts-module: var(--dark-color-ts-module); + --color-ts-namespace: var(--dark-color-ts-namespace); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-enum-member: var(--dark-color-ts-enum-member); + --color-ts-variable: var(--dark-color-ts-variable); + --color-ts-function: var(--dark-color-ts-function); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-constructor: var(--dark-color-ts-constructor); + --color-ts-property: var(--dark-color-ts-property); + --color-ts-method: var(--dark-color-ts-method); + --color-ts-reference: var(--dark-color-ts-reference); + --color-ts-call-signature: var(--dark-color-ts-call-signature); + --color-ts-index-signature: var(--dark-color-ts-index-signature); + --color-ts-constructor-signature: var( + --dark-color-ts-constructor-signature + ); + --color-ts-parameter: var(--dark-color-ts-parameter); + --color-ts-type-parameter: var(--dark-color-ts-type-parameter); + --color-ts-accessor: var(--dark-color-ts-accessor); + --color-ts-get-signature: var(--dark-color-ts-get-signature); + --color-ts-set-signature: var(--dark-color-ts-set-signature); + --color-ts-type-alias: var(--dark-color-ts-type-alias); + --color-document: var(--dark-color-document); + + --color-alert-note: var(--dark-color-alert-note); + --color-alert-tip: var(--dark-color-alert-tip); + --color-alert-important: var(--dark-color-alert-important); + --color-alert-warning: var(--dark-color-alert-warning); + --color-alert-caution: var(--dark-color-alert-caution); + + --external-icon: var(--dark-external-icon); + --color-scheme: var(--dark-color-scheme); + } + } + + html { + color-scheme: var(--color-scheme); + } + + body { + margin: 0; + } + + :root[data-theme="light"] { + --color-background: var(--light-color-background); + --color-background-secondary: var(--light-color-background-secondary); + --color-background-warning: var(--light-color-background-warning); + --color-warning-text: var(--light-color-warning-text); + --color-icon-background: var(--light-color-icon-background); + --color-accent: var(--light-color-accent); + --color-active-menu-item: var(--light-color-active-menu-item); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + --color-icon-text: var(--light-color-icon-text); + + --color-comment-tag-text: var(--light-color-text); + --color-comment-tag: var(--light-color-background); + + --color-link: var(--light-color-link); + --color-focus-outline: var(--light-color-focus-outline); + + --color-ts-keyword: var(--light-color-ts-keyword); + --color-ts-project: var(--light-color-ts-project); + --color-ts-module: var(--light-color-ts-module); + --color-ts-namespace: var(--light-color-ts-namespace); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-enum-member: var(--light-color-ts-enum-member); + --color-ts-variable: var(--light-color-ts-variable); + --color-ts-function: var(--light-color-ts-function); + --color-ts-class: var(--light-color-ts-class); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-constructor: var(--light-color-ts-constructor); + --color-ts-property: var(--light-color-ts-property); + --color-ts-method: var(--light-color-ts-method); + --color-ts-reference: var(--light-color-ts-reference); + --color-ts-call-signature: var(--light-color-ts-call-signature); + --color-ts-index-signature: var(--light-color-ts-index-signature); + --color-ts-constructor-signature: var( + --light-color-ts-constructor-signature + ); + --color-ts-parameter: var(--light-color-ts-parameter); + --color-ts-type-parameter: var(--light-color-ts-type-parameter); + --color-ts-accessor: var(--light-color-ts-accessor); + --color-ts-get-signature: var(--light-color-ts-get-signature); + --color-ts-set-signature: var(--light-color-ts-set-signature); + --color-ts-type-alias: var(--light-color-ts-type-alias); + --color-document: var(--light-color-document); + + --color-note: var(--light-color-note); + --color-tip: var(--light-color-tip); + --color-important: var(--light-color-important); + --color-warning: var(--light-color-warning); + --color-caution: var(--light-color-caution); + + --external-icon: var(--light-external-icon); + --color-scheme: var(--light-color-scheme); + } + + :root[data-theme="dark"] { + --color-background: var(--dark-color-background); + --color-background-secondary: var(--dark-color-background-secondary); + --color-background-warning: var(--dark-color-background-warning); + --color-warning-text: var(--dark-color-warning-text); + --color-icon-background: var(--dark-color-icon-background); + --color-accent: var(--dark-color-accent); + --color-active-menu-item: var(--dark-color-active-menu-item); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + --color-icon-text: var(--dark-color-icon-text); + + --color-comment-tag-text: var(--dark-color-text); + --color-comment-tag: var(--dark-color-background); + + --color-link: var(--dark-color-link); + --color-focus-outline: var(--dark-color-focus-outline); + + --color-ts-keyword: var(--dark-color-ts-keyword); + --color-ts-project: var(--dark-color-ts-project); + --color-ts-module: var(--dark-color-ts-module); + --color-ts-namespace: var(--dark-color-ts-namespace); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-enum-member: var(--dark-color-ts-enum-member); + --color-ts-variable: var(--dark-color-ts-variable); + --color-ts-function: var(--dark-color-ts-function); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-constructor: var(--dark-color-ts-constructor); + --color-ts-property: var(--dark-color-ts-property); + --color-ts-method: var(--dark-color-ts-method); + --color-ts-reference: var(--dark-color-ts-reference); + --color-ts-call-signature: var(--dark-color-ts-call-signature); + --color-ts-index-signature: var(--dark-color-ts-index-signature); + --color-ts-constructor-signature: var( + --dark-color-ts-constructor-signature + ); + --color-ts-parameter: var(--dark-color-ts-parameter); + --color-ts-type-parameter: var(--dark-color-ts-type-parameter); + --color-ts-accessor: var(--dark-color-ts-accessor); + --color-ts-get-signature: var(--dark-color-ts-get-signature); + --color-ts-set-signature: var(--dark-color-ts-set-signature); + --color-ts-type-alias: var(--dark-color-ts-type-alias); + --color-document: var(--dark-color-document); + + --color-note: var(--dark-color-note); + --color-tip: var(--dark-color-tip); + --color-important: var(--dark-color-important); + --color-warning: var(--dark-color-warning); + --color-caution: var(--dark-color-caution); + + --external-icon: var(--dark-external-icon); + --color-scheme: var(--dark-color-scheme); + } + + *:focus-visible, + .tsd-accordion-summary:focus-visible svg { + outline: 2px solid var(--color-focus-outline); + } + + .always-visible, + .always-visible .tsd-signatures { + display: inherit !important; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + line-height: 1.2; + } + + h1 { + font-size: 1.875rem; + margin: 0.67rem 0; + } + + h2 { + font-size: 1.5rem; + margin: 0.83rem 0; + } + + h3 { + font-size: 1.25rem; + margin: 1rem 0; + } + + h4 { + font-size: 1.05rem; + margin: 1.33rem 0; + } + + h5 { + font-size: 1rem; + margin: 1.5rem 0; + } + + h6 { + font-size: 0.875rem; + margin: 2.33rem 0; + } + + dl, + menu, + ol, + ul { + margin: 1em 0; + } + + dd { + margin: 0 0 0 34px; + } + + .container { + max-width: 1700px; + padding: 0 2rem; + } + + /* Footer */ + footer { + border-top: 1px solid var(--color-accent); + padding-top: 1rem; + padding-bottom: 1rem; + max-height: 3.5rem; + } + footer > p { + margin: 0 1em; + } + + .container-main { + margin: 0 auto; + /* toolbar, footer, margin */ + min-height: calc(100vh - 41px - 56px - 4rem); + } + + @keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + @keyframes fade-out { + from { + opacity: 1; + visibility: visible; + } + to { + opacity: 0; + } + } + @keyframes fade-in-delayed { + 0% { + opacity: 0; + } + 33% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + @keyframes fade-out-delayed { + 0% { + opacity: 1; + visibility: visible; + } + 66% { + opacity: 0; + } + 100% { + opacity: 0; + } + } + @keyframes pop-in-from-right { + from { + transform: translate(100%, 0); + } + to { + transform: translate(0, 0); + } + } + @keyframes pop-out-to-right { + from { + transform: translate(0, 0); + visibility: visible; + } + to { + transform: translate(100%, 0); + } + } + body { + background: var(--color-background); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", + Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + color: var(--color-text); + } + + a { + color: var(--color-link); + text-decoration: none; + } + a:hover { + text-decoration: underline; + } + a.external[target="_blank"] { + background-image: var(--external-icon); + background-position: top 3px right; + background-repeat: no-repeat; + padding-right: 13px; + } + a.tsd-anchor-link { + color: var(--color-text); + } + + code, + pre { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + padding: 0.2em; + margin: 0; + font-size: 0.875rem; + border-radius: 0.8em; + } + + pre { + position: relative; + white-space: pre-wrap; + word-wrap: break-word; + padding: 10px; + border: 1px solid var(--color-accent); + margin-bottom: 8px; + } + pre code { + padding: 0; + font-size: 100%; + } + pre > button { + position: absolute; + top: 10px; + right: 10px; + opacity: 0; + transition: opacity 0.1s; + box-sizing: border-box; + } + pre:hover > button, + pre > button.visible { + opacity: 1; + } + + blockquote { + margin: 1em 0; + padding-left: 1em; + border-left: 4px solid gray; + } + + .tsd-typography { + line-height: 1.333em; + } + .tsd-typography ul { + list-style: square; + padding: 0 0 0 20px; + margin: 0; + } + .tsd-typography .tsd-index-panel h3, + .tsd-index-panel .tsd-typography h3, + .tsd-typography h4, + .tsd-typography h5, + .tsd-typography h6 { + font-size: 1em; + } + .tsd-typography h5, + .tsd-typography h6 { + font-weight: normal; + } + .tsd-typography p, + .tsd-typography ul, + .tsd-typography ol { + margin: 1em 0; + } + .tsd-typography table { + border-collapse: collapse; + border: none; + } + .tsd-typography td, + .tsd-typography th { + padding: 6px 13px; + border: 1px solid var(--color-accent); + } + .tsd-typography thead, + .tsd-typography tr:nth-child(even) { + background-color: var(--color-background-secondary); + } + + .tsd-alert { + padding: 8px 16px; + margin-bottom: 16px; + border-left: 0.25em solid var(--alert-color); + } + .tsd-alert blockquote > :last-child, + .tsd-alert > :last-child { + margin-bottom: 0; + } + .tsd-alert-title { + color: var(--alert-color); + display: inline-flex; + align-items: center; + } + .tsd-alert-title span { + margin-left: 4px; + } + + .tsd-alert-note { + --alert-color: var(--color-alert-note); + } + .tsd-alert-tip { + --alert-color: var(--color-alert-tip); + } + .tsd-alert-important { + --alert-color: var(--color-alert-important); + } + .tsd-alert-warning { + --alert-color: var(--color-alert-warning); + } + .tsd-alert-caution { + --alert-color: var(--color-alert-caution); + } + + .tsd-breadcrumb { + margin: 0; + padding: 0; + color: var(--color-text-aside); + } + .tsd-breadcrumb a { + color: var(--color-text-aside); + text-decoration: none; + } + .tsd-breadcrumb a:hover { + text-decoration: underline; + } + .tsd-breadcrumb li { + display: inline; + } + .tsd-breadcrumb li:after { + content: " / "; + } + + .tsd-comment-tags { + display: flex; + flex-direction: column; + } + dl.tsd-comment-tag-group { + display: flex; + align-items: center; + overflow: hidden; + margin: 0.5em 0; + } + dl.tsd-comment-tag-group dt { + display: flex; + margin-right: 0.5em; + font-size: 0.875em; + font-weight: normal; + } + dl.tsd-comment-tag-group dd { + margin: 0; + } + code.tsd-tag { + padding: 0.25em 0.4em; + border: 0.1em solid var(--color-accent); + margin-right: 0.25em; + font-size: 70%; + } + h1 code.tsd-tag:first-of-type { + margin-left: 0.25em; + } + + dl.tsd-comment-tag-group dd:before, + dl.tsd-comment-tag-group dd:after { + content: " "; + } + dl.tsd-comment-tag-group dd pre, + dl.tsd-comment-tag-group dd:after { + clear: both; + } + dl.tsd-comment-tag-group p { + margin: 0; + } + + .tsd-panel.tsd-comment .lead { + font-size: 1.1em; + line-height: 1.333em; + margin-bottom: 2em; + } + .tsd-panel.tsd-comment .lead:last-child { + margin-bottom: 0; + } + + .tsd-filter-visibility h4 { + font-size: 1rem; + padding-top: 0.75rem; + padding-bottom: 0.5rem; + margin: 0; + } + .tsd-filter-item:not(:last-child) { + margin-bottom: 0.5rem; + } + .tsd-filter-input { + display: flex; + width: -moz-fit-content; + width: fit-content; + align-items: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + } + .tsd-filter-input input[type="checkbox"] { + cursor: pointer; + position: absolute; + width: 1.5em; + height: 1.5em; + opacity: 0; + } + .tsd-filter-input input[type="checkbox"]:disabled { + pointer-events: none; + } + .tsd-filter-input svg { + cursor: pointer; + width: 1.5em; + height: 1.5em; + margin-right: 0.5em; + border-radius: 0.33em; + /* Leaving this at full opacity breaks event listeners on Firefox. + Don't remove unless you know what you're doing. */ + opacity: 0.99; + } + .tsd-filter-input input[type="checkbox"]:focus-visible + svg { + outline: 2px solid var(--color-focus-outline); + } + .tsd-checkbox-background { + fill: var(--color-accent); + } + input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark { + stroke: var(--color-text); + } + .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-background { + fill: var(--color-background); + stroke: var(--color-accent); + stroke-width: 0.25rem; + } + .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-checkmark { + stroke: var(--color-accent); + } + + .settings-label { + font-weight: bold; + text-transform: uppercase; + display: inline-block; + } + + .tsd-filter-visibility .settings-label { + margin: 0.75rem 0 0.5rem 0; + } + + .tsd-theme-toggle .settings-label { + margin: 0.75rem 0.75rem 0 0; + } + + .tsd-hierarchy h4 label:hover span { + text-decoration: underline; + } + + .tsd-hierarchy { + list-style: square; + margin: 0; + } + .tsd-hierarchy-target { + font-weight: bold; + } + .tsd-hierarchy-toggle { + color: var(--color-link); + cursor: pointer; + } + + .tsd-full-hierarchy:not(:last-child) { + margin-bottom: 1em; + padding-bottom: 1em; + border-bottom: 1px solid var(--color-accent); + } + .tsd-full-hierarchy, + .tsd-full-hierarchy ul { + list-style: none; + margin: 0; + padding: 0; + } + .tsd-full-hierarchy ul { + padding-left: 1.5rem; + } + .tsd-full-hierarchy a { + padding: 0.25rem 0 !important; + font-size: 1rem; + display: inline-flex; + align-items: center; + color: var(--color-text); + } + .tsd-full-hierarchy svg[data-dropdown] { + cursor: pointer; + } + .tsd-full-hierarchy svg[data-dropdown="false"] { + transform: rotate(-90deg); + } + .tsd-full-hierarchy svg[data-dropdown="false"] ~ ul { + display: none; + } + + .tsd-panel-group.tsd-index-group { + margin-bottom: 0; + } + .tsd-index-panel .tsd-index-list { + list-style: none; + line-height: 1.333em; + margin: 0; + padding: 0.25rem 0 0 0; + overflow: hidden; + display: grid; + grid-template-columns: repeat(3, 1fr); + column-gap: 1rem; + grid-template-rows: auto; + } + @media (max-width: 1024px) { + .tsd-index-panel .tsd-index-list { + grid-template-columns: repeat(2, 1fr); + } + } + @media (max-width: 768px) { + .tsd-index-panel .tsd-index-list { + grid-template-columns: repeat(1, 1fr); + } + } + .tsd-index-panel .tsd-index-list li { + -webkit-page-break-inside: avoid; + -moz-page-break-inside: avoid; + -ms-page-break-inside: avoid; + -o-page-break-inside: avoid; + page-break-inside: avoid; + } + + .tsd-flag { + display: inline-block; + padding: 0.25em 0.4em; + border-radius: 4px; + color: var(--color-comment-tag-text); + background-color: var(--color-comment-tag); + text-indent: 0; + font-size: 75%; + line-height: 1; + font-weight: normal; + } + + .tsd-anchor { + position: relative; + top: -100px; + } + + .tsd-member { + position: relative; + } + .tsd-member .tsd-anchor + h3 { + display: flex; + align-items: center; + margin-top: 0; + margin-bottom: 0; + border-bottom: none; + } + + .tsd-navigation.settings { + margin: 1rem 0; + } + .tsd-navigation > a, + .tsd-navigation .tsd-accordion-summary { + width: calc(100% - 0.25rem); + display: flex; + align-items: center; + } + .tsd-navigation a, + .tsd-navigation summary > span, + .tsd-page-navigation a { + display: flex; + width: calc(100% - 0.25rem); + align-items: center; + padding: 0.25rem; + color: var(--color-text); + text-decoration: none; + box-sizing: border-box; + } + .tsd-navigation a.current, + .tsd-page-navigation a.current { + background: var(--color-active-menu-item); + } + .tsd-navigation a:hover, + .tsd-page-navigation a:hover { + text-decoration: underline; + } + .tsd-navigation ul, + .tsd-page-navigation ul { + margin-top: 0; + margin-bottom: 0; + padding: 0; + list-style: none; + } + .tsd-navigation li, + .tsd-page-navigation li { + padding: 0; + max-width: 100%; + } + .tsd-navigation .tsd-nav-link { + display: none; + } + .tsd-nested-navigation { + margin-left: 3rem; + } + .tsd-nested-navigation > li > details { + margin-left: -1.5rem; + } + .tsd-small-nested-navigation { + margin-left: 1.5rem; + } + .tsd-small-nested-navigation > li > details { + margin-left: -1.5rem; + } + + .tsd-page-navigation-section { + margin-left: 10px; + } + .tsd-page-navigation-section > summary { + padding: 0.25rem; + } + .tsd-page-navigation-section > div { + margin-left: 20px; + } + .tsd-page-navigation ul { + padding-left: 1.75rem; + } + + #tsd-sidebar-links a { + margin-top: 0; + margin-bottom: 0.5rem; + line-height: 1.25rem; + } + #tsd-sidebar-links a:last-of-type { + margin-bottom: 0; + } + + a.tsd-index-link { + padding: 0.25rem 0 !important; + font-size: 1rem; + line-height: 1.25rem; + display: inline-flex; + align-items: center; + color: var(--color-text); + } + .tsd-accordion-summary { + list-style-type: none; /* hide marker on non-safari */ + outline: none; /* broken on safari, so just hide it */ + } + .tsd-accordion-summary::-webkit-details-marker { + display: none; /* hide marker on safari */ + } + .tsd-accordion-summary, + .tsd-accordion-summary a { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + + cursor: pointer; + } + .tsd-accordion-summary a { + width: calc(100% - 1.5rem); + } + .tsd-accordion-summary > * { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; + } + .tsd-accordion .tsd-accordion-summary > svg { + margin-left: 0.25rem; + vertical-align: text-top; + } + /* + * We need to be careful to target the arrow indicating whether the accordion + * is open, but not any other SVGs included in the details element. + */ + .tsd-accordion:not([open]) > .tsd-accordion-summary > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h1 > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h2 > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h3 > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h4 > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h5 > svg:first-child { + transform: rotate(-90deg); + } + .tsd-index-content > :not(:first-child) { + margin-top: 0.75rem; + } + .tsd-index-heading { + margin-top: 1.5rem; + margin-bottom: 0.75rem; + } + + .tsd-no-select { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + .tsd-kind-icon { + margin-right: 0.5rem; + width: 1.25rem; + height: 1.25rem; + min-width: 1.25rem; + min-height: 1.25rem; + } + .tsd-signature > .tsd-kind-icon { + margin-right: 0.8rem; + } + + .tsd-panel { + margin-bottom: 2.5rem; + } + .tsd-panel.tsd-member { + margin-bottom: 4rem; + } + .tsd-panel:empty { + display: none; + } + .tsd-panel > h1, + .tsd-panel > h2, + .tsd-panel > h3 { + margin: 1.5rem -1.5rem 0.75rem -1.5rem; + padding: 0 1.5rem 0.75rem 1.5rem; + } + .tsd-panel > h1.tsd-before-signature, + .tsd-panel > h2.tsd-before-signature, + .tsd-panel > h3.tsd-before-signature { + margin-bottom: 0; + border-bottom: none; + } + + .tsd-panel-group { + margin: 2rem 0; + } + .tsd-panel-group.tsd-index-group { + margin: 2rem 0; + } + .tsd-panel-group.tsd-index-group details { + margin: 2rem 0; + } + .tsd-panel-group > .tsd-accordion-summary { + margin-bottom: 1rem; + } + + #tsd-search { + transition: background-color 0.2s; + } + #tsd-search .title { + position: relative; + z-index: 2; + } + #tsd-search .field { + position: absolute; + left: 0; + top: 0; + right: 2.5rem; + height: 100%; + } + #tsd-search .field input { + box-sizing: border-box; + position: relative; + top: -50px; + z-index: 1; + width: 100%; + padding: 0 10px; + opacity: 0; + outline: 0; + border: 0; + background: transparent; + color: var(--color-text); + } + #tsd-search .field label { + position: absolute; + overflow: hidden; + right: -40px; + } + #tsd-search .field input, + #tsd-search .title, + #tsd-toolbar-links a { + transition: opacity 0.2s; + } + #tsd-search .results { + position: absolute; + visibility: hidden; + top: 40px; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); + } + #tsd-search .results li { + background-color: var(--color-background); + line-height: initial; + padding: 4px; + } + #tsd-search .results li:nth-child(even) { + background-color: var(--color-background-secondary); + } + #tsd-search .results li.state { + display: none; + } + #tsd-search .results li.current:not(.no-results), + #tsd-search .results li:hover:not(.no-results) { + background-color: var(--color-accent); + } + #tsd-search .results a { + display: flex; + align-items: center; + padding: 0.25rem; + box-sizing: border-box; + } + #tsd-search .results a:before { + top: 10px; + } + #tsd-search .results span.parent { + color: var(--color-text-aside); + font-weight: normal; + } + #tsd-search.has-focus { + background-color: var(--color-accent); + } + #tsd-search.has-focus .field input { + top: 0; + opacity: 1; + } + #tsd-search.has-focus .title, + #tsd-search.has-focus #tsd-toolbar-links a { + z-index: 0; + opacity: 0; + } + #tsd-search.has-focus .results { + visibility: visible; + } + #tsd-search.loading .results li.state.loading { + display: block; + } + #tsd-search.failure .results li.state.failure { + display: block; + } + + #tsd-toolbar-links { + position: absolute; + top: 0; + right: 2rem; + height: 100%; + display: flex; + align-items: center; + justify-content: flex-end; + } + #tsd-toolbar-links a { + margin-left: 1.5rem; + } + #tsd-toolbar-links a:hover { + text-decoration: underline; + } + + .tsd-signature { + margin: 0 0 1rem 0; + padding: 1rem 0.5rem; + border: 1px solid var(--color-accent); + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + font-size: 14px; + overflow-x: auto; + } + + .tsd-signature-keyword { + color: var(--color-ts-keyword); + font-weight: normal; + } + + .tsd-signature-symbol { + color: var(--color-text-aside); + font-weight: normal; + } + + .tsd-signature-type { + font-style: italic; + font-weight: normal; + } + + .tsd-signatures { + padding: 0; + margin: 0 0 1em 0; + list-style-type: none; + } + .tsd-signatures .tsd-signature { + margin: 0; + border-color: var(--color-accent); + border-width: 1px 0; + transition: background-color 0.1s; + } + .tsd-signatures .tsd-index-signature:not(:last-child) { + margin-bottom: 1em; + } + .tsd-signatures .tsd-index-signature .tsd-signature { + border-width: 1px; + } + .tsd-description .tsd-signatures .tsd-signature { + border-width: 1px; + } + + ul.tsd-parameter-list, + ul.tsd-type-parameter-list { + list-style: square; + margin: 0; + padding-left: 20px; + } + ul.tsd-parameter-list > li.tsd-parameter-signature, + ul.tsd-type-parameter-list > li.tsd-parameter-signature { + list-style: none; + margin-left: -20px; + } + ul.tsd-parameter-list h5, + ul.tsd-type-parameter-list h5 { + font-size: 16px; + margin: 1em 0 0.5em 0; + } + .tsd-sources { + margin-top: 1rem; + font-size: 0.875em; + } + .tsd-sources a { + color: var(--color-text-aside); + text-decoration: underline; + } + .tsd-sources ul { + list-style: none; + padding: 0; + } + + .tsd-page-toolbar { + position: sticky; + z-index: 1; + top: 0; + left: 0; + width: 100%; + color: var(--color-text); + background: var(--color-background-secondary); + border-bottom: 1px var(--color-accent) solid; + transition: transform 0.3s ease-in-out; + } + .tsd-page-toolbar a { + color: var(--color-text); + text-decoration: none; + } + .tsd-page-toolbar a.title { + font-weight: bold; + } + .tsd-page-toolbar a.title:hover { + text-decoration: underline; + } + .tsd-page-toolbar .tsd-toolbar-contents { + display: flex; + justify-content: space-between; + height: 2.5rem; + margin: 0 auto; + } + .tsd-page-toolbar .table-cell { + position: relative; + white-space: nowrap; + line-height: 40px; + } + .tsd-page-toolbar .table-cell:first-child { + width: 100%; + } + .tsd-page-toolbar .tsd-toolbar-icon { + box-sizing: border-box; + line-height: 0; + padding: 12px 0; + } + + .tsd-widget { + display: inline-block; + overflow: hidden; + opacity: 0.8; + height: 40px; + transition: + opacity 0.1s, + background-color 0.2s; + vertical-align: bottom; + cursor: pointer; + } + .tsd-widget:hover { + opacity: 0.9; + } + .tsd-widget.active { + opacity: 1; + background-color: var(--color-accent); + } + .tsd-widget.no-caption { + width: 40px; + } + .tsd-widget.no-caption:before { + margin: 0; + } + + .tsd-widget.options, + .tsd-widget.menu { + display: none; + } + input[type="checkbox"] + .tsd-widget:before { + background-position: -120px 0; + } + input[type="checkbox"]:checked + .tsd-widget:before { + background-position: -160px 0; + } + + img { + max-width: 100%; + } + + .tsd-member-summary-name { + display: inline-flex; + align-items: center; + padding: 0.25rem; + text-decoration: none; + } + + .tsd-anchor-icon { + display: inline-flex; + align-items: center; + margin-left: 0.5rem; + color: var(--color-text); + } + + .tsd-anchor-icon svg { + width: 1em; + height: 1em; + visibility: hidden; + } + + .tsd-member-summary-name:hover > .tsd-anchor-icon svg, + .tsd-anchor-link:hover > .tsd-anchor-icon svg { + visibility: visible; + } + + .deprecated { + text-decoration: line-through !important; + } + + .warning { + padding: 1rem; + color: var(--color-warning-text); + background: var(--color-background-warning); + } + + .tsd-kind-project { + color: var(--color-ts-project); + } + .tsd-kind-module { + color: var(--color-ts-module); + } + .tsd-kind-namespace { + color: var(--color-ts-namespace); + } + .tsd-kind-enum { + color: var(--color-ts-enum); + } + .tsd-kind-enum-member { + color: var(--color-ts-enum-member); + } + .tsd-kind-variable { + color: var(--color-ts-variable); + } + .tsd-kind-function { + color: var(--color-ts-function); + } + .tsd-kind-class { + color: var(--color-ts-class); + } + .tsd-kind-interface { + color: var(--color-ts-interface); + } + .tsd-kind-constructor { + color: var(--color-ts-constructor); + } + .tsd-kind-property { + color: var(--color-ts-property); + } + .tsd-kind-method { + color: var(--color-ts-method); + } + .tsd-kind-reference { + color: var(--color-ts-reference); + } + .tsd-kind-call-signature { + color: var(--color-ts-call-signature); + } + .tsd-kind-index-signature { + color: var(--color-ts-index-signature); + } + .tsd-kind-constructor-signature { + color: var(--color-ts-constructor-signature); + } + .tsd-kind-parameter { + color: var(--color-ts-parameter); + } + .tsd-kind-type-parameter { + color: var(--color-ts-type-parameter); + } + .tsd-kind-accessor { + color: var(--color-ts-accessor); + } + .tsd-kind-get-signature { + color: var(--color-ts-get-signature); + } + .tsd-kind-set-signature { + color: var(--color-ts-set-signature); + } + .tsd-kind-type-alias { + color: var(--color-ts-type-alias); + } + + /* if we have a kind icon, don't color the text by kind */ + .tsd-kind-icon ~ span { + color: var(--color-text); + } + + * { + scrollbar-width: thin; + scrollbar-color: var(--color-accent) var(--color-icon-background); + } + + *::-webkit-scrollbar { + width: 0.75rem; + } + + *::-webkit-scrollbar-track { + background: var(--color-icon-background); + } + + *::-webkit-scrollbar-thumb { + background-color: var(--color-accent); + border-radius: 999rem; + border: 0.25rem solid var(--color-icon-background); + } + + /* mobile */ + @media (max-width: 769px) { + .tsd-widget.options, + .tsd-widget.menu { + display: inline-block; + } + + .container-main { + display: flex; + } + html .col-content { + float: none; + max-width: 100%; + width: 100%; + } + html .col-sidebar { + position: fixed !important; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + z-index: 1024; + top: 0 !important; + bottom: 0 !important; + left: auto !important; + right: 0 !important; + padding: 1.5rem 1.5rem 0 0; + width: 75vw; + visibility: hidden; + background-color: var(--color-background); + transform: translate(100%, 0); + } + html .col-sidebar > *:last-child { + padding-bottom: 20px; + } + html .overlay { + content: ""; + display: block; + position: fixed; + z-index: 1023; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.75); + visibility: hidden; + } + + .to-has-menu .overlay { + animation: fade-in 0.4s; + } + + .to-has-menu .col-sidebar { + animation: pop-in-from-right 0.4s; + } + + .from-has-menu .overlay { + animation: fade-out 0.4s; + } + + .from-has-menu .col-sidebar { + animation: pop-out-to-right 0.4s; + } + + .has-menu body { + overflow: hidden; + } + .has-menu .overlay { + visibility: visible; + } + .has-menu .col-sidebar { + visibility: visible; + transform: translate(0, 0); + display: flex; + flex-direction: column; + gap: 1.5rem; + max-height: 100vh; + padding: 1rem 2rem; + } + .has-menu .tsd-navigation { + max-height: 100%; + } + #tsd-toolbar-links { + display: none; + } + .tsd-navigation .tsd-nav-link { + display: flex; + } + } + + /* one sidebar */ + @media (min-width: 770px) { + .container-main { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); + grid-template-areas: "sidebar content"; + margin: 2rem auto; + } + + .col-sidebar { + grid-area: sidebar; + } + .col-content { + grid-area: content; + padding: 0 1rem; + } + } + @media (min-width: 770px) and (max-width: 1399px) { + .col-sidebar { + max-height: calc(100vh - 2rem - 42px); + overflow: auto; + position: sticky; + top: 42px; + padding-top: 1rem; + } + .site-menu { + margin-top: 1rem; + } + } + + /* two sidebars */ + @media (min-width: 1200px) { + .container-main { + grid-template-columns: minmax(0, 1fr) minmax(0, 2.5fr) minmax( + 0, + 20rem + ); + grid-template-areas: "sidebar content toc"; + } + + .col-sidebar { + display: contents; + } + + .page-menu { + grid-area: toc; + padding-left: 1rem; + } + .site-menu { + grid-area: sidebar; + } + + .site-menu { + margin-top: 1rem; + } + + .page-menu, + .site-menu { + max-height: calc(100vh - 2rem - 42px); + overflow: auto; + position: sticky; + top: 42px; + } + } +} diff --git a/deps/cloudxr/docs/documents/Client_Setup.html b/deps/cloudxr/docs/documents/Client_Setup.html new file mode 100644 index 0000000..05a9a03 --- /dev/null +++ b/deps/cloudxr/docs/documents/Client_Setup.html @@ -0,0 +1,247 @@ +Client Setup | CloudXR.js SDK Documentation - v6.0.0-beta

Client Setup Guide

This guide provides configuration examples for CloudXR.js client devices, including browser setup, web server hosting options, and connection modes. These examples demonstrate common deployment scenarios you may encounter.

+

CloudXR.js client applications can be hosted using either HTTP or HTTPS protocols. We provide examples for both modes to help you choose the right approach for your deployment.

+

This example demonstrates the simplest configuration for local development and testing.

+

Example command:

+
npm run dev-server
+
+ +

Access URLs:

+
    +
  • Local: http://localhost:8080/
  • +
  • Network: http://<server-ip>:8080/
  • +
+

Characteristics:

+
    +
  • Simplified setup with minimal configuration
  • +
  • Supports connections to both ws:// (direct) and wss:// (proxied) CloudXR Runtime endpoints
  • +
  • Requires browser security flags for WebXR functionality (see Browser Configuration)
  • +
  • Recommended for: Local development, trusted network environments
  • +
+

This example demonstrates HTTPS hosting, which is used for both development with WebXR devices and production deployments. It provides encrypted client connections and is required for secure WebSocket connections.

+

Example command:

+
npm run dev-server:https
+
+ +

Access URLs:

+
    +
  • Local: https://localhost:8080/
  • +
  • Network: https://<server-ip>:8080/
  • +
+

Characteristics:

+
    +
  • Automatically generates self-signed SSL certificates (for development) or custom certificates (for production)
  • +
  • Requires wss:// (secure WebSocket) connection to CloudXR Runtime
  • +
  • Browsers enforce mixed content policy, blocking ws:// connections from HTTPS pages
  • +
  • Requires certificate trust configuration in client browser
  • +
  • Recommended for: WebXR device testing, production deployments, remote access, security-sensitive environments
  • +
+

The following table shows example connection configurations between the web application server and CloudXR Runtime:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Client Web ServerRuntime ConnectionStatusUse Case
HTTPws://server-ip:49100✅ SupportedLocal development (simplest path)
HTTPwss://proxy-ip:48322✅ SupportedTesting proxy configuration
HTTPSws://server-ip:49100❌ BlockedMixed content security policy violation
HTTPSwss://proxy-ip:48322✅ SupportedWebXR device testing, production deployment
+
+

Important: When using HTTPS mode for your web application, you must configure a WebSocket proxy with TLS support. See the Networking Setup Guide - WebSocket Proxy Setup for configuration details.

+
+

This section provides configurations for Meta Quest 3 to enable WebXR functionality with CloudXR.js applications.

+

When using HTTP mode, the Meta Quest 3 browser needs explicit permission to access WebXR APIs from non-HTTPS origins. Here's how to configure it:

+
    +
  1. +

    Access Chrome flags:

    +
      +
    • Open the Meta Quest 3 Browser
    • +
    • Navigate to chrome://flags
    • +
    +
  2. +
  3. +

    Enable insecure origins:

    +
      +
    • Search for "insecure" in the search field
    • +
    • Locate the flag: unsafely-treat-insecure-origin-as-secure
    • +
    • Set the flag to Enabled
    • +
    +
  4. +
  5. +

    Add your development server:

    +
      +
    • In the text field below the flag, enter your web server URL
    • +
    • Format: http://<server-ip>:8080
    • +
    • Include the protocol (http://) and port number (:8080)
    • +
    • Multiple URLs can be separated by commas if needed
    • +
    +
  6. +
  7. +

    Apply configuration:

    +
      +
    • Click or tap outside the text field to defocus
    • +
    • A "Relaunch" button will appear at the bottom of the screen
    • +
    • Click Relaunch to apply changes
    • +
    +
  8. +
  9. +

    Verify configuration:

    +
      +
    • Return to chrome://flags after relaunch
    • +
    • Confirm the flag remains enabled and your URL is saved
    • +
    +
  10. +
+

When using HTTPS mode with self-signed certificates (for the web server and the WebSocket proxy), you need to configure the browser to trust these certificates.

+
+

Note: Certificate trust settings persist across browser sessions. You only need to perform this configuration once per certificate.

+
+
    +
  1. +

    Access the proxy endpoint:

    +
      +
    • +

      In Meta Quest 3 Browser in the sample client page, fill in the proxy IP as server IP and corresponding port, and click "click xxx to accept cert" as shown below. Or you can directly navigate to https://<proxy-ip>:48322/

      +

      +
    • +
    +
  2. +
  3. +

    Accept certificate warning:

    +
      +
    • +

      Click Advanced

      +
    • +
    • +

      Click Proceed to <proxy-ip> (unsafe) or similar option

      +

      +
    • +
    +
  4. +
  5. +

    Expected behavior:

    +
      +
    • The page may display a 501 Not Implemented error
    • +
    • This is expected - the proxy only handles WebSocket connections, not HTTP requests
    • +
    • The certificate is now trusted for WebSocket connections
    • +
    +
  6. +
+
+

Note: If you restart or recreate the proxy container (using the Docker HAProxy example with auto-generated certificates), a new self-signed certificate will be generated. You must repeat the certificate trust process by visiting https://<proxy-ip>:48322/ again and accepting the new certificate warning. If you mount your own certificate (see Networking Setup - Using Custom Certificates), the same certificate persists across restarts and you only need to trust it once.

+
+
    +
  1. +

    Access the web application:

    +
      +
    • Open Meta Quest 3 Browser
    • +
    • Navigate to https://<server-ip>:8080/
    • +
    +
  2. +
  3. +

    Accept certificate warning:

    +
      +
    • +

      Click Advanced

      +
    • +
    • +

      Click Proceed to <server-ip> (unsafe) or similar option

      +

      +
    • +
    +
  4. +
  5. +

    Verify access:

    +
      +
    • The web application should load successfully
    • +
    +
  6. +
+

When connecting to a CloudXR.js application:

+
    +
  1. The browser will prompt for WebXR permissions
  2. +
  3. Select Allow when prompted
  4. +
  5. The immersive session will begin after permission is granted
  6. +
+
+

Tip: For rapid development and debugging, you can test your CloudXR.js application on a desktop browser before deploying to Meta Quest 3. Detailed setup instructions are provided in the Isaac Lab Client guide.

+
+

For network and firewall configuration requirements, including port setup and bandwidth recommendations, see the Networking Setup Guide.

+

Symptoms:

+
    +
  • Application displays "WebXR not supported" or similar error
  • +
  • Connect button is disabled or missing
  • +
+

Solutions:

+
    +
  • Verify browser flags are configured correctly (HTTP mode example)
  • +
  • Ensure you're accessing via the configured URL exactly as specified
  • +
  • Confirm the browser has been relaunched after changing flags
  • +
  • Try accessing via incognito/private browsing mode
  • +
+

Symptoms:

+
    +
  • Connection fails with security warnings
  • +
  • WebSocket connection shows "SSL certificate error"
  • +
+

Solutions:

+
    +
  • Complete the certificate trust process for both web server and proxy
  • +
  • Verify you clicked "Proceed" on the certificate warning pages
  • +
  • Clear browser cache and retry the trust process
  • +
  • For persistent issues, regenerate certificates
  • +
+

Symptoms:

+
    +
  • Console shows "blocked by mixed content policy" errors
  • +
  • WebSocket connection fails from HTTPS page
  • +
+

Solutions:

+
    +
  • When using npm run dev-server:https, ensure runtime connection uses wss:// not ws://
  • +
  • Verify a WebSocket proxy is configured and running (see Networking Setup examples)
  • +
  • Check that proxy URL is accessible and trusted
  • +
+

Symptoms:

+
    +
  • Frequent disconnections during streaming
  • +
  • Poor video quality or high latency
  • +
+

Solutions:

+
    +
  • Review Networking Setup Guide for bandwidth and latency requirements
  • +
  • Verify firewall allows traffic on required ports
  • +
  • Ensure Wi-Fi connection is on 5GHz or 6GHz band
  • +
  • Check for network congestion from other devices
  • +
+ +
diff --git a/deps/cloudxr/docs/documents/Getting_Started.Sample_Client_-_React.html b/deps/cloudxr/docs/documents/Getting_Started.Sample_Client_-_React.html new file mode 100644 index 0000000..a2c9bb5 --- /dev/null +++ b/deps/cloudxr/docs/documents/Getting_Started.Sample_Client_-_React.html @@ -0,0 +1,150 @@ +Sample Client - React | CloudXR.js SDK Documentation - v6.0.0-beta

React Three Fiber Example

This is a comprehensive CloudXR.js React Three Fiber example application that demonstrates how to integrate CloudXR streaming with modern React development patterns. This example showcases the power of combining CloudXR.js with React Three Fiber, React Three XR, and React Three UIKit to create immersive XR experiences with rich 3D user interfaces.

+
+

NOTE: This example is not meant to be used for production.

+
+

This example showcases the integration of CloudXR.js with the React Three Fiber ecosystem, providing:

+
    +
  • React Three Fiber Integration: Seamless integration with Three.js through React components
  • +
  • React Three XR: WebXR session management with React hooks and state management
  • +
  • React Three UIKit: Rich 3D user interface components for VR/AR experiences
  • +
  • CloudXR Streaming: Real-time streaming of XR content from a CloudXR server
  • +
  • Modern React Patterns: Hooks, context, and component-based architecture
  • +
  • Dual UI System: 2D HTML interface for configuration and 3D VR interface for interaction
  • +
+
    +
  • Node.js (v20 or higher)
  • +
  • A CloudXR server running and accessible
  • +
  • A WebXR-compatible device (VR headset, AR device)
  • +
+
    +
  1. +

    Navigate to the example folder

    +
    cd react
    +
    + +
  2. +
  3. +

    Install Dependencies

    +
    # For this early access release, please run the following to install SDK from the given tarball. This step will not be needed when SDK is publicly accessible.
    npm install ../nvidia-cloudxr-6.0.0-beta.tgz

    npm install +
    + +
  4. +
  5. +

    Build the Application

    +
    npm run build
    +
    + +
  6. +
  7. +

    Start Development Server

    +
    npm run dev-server
    +
    + +
  8. +
  9. +

    Open in Browser

    +
      +
    • Navigate to http://localhost:8080 (or the port shown in terminal)
    • +
    • For desktop browsers, IWER (Immersive Web Emulator Runtime) will automatically load to emulate a Meta Quest 3 headset
    • +
    +
  10. +
+
    +
  1. +

    Configure Connection

    +
      +
    • Enter your CloudXR server IP address
    • +
    • Set the port (default: 49100)
    • +
    • Select AR or VR immersive mode
    • +
    +
  2. +
  3. +

    Adjust Settings (Optional)

    +
      +
    • Configure per-eye resolution (perEyeWidth and perEyeHeight, must be multiples of 16)
    • +
    • Set target frame rate and bitrate
    • +
    • Adjust XR reference space
    • +
    +
  4. +
  5. +

    Start Streaming

    +
      +
    • Click "CONNECT" to initiate the XR session
    • +
    • Grant XR permissions when prompted
    • +
    +
  6. +
+
+

NOTE: In order to connect to an actual server and start streaming, you need:

+
    +
  • A CloudXR server running and accessible
  • +
  • A WebXR-compatible device (VR/AR headset) or desktop browser (IWER loads automatically for emulation)
  • +
+
+

Main React application component managing:

+
    +
  • XR store configuration and session state
  • +
  • CloudXR component integration
  • +
  • 2D UI management and event handling
  • +
  • Error handling and capability checking
  • +
  • React Three Fiber Canvas setup
  • +
+

Handles the core CloudXR streaming functionality:

+
    +
  • CloudXR session lifecycle management
  • +
  • WebXR session event handling
  • +
  • WebGL state management and render target preservation
  • +
  • Frame-by-frame rendering loop with pose tracking
  • +
  • Integration with Three.js WebXRManager
  • +
+

Manages the 2D HTML interface:

+
    +
  • Form field management and localStorage persistence
  • +
  • Proxy configuration based on protocol
  • +
  • Event listener management and cleanup
  • +
  • Error handling and user feedback
  • +
  • Configuration validation and updates
  • +
+

Renders the in-VR user interface:

+
    +
  • React Three UIKit components for 3D UI
  • +
  • Interactive control buttons with hover effects
  • +
  • Server information and status display
  • +
  • Event handler integration
  • +
+
react/
├── src/
├── App.tsx # Main React application
├── CloudXRComponent.tsx # CloudXR streaming component
├── CloudXR2DUI.tsx # 2D UI management class
├── CloudXRUI.tsx # 3D VR UI component
├── index.tsx # React app entry point
└── index.html # HTML template
├── public/
├── play-circle.svg # Play button icon (Heroicons)
├── stop-circle.svg # Stop button icon (Heroicons)
├── arrow-uturn-left.svg # Reset button icon (Heroicons)
└── arrow-left-start-on-rectangle.svg # Disconnect button icon (Heroicons)
├── package.json # Dependencies and scripts
├── webpack.common.js # Webpack configuration
├── webpack.dev.js # Development webpack config
├── webpack.prod.js # Production webpack config
└── tsconfig.json # TypeScript configuration +
+ +

The application uses React Three XR's store for XR session management:

+
const store = createXRStore({
foveation: 0,
emulate: { syntheticEnvironment: false },
}); +
+ +

React Three Fiber Canvas with WebXR integration:

+
<Canvas events={noEvents} gl={{ preserveDrawingBuffer: true }}>
<XR store={store}>
<CloudXRComponent config={config} />
<CloudXR3DUI onAction1={handleAction1} />
</XR>
</Canvas> +
+ +

The CloudXR component uses useFrame for custom rendering:

+
useFrame((state, delta) => {
if (webXRManager.isPresenting && session) {
// CloudXR rendering logic
cxrSession.sendTrackingStateToServer(timestamp, xrFrame);
cxrSession.render(timestamp, xrFrame, layer);
}
}, -1000); +
+ +

The 3D UI uses React Three UIKit for modern VR/AR interfaces:

+
    +
  • Container: Layout and positioning components
  • +
  • Text: 3D text rendering with custom fonts
  • +
  • Button: Interactive buttons with hover effects
  • +
  • Image: Texture-based image display
  • +
  • Root: Main UI container with pixel-perfect rendering
  • +
+

3D UI elements are positioned in world space:

+
<group position={[1.8, -0.5, -1.3]} rotation={[0, -0.3, 0]}>
<Root pixelSize={0.001} width={1920} height={1440}>
{/* UI components */}
</Root>
</group> +
+ +

This example uses WebGL state tracking to prevent rendering conflicts between React Three Fiber and CloudXR. Both libraries render to the same WebGL context, but CloudXR's rendering operations modify WebGL state (framebuffers, textures, buffers, VAOs, shaders, blend modes, etc.) which can interfere with React Three Fiber's expectations. The example wraps the WebGL context with bindGL() from @helpers/WebGLStateBinding, then uses CloudXR's onWebGLStateChangeBegin and onWebGLStateChangeEnd callbacks to automatically save and restore state around CloudXR's rendering. This ensures React Three Fiber always finds the WebGL context in the expected state after each CloudXR render operation.

+

See examples/helpers/WebGLStateBinding.ts, WebGLState.ts, and WebGLStateApply.ts for implementation details. Comprehensive tests are available in tests/unit/WebGLState.test.ts and tests/playwright/WebGLTests/src/WebGLStateBindingTests.ts.

+

See the LICENSE file for details.

+

Icons used in the immersive UI are from Heroicons by Tailwind Labs, licensed under the MIT License. See HEROICONS_LICENSE for details.

+

Experience one of the compatible OpenXR applications with CloudXR Runtime

+ +
diff --git a/deps/cloudxr/docs/documents/Getting_Started.Sample_Client_-_WebGL.html b/deps/cloudxr/docs/documents/Getting_Started.Sample_Client_-_WebGL.html new file mode 100644 index 0000000..3f070f7 --- /dev/null +++ b/deps/cloudxr/docs/documents/Getting_Started.Sample_Client_-_WebGL.html @@ -0,0 +1,118 @@ +Sample Client - WebGL | CloudXR.js SDK Documentation - v6.0.0-beta

CloudXR.js Simple Example

A minimal WebGL example demonstrating WebXR streaming from a CloudXR server to a web browser. This example shows how to integrate WebXR with CloudXR to stream immersive VR/AR content.

+
+

Note: This example is for learning purposes, not production use.

+
+
    +
  • Node.js (v20 or higher)
  • +
+
    +
  1. +

    Navigate to the example folder

    +
    cd simple
    +
    + +
  2. +
  3. +

    Install Dependencies

    +
    # For this early access release, please run the following to install SDK from the given tarball. This step will not be needed when SDK is publicly accessible.
    npm install ../nvidia-cloudxr-6.0.0-beta.tgz

    npm install +
    + +
  4. +
  5. +

    Build the Application

    +
    npm run build
    +
    + +
  6. +
  7. +

    Start Development Server

    +
    npm run dev-server
    +
    + +
  8. +
  9. +

    Open in Browser

    +
      +
    • Navigate to http://localhost:8080 (or the port shown in terminal)
    • +
    • For desktop browsers, IWER (Immersive Web Emulator Runtime) will automatically load to emulate a Meta Quest 3 headset
    • +
    +
  10. +
+
    +
  1. +

    Configure Connection

    +
      +
    • Enter CloudXR server IP address (default: localhost)
    • +
    • Set port (default: 49100)
    • +
    • Select AR or VR mode
    • +
    +
  2. +
  3. +

    Adjust Settings (Optional)

    +
      +
    • Per-eye resolution (must be multiples of 16)
    • +
    • Target frame rate (72, 90, or 120 FPS)
    • +
    • Streaming bitrate
    • +
    • XR reference space and camera offsets
    • +
    +
  4. +
  5. +

    Start Streaming

    +
      +
    • Click "CONNECT"
    • +
    • Grant XR permissions when prompted
    • +
    +
  6. +
+

Requirements:

+
    +
  • CloudXR server running and accessible
  • +
  • WebXR-compatible device (VR/AR headset) or desktop browser (IWER loads automatically for emulation)
  • +
+

The main application class (CloudXRClient in main.ts) handles:

+

Initialization:

+
    +
  • UI element management and localStorage persistence
  • +
  • Browser capability checks (WebXR, WebGL2, WebRTC)
  • +
  • Event listener setup
  • +
+

Connection Flow:

+
    +
  1. WebGL Setup - Creates high-performance WebGL2 context
  2. +
  3. WebXR Session - Enters immersive VR/AR mode
  4. +
  5. Reference Space - Configures coordinate system for tracking
  6. +
  7. CloudXR Session - Establishes streaming connection to server
  8. +
  9. Render Loop - Sends tracking data, receives video, renders frames
  10. +
+

Key Components:

+
    +
  • WebXR Session - Hardware access (headset, controllers)
  • +
  • WebGL Context - Video rendering
  • +
  • CloudXR Session - Streaming management (WebRTC-based)
  • +
  • XRWebGLLayer - Bridge between WebXR and WebGL
  • +
+
simple/
├── src/
│ └── main.ts # Main application sample
├── index.html # UI and form elements sample
├── package.json # Dependencies and scripts
├── webpack.common.js # Webpack base configuration sample
├── webpack.dev.js # Development configuration sample
├── webpack.prod.js # Production configuration sample
└── tsconfig.json # TypeScript configuration sample +
+ +

The main.ts file contains well-commented code explaining each step:

+
    +
  1. Browser Checks - Validates WebXR, WebGL2, and WebRTC support
  2. +
  3. Connection Setup - Reads form inputs and validates configuration
  4. +
  5. WebGL Initialization - Creates optimized rendering context
  6. +
  7. WebXR Session - Enters immersive mode with requested features
  8. +
  9. CloudXR Setup - Configures streaming session with event handlers
  10. +
  11. Render Loop - Runs 72-120 times per second: +
      +
    • Sends tracking data to server
    • +
    • Receives video frame
    • +
    • Renders to display
    • +
    +
  12. +
+

Each method includes inline comments explaining the purpose and key concepts.

+

See the LICENSE file for details.

+

Experience one of the compatible OpenXR applications with CloudXR Runtime

+ +
diff --git a/deps/cloudxr/docs/documents/Getting_Started.html b/deps/cloudxr/docs/documents/Getting_Started.html new file mode 100644 index 0000000..836ac3b --- /dev/null +++ b/deps/cloudxr/docs/documents/Getting_Started.html @@ -0,0 +1,103 @@ +Getting Started | CloudXR.js SDK Documentation - v6.0.0-beta

Getting Started

+

It is strongly recommended that you work through this guide if you have never run CloudXR.js before.

+

Even for development, you'll need all the pieces of a CloudXR.js deployment in order to build and test a client. These are:

+
    +
  • a CloudXR Server +
      +
    • with a top-end NVIDIA GPU or 2 (e.g. dual RTX 6000 Ada)
    • +
    • which will run +
        +
      • the CloudXR Runtime
      • +
      • an OpenXR application (the thing you want to render on the server but see on the client)
      • +
      +
    • +
    +
  • +
  • a CloudXR.js development workstation +
      +
    • with Node.js and npm
    • +
    • which will run +
        +
      • the CloudXR.js sample client build
      • +
      • a Node-based development web server
      • +
      +
    • +
    +
  • +
  • a CloudXR.js client +
      +
    • which is one of: +
        +
      • a Meta Quest 3 with its built-in Browser app
      • +
      • a desktop browser: Google Chrome or Edge (IWER automatically loads for WebXR emulation)
      • +
      +
    • +
    • which will run... +
        +
      • the CloudXR.js sample client served from the development web server.
      • +
      +
    • +
    +
  • +
+

We recommend that for your first experience, all above run on the same computer to eliminate networking related issues.

+

You need both a working client and a working server in order to test. Typically we follow a startup flow where server starts before the client:

+
    +
  1. CloudXR Runtime
  2. +
  3. Server XR application
  4. +
  5. Sample client build + web server
  6. +
  7. Test from the same computer
  8. +
  9. Test from Quest 3 or a different computer
  10. +
+

Minimum: Single NVIDIA RTX 6000 Ada / L40 GPU or equivalent

+

Recommended: Two (2x) NVIDIA RTX 6000-class Ada (or later) GPUs:

+
    +
  • RTX 6000 Ada
  • +
  • L40 / L40S
  • +
  • RTX 6000 PRO Blackwell (Workstation, Max-Q Workstation, or Server)
  • +
+

Additional requirements:

+
    +
  • Linux OS (required for Isaac Lab Teleoperation)
  • +
  • Docker and NVIDIA Container Toolkit (Linux only)
  • +
  • Network: Wired Ethernet connection recommended
  • +
+

Supported headset:

+
    +
  • Meta Quest 3 (OS version 79 or later) - only headset validated for this release
  • +
  • Uses built-in Browser app (no special software installation needed)
  • +
  • Some manual browser configuration required (see Client Setup Guide)
  • +
+

For development/testing:

+
    +
  • Desktop Google Chrome (IWER automatically loads for WebXR emulation)
  • +
+

Critical requirements:

+
    +
  • Latency: ≤ 40ms required, ≤ 20ms recommended
  • +
  • Bandwidth: 200+ Mbps per streaming connection
  • +
  • WiFi: WiFi 6 (WiFi 6e recommended) at 5GHz or 6GHz
  • +
  • At most one WiFi hop: Either server or client must use wired Ethernet
  • +
+

See Networking Setup Guide for detailed configuration examples.

+

Following the high-level workflow above, use these guides to set up each component:

+

Choose an OpenXR server application to stream:

+ +

The guide includes instructions for setting up the CloudXR Runtime alongside the server application.

+

Build one of our example web clients:

+ +
    +
  • Test from the same computer first - Follow the desktop testing sections in the sample guides
  • +
  • Test from Meta Quest 3 - See Client Setup Guide for browser configuration
  • +
  • Configure networking (if needed) - See Networking Setup Guide for firewall and proxy examples
  • +
+
    +
  • Session APIs - Programmatic control of CloudXR sessions
  • +
  • Telemetry - Performance monitoring and debugging
  • +
+
diff --git a/deps/cloudxr/docs/documents/Networking_Setup.html b/deps/cloudxr/docs/documents/Networking_Setup.html new file mode 100644 index 0000000..94e7654 --- /dev/null +++ b/deps/cloudxr/docs/documents/Networking_Setup.html @@ -0,0 +1,418 @@ +Networking Setup | CloudXR.js SDK Documentation - v6.0.0-beta

Networking Setup

Proper network configuration is essential for high-quality CloudXR streaming. This guide covers network requirements, configuration, and optimization for CloudXR.js applications.

+

CloudXR.js operates using a two-tier connection architecture:

+

Hosts your WebXR application and serves it to client devices.

+
    +
  • +

    HTTP Mode: npm run dev-server

    +
      +
    • Local: http://localhost:8080/
    • +
    • Network: http://<server-ip>:8080/
    • +
    • Use case: Local development, trusted networks
    • +
    +
  • +
  • +

    HTTPS Mode: npm run dev-server:https

    +
      +
    • Local: https://localhost:8080/
    • +
    • Network: https://<server-ip>:8080/
    • +
    • Use case: Local development and production deployments, remote access
    • +
    +
  • +
+

Handles the XR streaming protocol between the client and CloudXR Runtime.

+
    +
  • +

    Direct Connection: ws://<server-ip>:49100

    +
      +
    • Uses unsecured WebSocket protocol (ws://)
    • +
    • No proxy required
    • +
    • Suitable for HTTP clients and local networks
    • +
    +
  • +
  • +

    Proxied Connection: wss://<proxy-ip>:48322

    +
      +
    • Uses WebSocket Secure protocol (wss:// - WebSocket over TLS/SSL)
    • +
    • Requires WebSocket proxy with TLS support
    • +
    • Required for HTTPS clients (browsers enforce mixed content policy)
    • +
    +
  • +
+
+

Important: When hosting your web application using HTTPS (npm run dev-server:https), you must configure a WebSocket proxy and connect using wss://. Browsers block non-secure WebSocket (ws://) connections from secure (HTTPS) pages due to mixed content security policies.

+
+

For detailed client configuration instructions, see the Client Setup Guide.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricRecommendedMin/Max
Downstream Available Bandwidth>200Mbps>100Mbps
Client-to-Server Ping<20ms<40ms
Downstream/Upstream Jitter1ms4ms
Downstream Packet Loss0%1%
Wifi Channel5GHz/6GHz5GHz
Wifi Channel Width80Mhz40Mhz
+

For optimal performance, use a dedicated network setup:

+
[CloudXR Server] ←→ [Router] ←→ [Client Device]
(Ethernet) (WiFi 6) (Meta Quest 3) +
+ +

Key considerations:

+
    +
  • Use wired connection for the server
  • +
  • Dedicate WiFi channel for the client device
  • +
  • Minimize network hops between server and client
  • +
  • Avoid network congestion from other devices
  • +
+
    +
  • Frequency Band: 5GHz or 6GHz (avoid 2.4GHz)
  • +
  • Channel Width: 80MHz or 160MHz
  • +
  • Security: WPA3 (WPA2 acceptable)
  • +
  • Channel Selection: Use non-overlapping channels
  • +
+
    +
  1. Enable 5GHz/6GHz bands with separate SSIDs
  2. +
  3. Disable 2.4GHz if possible to avoid interference
  4. +
  5. Set fixed channels instead of auto-selection
  6. +
  7. Enable QoS for traffic prioritization
  8. +
  9. Disable band steering to prevent automatic switching
  10. +
+

The CloudXR Runtime attempts to open the required ports on the workstation firewall when started. This requires users to respond to an elevated prompt. If this is not possible, you may need to manually configure ports or disable the firewall entirely. Similarly, the WiFi network should also be configured to allow traffic on these ports.

+

Here is the list of ports that must be open:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ServiceProtocolServer PortDescription
WebSocketTCP49100CloudXR Runtime signaling port
Video StreamUDP47998-48012CloudXR Runtime media port
WebSocket ProxyTCP48322Default wss:// proxy port (if using HTTPS)
+
# Allow CloudXR Runtime ports
netsh advfirewall firewall add rule name="CloudXR Signaling" dir=in action=allow protocol=TCP localport=49100
netsh advfirewall firewall add rule name="CloudXR Media" dir=in action=allow protocol=UDP localport=47998-48012

# Allow wss:// proxy port (if using HTTPS)
netsh advfirewall firewall add rule name="CloudXR wss:// Proxy" dir=in action=allow protocol=TCP localport=48322 +
+ +
# Allow CloudXR Runtime ports
sudo ufw allow 49100/tcp
sudo ufw allow 47998:48012/udp

# Allow wss:// proxy port (if using HTTPS)
sudo ufw allow 48322/tcp +
+ +
# Test bandwidth between server and client
iperf3 -s # On server
iperf3 -c <server-ip> # On client +
+ +
# Test latency
ping <server-ip> +
+ +

Use online tools like packetlosstest.com to test packet loss and jitter.

+
    +
  • Cause: Network congestion, poor WiFi signal, or server overload
  • +
  • Solutions: +
      +
    • Move closer to router
    • +
    • Use 5GHz instead of 2.4GHz
    • +
    • Close other bandwidth-intensive applications
    • +
    • Check server performance
    • +
    +
  • +
+
    +
  • Cause: WiFi interference, poor signal strength, or network congestion
  • +
  • Solutions: +
      +
    • Change WiFi channel
    • +
    • Reduce distance from router
    • +
    • Check for interference sources
    • +
    • Use wired connection for server
    • +
    +
  • +
+
    +
  • Cause: Network instability, server issues, or timeout
  • +
  • Solutions: +
      +
    • Check network stability
    • +
    • Verify server is running
    • +
    • Increase timeout values
    • +
    • Implement reconnection logic
    • +
    +
  • +
+
    +
  • Use WPA3 encryption for WiFi
  • +
  • Enable network isolation if needed
  • +
  • Use VPN for remote connections
  • +
  • Regularly update router firmware
  • +
+

When using HTTPS for your web application (for development or production), you need a WebSocket proxy with TLS support to establish secure connections. We provide example configurations for two common deployment scenarios.

+

Consider using a WebSocket proxy when:

+
    +
  • Hosting your web application using HTTPS (npm run dev-server:https)
  • +
  • Deploying to production environments with proper SSL certificates
  • +
  • Accessing CloudXR from remote networks or the internet
  • +
+

The proxy acts as a secure gateway, providing TLS termination for WebSocket connections between CloudXR.js clients and the CloudXR Runtime.

+

We provide two example proxy configurations to help you get started:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Deployment ScenarioExample SolutionSetup Complexity
Local development with HTTPNo proxy needed (direct ws:// connection)None
Development/testing with HTTPSDocker HAProxy exampleLow
Single-server productionDocker HAProxy exampleLow
Kubernetes productionnginx Ingress exampleMedium
Multi-server/enterprisenginx Ingress exampleMedium-High
+

This example demonstrates a lightweight WebSocket proxy using HAProxy in a Docker container. It automatically generates self-signed SSL certificates and works well for development and single-server deployments. You can deploy this on either WSL2 in Windows OS or on Linux directly.

+
+1. Create the Dockerfile (Dockerfile.wss.proxy) - Click to expand +
FROM haproxy:3.2

# Switch to root user for package installation
USER root

# Install necessary tools
RUN apt-get update && apt-get install -y \
bash \
gettext-base \
openssl \
&& rm -rf /var/lib/apt/lists/*

# Create directory for configuration
RUN mkdir -p /usr/local/etc/haproxy/certs \
&& chown -R haproxy:haproxy /usr/local/etc/haproxy

# Create simple certificate generation script
COPY <<EOF /usr/local/bin/generate-cert.sh
#!/bin/bash
cd /usr/local/etc/haproxy/certs
openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes -subj "/CN=localhost" -quiet
# Combine certificate and key into a single file for HAProxy
cat server.crt server.key > server.pem
chown haproxy:haproxy server.key server.crt server.pem
chmod 600 server.key server.pem
chmod 644 server.crt
EOF

RUN chmod +x /usr/local/bin/generate-cert.sh

# Create the HAProxy configuration template file
COPY --chown=haproxy:haproxy <<EOF /usr/local/etc/haproxy/haproxy.cfg.template
global
log stdout local0 info
stats timeout 30s
user haproxy

# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private

# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=3.2&config=intermediate
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

defaults
log global
option httplog
option dontlognull
option logasap
timeout connect 5s
timeout client 3600s
timeout server 3600s
# WebSocket tunnel timeout (keep connection alive)
timeout tunnel 3600s

frontend websocket_frontend
log global
bind *:\${PROXY_PORT} \${PROXY_SSL_BIND_OPTIONS}
mode http

# Log connection details
capture request header Host len 32
capture request header Upgrade len 32
capture request header Connection len 32

# Add CORS headers for all responses
http-response set-header Access-Control-Allow-Origin "*"
http-response set-header Access-Control-Allow-Headers "*"
http-response set-header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
http-response set-header Access-Control-Expose-Headers "*"

# Handle OPTIONS requests for CORS preflight
http-request return status 200 content-type "text/plain" string "OK" if METH_OPTIONS

default_backend websocket_backend

backend websocket_backend
log global
mode http

# WebSocket support - HAProxy automatically handles Upgrade header
# No special configuration needed for WebSocket protocol upgrade

# Health check configuration:
# - inter: time between checks
# - rise: successful checks to mark as UP
# - fall: failed checks to mark as DOWN
# - on-marked-down shutdown-sessions: close existing sessions when backend goes down
server local_websocket \${BACKEND_HOST}:\${BACKEND_PORT} check inter \${HEALTH_CHECK_INTERVAL} rise \${HEALTH_CHECK_RISE} fall \${HEALTH_CHECK_FALL} on-marked-down shutdown-sessions
EOF

# Create the entrypoint script
COPY <<EOF /entrypoint.sh
#!/bin/bash

# Use default BACKEND_HOST if not set
if [ -z "\${BACKEND_HOST:+x}" ]; then
export BACKEND_HOST=localhost
echo "BACKEND_HOST not set, using default: \${BACKEND_HOST}"
fi

# Use default BACKEND_PORT if not set
if [ -z "\${BACKEND_PORT:+x}" ]; then
export BACKEND_PORT=49100
echo "BACKEND_PORT not set, using default: \${BACKEND_PORT}"
fi

# Use default PROXY_PORT if not set
if [ -z "\${PROXY_PORT:+x}" ]; then
export PROXY_PORT=48322
echo "PROXY_PORT not set, using default: \${PROXY_PORT}"
fi

# Use default health check interval if not set
if [ -z "\${HEALTH_CHECK_INTERVAL:+x}" ]; then
export HEALTH_CHECK_INTERVAL=2s
echo "HEALTH_CHECK_INTERVAL not set, using default: \${HEALTH_CHECK_INTERVAL}"
fi

# Use default health check rise if not set
if [ -z "\${HEALTH_CHECK_RISE:+x}" ]; then
export HEALTH_CHECK_RISE=2
echo "HEALTH_CHECK_RISE not set, using default: \${HEALTH_CHECK_RISE}"
fi

# Use default health check fall if not set
if [ -z "\${HEALTH_CHECK_FALL:+x}" ]; then
export HEALTH_CHECK_FALL=3
echo "HEALTH_CHECK_FALL not set, using default: \${HEALTH_CHECK_FALL}"
fi

echo "Launching WebSocket SSL Proxy:"
echo " Backend Host: \${BACKEND_HOST}"
echo " Backend Port: \${BACKEND_PORT}"
echo " Proxy Port: \${PROXY_PORT}"
echo " Health Check Interval: \${HEALTH_CHECK_INTERVAL}"
echo " Health Check Rise: \${HEALTH_CHECK_RISE}"
echo " Health Check Fall: \${HEALTH_CHECK_FALL}"

# Generate self-signed SSL certificate
/usr/local/bin/generate-cert.sh
export PROXY_SSL_BIND_OPTIONS="ssl crt /usr/local/etc/haproxy/certs/server.pem"
echo "SSL enabled - self-signed certificate generated"

# Process the template and create the final config
envsubst < /usr/local/etc/haproxy/haproxy.cfg.template > /usr/local/etc/haproxy/haproxy.cfg

# Function to handle signals and forward them to HAProxy
handle_signal() {
echo "Received signal, shutting down HAProxy..."
if [ -n "\$HAPROXY_PID" ]; then
kill -TERM "\$HAPROXY_PID" 2>/dev/null
wait "\$HAPROXY_PID"
fi
exit 0
}

# Set up signal handlers
trap handle_signal SIGTERM SIGINT

# Start HAProxy in background and capture PID
echo "Starting HAProxy..."
haproxy -f /usr/local/etc/haproxy/haproxy.cfg &
HAPROXY_PID=\$!

# Wait for HAProxy process
wait "\$HAPROXY_PID"
EOF

RUN chmod +x /entrypoint.sh

# Switch back to haproxy user
USER haproxy

# Set the entrypoint
ENTRYPOINT ["/entrypoint.sh"] +
+ +
+
    +
  1. Build the Docker image:
  2. +
+
docker build -t websocket-ssl-proxy -f Dockerfile.wss.proxy .
+
+ +
    +
  1. Run the proxy container:
  2. +
+
docker run -d --name wss-proxy \
--network host \
-e BACKEND_HOST=localhost \
-e BACKEND_PORT=49100 \
-e PROXY_PORT=48322 \
websocket-ssl-proxy +
+ +
    +
  1. Verify the proxy is running:
  2. +
+
# Check container status
docker ps | grep wss-proxy

# View logs
docker logs wss-proxy +
+ +

You can customize the proxy behavior using these environment variables:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableDefaultDescription
BACKEND_HOSTlocalhostCloudXR Runtime hostname or IP address
BACKEND_PORT49100CloudXR Runtime WebSocket port
PROXY_PORT48322SSL proxy listening port
HEALTH_CHECK_INTERVAL2sTime between backend health checks
HEALTH_CHECK_RISE2Consecutive successful checks to mark backend UP
HEALTH_CHECK_FALL3Consecutive failed checks to mark backend DOWN
+

If you have your own SSL certificate, you can use it instead of the auto-generated self-signed certificate:

+
    +
  1. +

    Prepare certificate:

    +
      +
    • Combine your certificate and private key into a single PEM file:
    • +
    +
    cat your-cert.crt your-key.key > server.pem
    +
    + +
  2. +
  3. +

    Mount certificate into container:

    +
    docker run -d --name wss-proxy \
    --network host \
    -v /path/to/server.pem:/usr/local/etc/haproxy/certs/server.pem:ro \
    -e BACKEND_HOST=localhost \
    -e BACKEND_PORT=49100 \
    -e PROXY_PORT=48322 \
    websocket-ssl-proxy +
    + +
  4. +
+

The proxy continuously monitors the CloudXR Runtime backend:

+
    +
  • +

    Backend DOWN: Logs show Server websocket_backend/local_websocket is DOWN

    +
      +
    • Expected during CloudXR Runtime startup
    • +
    • Proxy automatically reconnects when runtime becomes available
    • +
    +
  • +
  • +

    Backend UP: Logs show Server websocket_backend/local_websocket is UP

    +
      +
    • Proxy is ready to accept client connections
    • +
    • WebSocket connections are forwarded to the runtime
    • +
    +
  • +
+

Stop the proxy:

+
docker stop wss-proxy
+
+ +

Start a stopped proxy:

+
docker start wss-proxy
+
+ +

Delete the proxy container:

+
docker stop wss-proxy
docker rm wss-proxy +
+ +
+

Important: Each time the container is created or restarted, a new self-signed certificate is generated unless you mount your own certificate (see Using Custom Certificates below). With auto-generated certificates, you will need to re-trust the certificate in your browser by visiting https://<server-ip>:48322/ and accepting the certificate warning. See the Client Setup Guide - Trust SSL Certificates for detailed instructions.

+
+

After starting the proxy, you can configure your CloudXR.js client to connect using:

+
wss://<server-ip>:48322
+
+ +

For client configuration instructions, see the Client Setup Guide - Trust SSL Certificates.

+

"Connection Refused" errors during startup:

+
    +
  • This is expected behavior during CloudXR Runtime initialization
  • +
  • The proxy will automatically connect when the runtime becomes ready
  • +
  • Monitor the logs to see when connection is established: docker logs -f wss-proxy
  • +
+

Certificate trust issues:

+
    +
  • Ensure your client browser has trusted the self-signed certificate
  • +
  • Follow the Client Setup Guide
  • +
  • For production deployments, consider using properly signed certificates
  • +
+

Firewall blocking connections:

+
    +
  • Verify that port 48322 (or your configured PROXY_PORT) is open:
    # Ubuntu/Debian example
    sudo ufw allow 48322/tcp +
    + +
  • +
+

This example demonstrates an enterprise-grade solution using nginx Ingress Controller on Kubernetes. This configuration supports multiple CloudXR servers, load balancing, and integration with existing Kubernetes infrastructure.

+

This example assumes you have:

+
    +
  • Kubernetes cluster with kubectl access
  • +
  • nginx Ingress Controller installed
  • +
  • Valid TLS certificate and key (tls.crt and tls.key)
  • +
  • Familiarity with Kubernetes resource management
  • +
+
kubectl create secret tls my-tls --cert=tls.crt --key=tls.key
+
+ +

The nginx proxy configuration example below handles WebSocket connections by +routing /{IP}:{PORT}/{path} to target CloudXR servers.

+

ConfigMap:

+
apiVersion: v1
kind: ConfigMap
...
data:
nginx.conf: |
...
http {
...
server {
...
location = /test {
return 200 'WebSocket proxy ready\n';
}
location ~ ^/([0-9.]+)(?::[0-9]+)?(.*)$ {
set $target_ip $1;
set $target_port 49100;
set $request_path $2;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_pass http://$target_ip:$target_port$request_path;
}
}
}
... +
+ +

The Ingress resource exposes the proxy service externally with:

+
    +
  • TLS Termination: Handles HTTPS encryption/decryption
  • +
  • WebSocket Support: Special annotations for WebSocket protocol handling
  • +
  • Path Routing: Routes /* requests to the nginx proxy service
  • +
  • SSL Redirect: Automatically redirects HTTP to HTTPS
  • +
+
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/websocket-services: "..."
...
spec:
rules:
- host: <https-proxy>
http:
paths:
- backend:
service:
name: <nginx-service>
port:
number: <nginx-port>
path: /
pathType: Prefix
tls:
- hosts:
- <https-proxy>
secretName: my-tls +
+ +

Once deployed, you can test via

+
curl -k https://<https-proxy>/test
+
+ +

Refer to the Getting Started Guide and checkout the examples +we provide to see how the proxy is used. +You could run the example web server on HTTPS and then fill in the proxy URL.

+

The secure WebSocket connection format in the console log will become:

+
wss://{https-proxy}/{cloudxr-server-ip}:{port}/{optional-path}
+
+ +
diff --git a/deps/cloudxr/docs/documents/Overview.html b/deps/cloudxr/docs/documents/Overview.html new file mode 100644 index 0000000..9181a96 --- /dev/null +++ b/deps/cloudxr/docs/documents/Overview.html @@ -0,0 +1,32 @@ +Overview | CloudXR.js SDK Documentation - v6.0.0-beta

NVIDIA CloudXR.js

CloudXR.js is a JavaScript Client SDK that enables developers to build enterprise web applications for streaming high-performance VR and AR content from the CloudXR Runtime. Built on NVIDIA's CloudXR technology, this library provides seamless integration between web browsers and remote rendering systems, allowing users to experience immersive 3D applications directly in their browser.

+

The SDK offers a complete solution for WebXR streaming, featuring automatic session management, optimized network protocols, and cross-platform compatibility. You can create rich VR/AR experiences that leverage the power of remote servers while maintaining the accessibility and ease of deployment that web applications provide. While we provide examples for WebGL and React Three Fiber implementations, CloudXR.js is a generic solution that can be integrated with any WebXR-compatible frameworks, making it suitable for a wide range of use cases from simple 3D visualizations to complex interactive applications.

+
+

Note: This is an early access beta release of CloudXR.js. Features and APIs may change in future releases.

+
+

CloudXR.js works with any OpenXR-compatible application running on a local or remote server with the CloudXR Runtime, including:

+
    +
  • NVIDIA Isaac Lab - A unified and modular framework for robot learning that aims to simplify common workflows in robotics research.
  • +
  • OpenXR applications - Any OpenXR-compatible application running with the CloudXR Runtime.
  • +
+

Our current release is optimized for Meta Quest 3.

+

We provide several web examples to integrate with CloudXR.js. Please follow our Getting Started Guide.

+

Learn how to configure your client device and web application hosting. Includes browser setup for Meta Quest 3, HTTP vs HTTPS hosting modes, and connection architecture. See Client Setup Guide for configuration examples and best practices.

+

Learn how to manage CloudXR streaming sessions, including connection lifecycle, configuration options, and event handling. See Session API Guide for comprehensive API documentation.

+

Configure your network for optimal CloudXR streaming performance. We provide example configurations for firewall rules, WiFi optimization, and WebSocket proxy deployments. See Networking Setup Guide for sample configurations and recommendations.

+

Resolve common issues with CloudXR.js applications, including connection problems, streaming issues, and performance optimization. See Troubleshooting Guide for solutions to common problems.

+

CloudXR.js incorporates third-party open source libraries. The following dependencies require attention for license compliance:

+ +
    +
  • gl-matrix - Mathematics library for graphics programming. Licensed under MIT License.
  • +
  • long.js - A Long class for representing a 64-bit two's-complement integer value. Used for accurate handling of uint64 fields in protobuf messages. Licensed under Apache License 2.0.
  • +
+

For a complete list of all dependencies and their licenses, refer to the package.json file in the root directory.

+

This project is licensed under the NVIDIA CloudXR License.

+
+

Important: This is an evaluation license for internal test and evaluation purposes only. For commercial use, please contact NVIDIA for appropriate licensing terms.

+

Please review the license terms before using this SDK in your projects.

+
+
diff --git a/deps/cloudxr/docs/documents/Session_API.html b/deps/cloudxr/docs/documents/Session_API.html new file mode 100644 index 0000000..79d9a2e --- /dev/null +++ b/deps/cloudxr/docs/documents/Session_API.html @@ -0,0 +1,92 @@ +Session API | CloudXR.js SDK Documentation - v6.0.0-beta

Session API

The CloudXR.js Session API provides comprehensive management of streaming sessions with the CloudXR Runtime. This guide covers the core session lifecycle, configuration, and key operations.

+

CloudXR.js is a JavaScript Client SDK that enables WebXR streaming from the CloudXR Runtime. It requires compatible server applications such as Isaac Lab, or any OpenXR-compatible application running with the CloudXR Runtime.

+

The CloudXR session follows a well-defined lifecycle with distinct states and transitions:

+
InitializedConnectingConnectedDisconnectingDisconnected
+
+ + +

Full API reference: createSession

+
import { createSession } from '@nvidia/cloudxr';

const session = createSession({
serverAddress: 'your-server',
serverPort: 49100,
useSecureConnection: false,
gl: webglContext,
perEyeWidth: 2048,
perEyeHeight: 1792,
referenceSpace: xrReferenceSpace,
// Additional configuration options
}); +
+ +

Note: The perEyeWidth and perEyeHeight parameters specify the resolution for each eye. The actual stream resolution will be calculated automatically:

+
    +
  • Stream width = perEyeWidth * 2
  • +
  • Stream height = perEyeHeight * 9 / 4
  • +
+

See SessionOptions for all available configuration options.

+

Full API reference: Session

+
// Connect throws an error if connection cannot be initiated
try {
session.connect();
console.log('Session connection initiated');
// Connection state changes will be reported via delegates
} catch (error) {
console.error('Failed to initiate connection:', error.message);
} +
+ +
// Disconnect from the server
session.disconnect(); +
+ +

The session will transition to SessionState.Disconnecting state immediately. +Then to SessionState.Disconnected state when the disconnection is complete.

+
+

Note: CloudXR.js does not currently support pause/resume functionality. To temporarily stop streaming, disconnect the session and reconnect when needed.

+
+
// Send viewer pose and input tracking to server
const success = session.sendTrackingStateToServer(timestamp, xrFrame);
if (!success) {
console.warn('Failed to send tracking state');
} +
+ +
// Render a frame from CloudXR Runtime
session.render(timestamp, xrFrame, xrWebGLLayer); +
+ +
// Send custom message to server
try {
session.sendServerMessage(messageObject);
console.log('Message sent successfully');
} catch (error) {
console.error('Failed to send server message:', error.message);
} +
+ +

CloudXR uses a delegate-based event system. Pass delegates when creating the session:

+
const sessionDelegates = {
onStreamStarted: () => {
console.log('Streaming started');
},

onStreamStopped: (error) => {
if (error) {
console.log('Streaming stopped due to error:', error);
} else {
console.log('Streaming stopped normally');
}
},

onWebGLStateChangeBegin: () => {
// Called before session changes WebGL state
console.log('WebGL state change beginning');
},

onWebGLStateChangeEnd: () => {
// Called after session changes WebGL state
console.log('WebGL state change complete');
}
};

const session = createSession(sessionOptions, sessionDelegates); +
+ +

See SessionDelegates for more details.

+
// Check current session state
console.log('Current state:', session.state); +
+ +
// Request WebXR session
const xrSession = await navigator.xr.requestSession('immersive-vr', {
requiredFeatures: ['local-floor']
});

// Get WebGL context and reference space
const gl = xrSession.renderState.baseLayer.context;
const referenceSpace = await xrSession.requestReferenceSpace('local-floor');

// Create CloudXR session
const sessionOptions = {
serverAddress: 'your-server',
serverPort: 49100,
useSecureConnection: false,
gl: gl,
perEyeWidth: 2048,
perEyeHeight: 1792,
referenceSpace: referenceSpace,
deviceFrameRate: 90,
maxStreamingBitrateKbps: 150000
};

const sessionDelegates = {
onStreamStarted: () => console.log('CloudXR streaming started'),
onStreamStopped: (error) => console.log('CloudXR streaming stopped', error)
};

const cloudxrSession = createSession(sessionOptions, sessionDelegates);

// Connect to CloudXR Runtime
try {
cloudxrSession.connect();
console.log('CloudXR connection initiated');
} catch (error) {
console.error('Failed to initiate CloudXR connection:', error.message);
}

// Begin rendering loop
function renderFrame(time, frame) {
// Send tracking state to server
cloudxrSession.sendTrackingStateToServer(time, frame);

// Render CloudXR frame
cloudxrSession.render(time, frame, xrSession.renderState.baseLayer);

// Continue rendering
xrSession.requestAnimationFrame(renderFrame);
}

xrSession.requestAnimationFrame(renderFrame); +
+ +

CloudXR automatically handles controller input and hand tracking through the WebXR session. The tracking data is sent to the server via sendTrackingStateToServer():

+
// Controllers and hand tracking are automatically handled
// when you call sendTrackingStateToServer with the XR frame
function renderFrame(time, frame) {
// This automatically includes controller and hand tracking data
cloudxrSession.sendTrackingStateToServer(time, frame);

// Render the frame
cloudxrSession.render(time, frame, xrWebGLLayer);
} +
+ +

The server will receive all input data including:

+
    +
  • Controller button presses and triggers
  • +
  • Controller pose and orientation
  • +
  • Hand tracking data (if supported by the device)
  • +
  • Head tracking and viewer pose
  • +
+
const sessionDelegates = {
onStreamStarted: () => {
console.log('Streaming started successfully');
},

onStreamStopped: (error) => {
if (error) {
console.error('Streaming stopped due to error:', error);
// Handle specific error types
if (error.message.includes('connection')) {
console.error('Connection error - check network and server');
}
} else {
console.log('Streaming stopped normally');
}
}
}; +
+ +
// Connect throws an error if connection cannot be initiated
try {
session.connect();
} catch (error) {
console.error('Failed to initiate connection:', error.message);
}

// Send tracking state returns false if not connected
if (!session.sendTrackingStateToServer(timestamp, frame)) {
console.warn('Cannot send tracking state - session not connected');
}

// Send server message throws an error if not connected or invalid message
try {
session.sendServerMessage(message);
} catch (error) {
console.error('Failed to send server message:', error.message);
} +
+ +

For comprehensive WebXR performance monitoring and debugging tools, refer to the Meta Horizon WebXR Performance Tools documentation.

+
    +
  • WebXR Performance Guide: MDN WebXR Performance Guide - Comprehensive performance optimization strategies
  • +
  • Immersive Web Emulator: immersiveweb.dev - Browser extension for testing WebXR performance on various devices
  • +
+

CloudXR.js requires a compatible server running:

+
    +
  • CloudXR Runtime - The streaming server component
  • +
  • OpenXR-compatible application - Such as: +
      +
    • Isaac Lab simulations
    • +
    • Custom OpenXR applications
    • +
    • Unity/Unreal Engine applications with OpenXR support
    • +
    +
  • +
+

The server must be accessible via WebSocket connection and properly configured for CloudXR streaming.

+

See our Getting Started Guide for more details.

+
diff --git a/deps/cloudxr/docs/documents/Show_Cases.Isaac_Lab_Teleop.Isaac_Lab_Teleop_Troubleshooting.html b/deps/cloudxr/docs/documents/Show_Cases.Isaac_Lab_Teleop.Isaac_Lab_Teleop_Troubleshooting.html new file mode 100644 index 0000000..52acc55 --- /dev/null +++ b/deps/cloudxr/docs/documents/Show_Cases.Isaac_Lab_Teleop.Isaac_Lab_Teleop_Troubleshooting.html @@ -0,0 +1,20 @@ +Isaac Lab Teleop Troubleshooting | CloudXR.js SDK Documentation - v6.0.0-beta

Troubleshooting Isaac Lab Teleop using CloudXR.js

When running the Docker container, also set the environment variable NV_GPU_INDEX for the runtime container to 0, 1, 2, etc., so the GPU index selected by CUDA matches the one selected by the host or other container.

+

This is a known limitation in Meta Quest 3, and we do not have a workaround to disable this gesture. We advise avoiding moving objects with this gesture.

+

This usually happens if the streaming session does not start correctly. Please quit the WebXR session and refresh the page.

+

If you are using remote debugging view, the Meta Quest 3 browser will not show the DOM overlay of the webpage unless you quit the entire WebXR session.

+

In our upcoming release, we will provide ways to build immersive UIs so you do not need to rely on the webpage. The current workaround is to avoid using remote debugging.

+

In the CloudXR Client web page in the Meta Quest 3 browser, ensure that the Preferred Reference Space is set to local and adjust the offsets as recommended.

+

There might be different factors causing the issue. For example, if you run streaming for a long time, heat throttling can reduce performance and frame rate. +In this case, please power cycle your headset and give it time to cool down first. Other issues include networking, which can be improved by having less congested +network environments, and low battery. Please ensure your headset is charged to 50% or above.

+

See the Generic Troubleshooting Guide.

+

If you have encountered more issues, please reach out to NVIDIA CloudXR team and share the following files

+
# Checks filenames of the logs
docker exec -it cloudxr-runtime ls /tmp

# Copy the logs and ETLI files
docker cp cloudxr-runtime:/tmp/xxx/cxr_server.xxx.log .
docker cp cloudxr-runtime:/tmp/xxx/xxx.etli . +
+ +

Isaac Lab console logs can also be helpful.

+

Known Limitations

    +
  • Re-connection may require page refresh
  • +
  • Meta Quest 3 home gesture cannot be disabled and may interfere with hand tracking
  • +
+
diff --git a/deps/cloudxr/docs/documents/Show_Cases.Isaac_Lab_Teleop.Setting_Up_Server_for_Teleoperation.html b/deps/cloudxr/docs/documents/Show_Cases.Isaac_Lab_Teleop.Setting_Up_Server_for_Teleoperation.html new file mode 100644 index 0000000..4181eee --- /dev/null +++ b/deps/cloudxr/docs/documents/Show_Cases.Isaac_Lab_Teleop.Setting_Up_Server_for_Teleoperation.html @@ -0,0 +1,116 @@ +Setting Up Server for Teleoperation | CloudXR.js SDK Documentation - v6.0.0-beta

Running Isaac Lab with the CloudXR Runtime

+

Note: For this early access release, make sure you have a valid subscription to the NVIDIA CloudXR Early Access program on NVIDIA NGC.

+
+
    +
  1. Follow NGC API Key doc to get your +NGC API Key (nvapi-xxxx) for your NGC Organization. +
      +
    • The key should have granted permission to access NGC Catalog
    • +
    • Save the key to a safe place as it will only appear once
    • +
    +
  2. +
  3. Setup credential for docker (use username $oauthtoken as literal)
    docker login nvcr.io
    Username: $oauthtoken
    Password: <your-ngc-api-key> +
    + +
  4. +
  5. Try to pull docker image and it should succeed
    docker pull nvcr.io/nvidia/cloudxr-runtime-early-access:6.0.1-webrtc
    +
    + +
  6. +
+

On your Isaac Lab workstation:

+
    +
  1. +

    From the root of the Isaac Lab repository, update ./docker/.env.cloudxr-runtime to the following version tag

    +
    # NVIDIA CloudXR Runtime base image
    CLOUDXR_RUNTIME_BASE_IMAGE_ARG=nvcr.io/nvidia/cloudxr-runtime-early-access
    # NVIDIA CloudXR Runtime version to use
    CLOUDXR_RUNTIME_VERSION_ARG=6.0.1-webrtc +
    + +
  2. +
  3. +

    Also update ./docker/docker-compose.cloudxr-runtime.patch.yaml to use host network instead:

    +
    services:
    cloudxr-runtime:
    image: ${CLOUDXR_RUNTIME_BASE_IMAGE_ARG}:${CLOUDXR_RUNTIME_VERSION_ARG}
    network_mode: host
    #ports:
    # - "48010:48010/tcp" # signaling
    # - "47998:47998/udp" # media
    # - "47999:47999/udp" # media
    # - "48000:48000/udp" # media
    # - "48005:48005/udp" # media
    # - "48008:48008/udp" # media
    # - "48012:48012/udp" # media
    healthcheck:
    ...
    ... +
    + +
  4. +
  5. +

    Start the Isaac Lab and CloudXR Runtime containers using the Isaac Lab container.py script

    +
    ./docker/container.py start \
    --files docker-compose.cloudxr-runtime.patch.yaml \
    --env-file .env.cloudxr-runtime +
    + +

    If prompted, elect to activate X11 forwarding, which is necessary to see the Isaac Lab UI.

    +
    +

    Note: The container.py script is a thin wrapper around Docker Compose. The additional --files and --env-file arguments augment the base Docker Compose configuration to additionally run the CloudXR Runtime. +For more details on container.py and running Isaac Lab with Docker Compose, see the Docker Guide.

    +
    +
  6. +
  7. +

    Enter the Isaac Lab base container with:

    +
    ./docker/container.py enter base
    +
    + +

    From within the Isaac Lab base container, you can run Isaac Lab scripts that use XR.

    +
  8. +
  9. +

    Run an example teleop task with:

    +
    ./isaaclab.sh -p scripts/environments/teleoperation/teleop_se3_agent.py \
    --task Isaac-PickPlace-Locomanipulation-G1-Abs-v0 \
    --teleop_device handtracking \
    --enable_pinocchio \
    --info +
    + +
    +

    Note: You could also choose a different environment like Isaac-PickPlace-GR1T2-Abs-v0.

    +
    +
  10. +
  11. +

    You'll want to leave the container running for the next steps. But once you are finished, you can stop the containers with:

    +
    ./docker/container.py stop \
    --files docker-compose.cloudxr-runtime.patch.yaml \
    --env-file .env.cloudxr-runtime +
    + +
  12. +
+

You can also run Isaac Lab as a local process that connects to the CloudXR Runtime Docker container. +This method requires you to manually specify a shared directory for communication between the Isaac Lab instance and the CloudXR Runtime.

+

On your Isaac Lab workstation:

+
    +
  1. From the root of the Isaac Lab repository, create a local folder for temporary cache files:
  2. +
+
mkdir -p $(pwd)/openxr
+
+ +
    +
  1. Initiate the CloudXR Runtime Docker container, ensuring the previously created directory is mounted to the /openxr directory within the container for Isaac Lab visibility:
  2. +
+
docker run -dit --rm --name cloudxr-runtime \
--user $(id -u):$(id -g) \
--gpus=all \
-e "ACCEPT_EULA=Y" \
--mount type=bind,src=$(pwd)/openxr,dst=/openxr \
--network host \
nvcr.io/nvidia/cloudxr-runtime-early-access:6.0.1-webrtc +
+ +
+

If you choose a particular GPU instead of all, you need to make sure Isaac Lab also runs on that GPU. See Isaac Lab Teleop Troubleshooting how to do so.

+
+
    +
  1. In a new terminal where you intend to run Isaac Lab, export the following environment variables, which reference the directory created above:
  2. +
+
export XDG_RUNTIME_DIR=$(pwd)/openxr/run
export XR_RUNTIME_JSON=$(pwd)/openxr/share/openxr/1/openxr_cloudxr.json +
+ +
    +
  1. Run the example teleop task with:
  2. +
+
./isaaclab.sh -p scripts/environments/teleoperation/teleop_se3_agent.py \
--task Isaac-PickPlace-GR1T2-Abs-v0 \
--teleop_device handtracking \
--enable_pinocchio \
--info +
+ +

With Isaac Lab and the CloudXR Runtime running:

+
    +
  1. In the Isaac Lab UI: locate the Panel named AR.
  2. +
+

Isaac Lab UI: AR Panel

+
    +
  1. Click Start AR
  2. +
+

Start AR

+

The Viewport should show two eyes being rendered, and you should see the status "AR profile is active".

+

Isaac Lab viewport rendering two eyes

+
+

Above instructions are modified from these instructions, +in particular, to provide the CloudXR Runtime with environment variables needed for streaming to the Meta Quest 3 Browser.

+
+

Teleoperating on Meta Quest 3

+

Running Isaac Lab Teleoperation on Meta Quest 3

+
diff --git a/deps/cloudxr/docs/documents/Show_Cases.Isaac_Lab_Teleop.Teleoperating_on_Meta_Quest_3.html b/deps/cloudxr/docs/documents/Show_Cases.Isaac_Lab_Teleop.Teleoperating_on_Meta_Quest_3.html new file mode 100644 index 0000000..a2cd599 --- /dev/null +++ b/deps/cloudxr/docs/documents/Show_Cases.Isaac_Lab_Teleop.Teleoperating_on_Meta_Quest_3.html @@ -0,0 +1,192 @@ +Teleoperating on Meta Quest 3 | CloudXR.js SDK Documentation - v6.0.0-beta

Teleoperating on Meta Quest 3

Before building and hosting the web client, ensure you have the following installed:

+
    +
  • Node.js (v20.19.0 or later) and npm + +
  • +
+

Once you have Isaac Lab and the CloudXR Runtime running, you can build and host the web server for the CloudXR.js client application.

+
cd isaac
+
+ +
# For this early access release, install the SDK from the provided tarball
# This step will not be required when the SDK is publicly accessible
npm install /path/to/nvidia-cloudxr-6.0.0-beta.tgz

# Install remaining dependencies
npm install +
+ +

Select the appropriate server mode based on your deployment requirements:

+

For local development (HTTP):

+
npm run dev-server
+
+ +
    +
  • Local: http://localhost:8080/
  • +
  • Network: http://<server-ip>:8080/
  • +
  • Supports both ws:// (direct) and wss:// (proxied) runtime connections
  • +
+

For local development or production (HTTPS):

+
npm run dev-server:https
+
+ +
    +
  • Local: https://localhost:8080/
  • +
  • Network: https://<server-ip>:8080/
  • +
  • Generates self-signed certificates for development, use custom certificates for production
  • +
  • See Client Setup - Trust SSL Certificates for trusting certs in your device
  • +
+
+

Note: When using HTTPS mode, you must configure a WebSocket proxy. Browsers enforce mixed content policies that block insecure ws:// connections from HTTPS pages. See Networking Setup - WebSocket Proxy for proxy configuration example.

+
+

For local development, our web app automatically loads IWER (Immersive Web Emulator Runtime) to emulate a Meta Quest 3 headset when no physical XR device is detected.

+
    +
  1. +

    Make sure you have:

    + +
  2. +
  3. +

    Ensure your CloudXR Runtime and OpenXR application are running

    +
  4. +
  5. +

    Navigate to the web application based on your chosen hosting mode:

    + +
  6. +
  7. +

    Make sure you see the following messaging, indicating IWER is loaded

    +

    +
  8. +
  9. +

    (HTTPS mode only) Fill in the Proxy URL if you have any, or click to accept the cert

    +

    +
  10. +
  11. +

    Click CONNECT

    +
  12. +
+

If successful, you should see the green CONNECT button change to CONNECT (STREAMING), and the streamed content from your server application will appear in your browser!

+

You could use the develop UI from IWER to emulate device position and input, for example:

+

+

Once you've validated the setup works locally, you can test with an actual Meta Quest 3 headset.

+
    +
  1. +

    Configure Browser: Follow the Client Setup Guide - Meta Quest 3 Browser Configuration to:

    +
      +
    • Enable WebXR for HTTP origins (if using npm run dev-server)
    • +
    • Trust SSL certificates (if using npm run dev-server:https)
    • +
    +
  2. +
  3. +

    Configure Network: Ensure required ports are open:

    +
      +
    • Web server: Port 8080 (TCP) or your configured port
    • +
    • CloudXR Runtime: Port 49100 (TCP) and 47998-48012 (UDP)
    • +
    • WebSocket proxy (if using HTTPS): Port 48322 (TCP) or your configured port
    • +
    • See Networking Setup - Firewall Configuration for details
    • +
    +
  4. +
+
    +
  1. +

    Navigate to the web application on your Meta Quest 3 Browser:

    +
      +
    • HTTP mode: http://<server-ip>:8080/
    • +
    • HTTPS mode: https://<server-ip>:8080/
    • +
    +
  2. +
  3. +

    Configure runtime connection based on your hosting mode:

    +

    If using HTTP client (npm run dev-server):

    +
      +
    • Direct: ws://<server-ip>:49100
    • +
    • Proxied: wss://<server-ip>:48322
    • +
    +

    If using HTTPS client (npm run dev-server:https):

    +
      +
    • Proxied: wss://<server-ip>:48322 (required)
    • +
    +
  4. +
  5. +

    Grant permissions:

    +
      +
    • Click CONNECT
    • +
    • Allow WebXR permissions when prompted
    • +
    +
  6. +
+

Before connecting, verify your Isaac Lab server is ready:

+
    +
  • CloudXR Runtime is running
  • +
  • Isaac Lab is running with the desired task
  • +
  • Isaac Lab is using System OpenXR Runtime
  • +
  • Isaac Lab is in AR mode (Start AR button pressed)
  • +
+

Configure the following application settings in the client web page's Debug Settings section:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingRecommended ValueNotes
Preferred Reference SpacelocalRequired for proper tracking
X Offset (cm)0Horizontal positioning
Y Offset (cm)-155Vertical positioning
Z Offset (cm)10Depth positioning
Teleoperation Countdown Duration (s)User preferenceTime before recording starts (optional)
+
+

Note: Offset values are task-dependent. Adjust based on robot height and workspace as needed.

+
+

Once streaming starts, you will see a window to your right with teleoperation controls:

+

Isaac Teleoperation Buttons

+
    +
  • Countdown - you can use the - (minus) and + (plus) buttons to adjust the countdown timer for starting teleoperation.
  • +
  • Play - this will start the countdown. Once countdown is finished, the teleoperation session recording starts.
  • +
  • Reset - this will both stop and reset the teleoperation session. After pressing this you can press Play to continue teleoperation (following the countdown timer).
  • +
  • Disconnect - this will exit the whole streaming and WebXR session
  • +
+

To start teleoperating, click Play. It will start counting down in seconds as configured.

+

Isaac Teleoperation Countdown

+

During the countdown, position your hands at the right place and wait for recording to start.

+

Whenever you complete a task, it will automatically start a new recording and generate the recording file.

+

Isaac Teleoperation Running

+

To stop teleoperating and reset the position of robot arm, click the Reset button.

+

Troubleshooting Isaac Lab Teleoperation using CloudXR.js

+

Running Isaac Lab with the CloudXR Runtime

+
diff --git a/deps/cloudxr/docs/documents/Show_Cases.Isaac_Lab_Teleop.html b/deps/cloudxr/docs/documents/Show_Cases.Isaac_Lab_Teleop.html new file mode 100644 index 0000000..b014fe8 --- /dev/null +++ b/deps/cloudxr/docs/documents/Show_Cases.Isaac_Lab_Teleop.html @@ -0,0 +1,33 @@ +Isaac Lab Teleop | CloudXR.js SDK Documentation - v6.0.0-beta

Running Isaac Lab Teleoperation on Meta Quest 3

NVIDIA CloudXR enables seamless, high-fidelity immersive streaming to extended reality (XR) devices over the network. Developers can use CloudXR with NVIDIA Isaac Lab to build teleoperation workflows that require immersive XR rendering for increased spatial acuity and hand tracking for teleoperation of dexterous robots.

+

In these workflows, a compatible XR system captures operator head and hand motion. CloudXR sends this data to Isaac Lab, which renders and submits stereo views of the robot simulation to CloudXR. CloudXR then encodes and streams the rendered views back to the XR system for display in realtime using a low-latency, GPU-accelerated pipeline.

+

This guide provides the Isaac Lab-specific details referenced in the First Run Guide, including installation, CloudXR Runtime configuration, and teleoperation settings.

+

Prior to using CloudXR with Isaac Lab, please review the following system requirements:

+ +

Meta Quest 3 (OS version 79 or later).

+
    +
  • A strong wireless connection is essential for a high-quality streaming experience. Refer to the requirements of Networking Setup for more details.
  • +
  • We recommend using a dedicated router, as concurrent usage will degrade quality
  • +
  • The Meta Quest 3 and Isaac Lab workstation must be IP-reachable from one another +
      +
    • Many institutional wireless networks will prevent devices from reaching each other, resulting in the Meta Quest 3 being unable to find the Isaac Lab workstation on the network
    • +
    • See Networking Setup guide for more details
    • +
    +
  • +
+
    +
  1. Running Isaac Lab with the CloudXR Runtime
  2. +
  3. Running Teleop Client on Meta Quest 3
  4. +
+

If you encounter issues, please see Troubleshooting.

+
diff --git a/deps/cloudxr/docs/documents/Show_Cases.html b/deps/cloudxr/docs/documents/Show_Cases.html new file mode 100644 index 0000000..d7c346e --- /dev/null +++ b/deps/cloudxr/docs/documents/Show_Cases.html @@ -0,0 +1,4 @@ +Show Cases | CloudXR.js SDK Documentation - v6.0.0-beta
diff --git a/deps/cloudxr/docs/documents/Telemetry.html b/deps/cloudxr/docs/documents/Telemetry.html new file mode 100644 index 0000000..540a904 --- /dev/null +++ b/deps/cloudxr/docs/documents/Telemetry.html @@ -0,0 +1,40 @@ +Telemetry | CloudXR.js SDK Documentation - v6.0.0-beta

Telemetry

Anonymous collection of system performance data to enhance service quality.

+

We collect the following functional events:

+
    +
  • +

    CXR_LifetimeEvent: Tracks events when a CloudXRSession is created and destroyed.

    +
  • +
  • +

    CXR_SessionConfiguration: Gathers session initialization metadata such as resolution used to initialize a CloudXRSession.

    +
  • +
  • +

    CXR_SessionState: Monitors changes in the SessionState.

    +
  • +
  • +

    CXR_ExceptionInfo: Records exceptions, occurring within a CloudXRSession.

    +
  • +
  • +

    CXR_ClientRequest: Records the action when the client sends a message to server.

    +
  • +
  • +

    CXR_ServerResponse: Records the action when the server responds to client requests.

    +
  • +
  • +

    CXR_ClientMetricEvent: Records performance metrics for CloudXRSession, such as session duration, frame counts, and message statistics.

    +
  • +
+

Data is collected exclusively for service purposes, without capturing any persistent IDs or personal information, and it is not associated with any specific user or device. To opt out of telemetry data collection, disable telemetry in the session configuration.

+

Telemetry can be configured when creating a CloudXR session:

+
import * as CloudXR from '@nvidia/cloudxr';

const session = CloudXR.createSession({
// ... other session options
telemetry: {
enabled: true, // Enable telemetry collection (default: true)
appInfo: {
product: 'My CloudXR App',
version: '1.0.0'
}
}
}); +
+ +
    +
  • enabled: Boolean flag to enable/disable telemetry collection. Defaults to true.
  • +
  • appInfo: Optional application information object containing: +
      +
    • product: Product name (e.g., "MyApp")
    • +
    • version: Application version (e.g., "1.0.0")
    • +
    +
  • +
+
diff --git a/deps/cloudxr/docs/documents/Troubleshooting.html b/deps/cloudxr/docs/documents/Troubleshooting.html new file mode 100644 index 0000000..0b387fc --- /dev/null +++ b/deps/cloudxr/docs/documents/Troubleshooting.html @@ -0,0 +1,56 @@ +Troubleshooting | CloudXR.js SDK Documentation - v6.0.0-beta

Troubleshooting

This guide helps you diagnose and resolve common issues when using CloudXR.js for WebXR streaming.

+

For HTTP Mode:

+

If you're using HTTP mode (http://), you need to configure the Chromium browser to allow WebXR usage from insecure origins:

+
    +
  • Open Meta Quest 3 Browser or desktop Google Chrome
  • +
  • Navigate to chrome://flags
  • +
  • Search for "insecure", and locate "Treat insecure origins as secure"
  • +
  • Enable the flag
  • +
  • Then in the text box below the flag, add your development web server +
      +
    • You must include the protocol (http) and the port (:8080)
    • +
    • http://your-ip-address:8080
    • +
    +
  • +
  • De-focus the text box by "clicking somewhere else"
  • +
  • A "Relaunch" message should appear at the bottom of the window
  • +
  • Click "Relaunch"
  • +
  • Verify the flags are set as intended in chrome://flags
  • +
+

For HTTPS Mode:

+

If you're using HTTPS mode (https://), you need to trust the SSL certificates:

+
    +
  • Navigate to your web server URL in the browser: https://your-ip-address:8080
  • +
  • Accept the certificate warning by clicking "Advanced" → "Proceed to [your-ip-address] (unsafe)"
  • +
  • If using a WebSocket proxy, also trust its certificate by visiting: https://your-proxy-ip:48322
  • +
+

See the Client Setup Guide for detailed SSL certificate configuration.

+

You could pause the session by clicking the MENU button and resume the WebXR session. +If the behavior persists, we advise to restart your Meta headset to clear up caches.

+

You might see an error with an error code (shown in hexadecimal format like 0xC0F22202). In most cases, they are because of the following reasons:

+
    +
  • Verify the server address and port are correct
  • +
  • Check network connectivity between client and server
  • +
  • Ensure the CloudXR runtime is running on the server
  • +
  • Verify firewall rules allow traffic
  • +
  • Ensure UDP ports are not blocked
  • +
  • Check for high network latency or packet loss
  • +
  • Verify stable network connection (check for Wi-Fi disconnections)
  • +
  • Test network quality (ping, bandwidth, packet loss)
  • +
  • Ensure WebRTC is not blocked by browser settings or extensions
  • +
  • Review network topology (double NAT, symmetric NAT issues)
  • +
  • Setup TURN relay server if direct connection fails
  • +
  • Check server configuration and logs
  • +
  • Restart the CloudXR runtime if necessary
  • +
+

For most issues, you can consult the Networking Setup Guide for detailed configuration instructions and the issue should resolve.

+

If you're still experiencing issues:

+
    +
  1. Check the console for error messages
  2. +
  3. Test with different browsers
  4. +
  5. Verify server configuration and logs
  6. +
  7. Check network conditions and requirements
  8. +
  9. Review the Session API documentation
  10. +
+

For additional support, please reach out to NVIDIA CloudXR team.

+
diff --git a/deps/cloudxr/docs/enums/CloudXR.SessionState.html b/deps/cloudxr/docs/enums/CloudXR.SessionState.html new file mode 100644 index 0000000..0ea9499 --- /dev/null +++ b/deps/cloudxr/docs/enums/CloudXR.SessionState.html @@ -0,0 +1,33 @@ +SessionState | CloudXR.js SDK Documentation - v6.0.0-beta

Defines the states of a CloudXR streaming session.

+

The session follows a well-defined lifecycle with distinct states and transitions:

+
InitializedConnectingConnectedDisconnectingDisconnected
+
+ +
// Check session state
if (session.state === SessionState.Connected) {
console.info('Session is ready for rendering');
} +
+ +

Enumeration Members

Initialized: "Initialized"

Initial state when the session is created but connect() has not been called.

+

The session is ready to be configured and connected. +No network activity or streaming occurs.

+
Connecting: "Connecting"

The session is attempting to establish a connection and start streaming.

+

The session is actively trying to connect to the CloudXR Runtime. +This state is temporary and transitions to either Connected or Error.

+
Connected: "Connected"

The session is fully active and ready to render.

+

Connection is established, video streaming is active, and WebGL resources are ready. +This is the normal operational state for rendering CloudXR content.

+
Disconnecting: "Disconnecting"

The session is in the process of disconnecting from the CloudXR Runtime.

+

The session is actively closing the connection and cleaning up resources. +This state is temporary and transitions to Disconnected.

+
Disconnected: "Disconnected"

The session has been disconnected and is ready to be reconnected.

+

The session has been cleanly disconnected and can be reconnected +by calling connect() again.

+
Error: "Error"

The session encountered a critical error and cannot continue.

+

The session has encountered an unrecoverable error. Destroy and recreate +the session to recover. Check error details in the onStreamStopped delegate callback.

+
diff --git a/deps/cloudxr/docs/functions/CloudXR.createSession.html b/deps/cloudxr/docs/functions/CloudXR.createSession.html new file mode 100644 index 0000000..69458fc --- /dev/null +++ b/deps/cloudxr/docs/functions/CloudXR.createSession.html @@ -0,0 +1,21 @@ +createSession | CloudXR.js SDK Documentation - v6.0.0-beta
  • Creates a new CloudXR streaming session with the provided configuration.

    +

    This is the primary entry point for creating CloudXR streaming sessions. +The returned session is ready to be connected to the CloudXR Runtime using +the connect() method.

    +

    Parameters

    • options: SessionOptions

      Configuration options for the session. Must include all +required parameters such as serverAddress, serverPort, +gl context, and per-eye dimensions.

      +
    • Optionaldelegates: SessionDelegates

      Optional delegate object to receive essential session +events such as onStreamStarted, onStreamStopped, and +WebGL state change notifications.

      +

    Returns Session

    A Session object ready to connect to the CloudXR Runtime

    +

    When perEyeWidth is not a positive number or not a multiple of 16

    +

    When perEyeHeight is not a positive number or not a multiple of 16

    +

    When derived stream dimensions are not multiples of 16

    +
    // Basic session creation
    const session = createSession({
    serverAddress: '192.168.1.100',
    serverPort: 49100,
    useSecureConnection: false,
    gl: webglContext,
    perEyeWidth: 2048,
    perEyeHeight: 1792,
    referenceSpace: xrReferenceSpace
    });

    // With event delegates
    const session = createSession(sessionOptions, {
    onStreamStarted: () => {
    console.info('CloudXR streaming started');
    },
    onStreamStopped: (error) => {
    if (error) {
    console.error('Streaming error:', error);
    } else {
    console.info('Streaming stopped normally');
    }
    }
    });

    // Connect to CloudXR Runtime
    if (session.connect()) {
    console.info('Connection initiated');
    } +
    + +
    // Complete WebXR integration example
    async function setupCloudXR() {
    // Request WebXR session
    const xrSession = await navigator.xr.requestSession('immersive-vr', {
    requiredFeatures: ['local-floor']
    });

    // Get WebGL context and reference space
    const gl = xrSession.renderState.baseLayer.context;
    const referenceSpace = await xrSession.requestReferenceSpace('local-floor');

    // Create CloudXR streaming session
    const session = createSession({
    serverAddress: 'your-server-ip',
    serverPort: 49100,
    useSecureConnection: false,
    gl: gl,
    perEyeWidth: 2048,
    perEyeHeight: 1792,
    referenceSpace: referenceSpace,
    deviceFrameRate: 90,
    maxStreamingBitrateKbps: 150000
    }, {
    onStreamStarted: () => console.info('Ready to render'),
    onStreamStopped: (error) => console.info('Streaming stopped', error)
    });

    // Connect and start rendering
    if (session.connect()) {
    function renderFrame(time, frame) {
    session.sendTrackingStateToServer(time, frame);
    session.render(time, frame, xrSession.renderState.baseLayer);
    xrSession.requestAnimationFrame(renderFrame);
    }
    xrSession.requestAnimationFrame(renderFrame);
    }
    } +
    + +
diff --git a/deps/cloudxr/docs/index.html b/deps/cloudxr/docs/index.html new file mode 100644 index 0000000..50b1f94 --- /dev/null +++ b/deps/cloudxr/docs/index.html @@ -0,0 +1,32 @@ +CloudXR.js SDK Documentation - v6.0.0-beta

CloudXR.js SDK Documentation - v6.0.0-beta

NVIDIA CloudXR.js

CloudXR.js is a JavaScript Client SDK that enables developers to build enterprise web applications for streaming high-performance VR and AR content from the CloudXR Runtime. Built on NVIDIA's CloudXR technology, this library provides seamless integration between web browsers and remote rendering systems, allowing users to experience immersive 3D applications directly in their browser.

+

The SDK offers a complete solution for WebXR streaming, featuring automatic session management, optimized network protocols, and cross-platform compatibility. You can create rich VR/AR experiences that leverage the power of remote servers while maintaining the accessibility and ease of deployment that web applications provide. While we provide examples for WebGL and React Three Fiber implementations, CloudXR.js is a generic solution that can be integrated with any WebXR-compatible frameworks, making it suitable for a wide range of use cases from simple 3D visualizations to complex interactive applications.

+
+

Note: This is an early access beta release of CloudXR.js. Features and APIs may change in future releases.

+
+

CloudXR.js works with any OpenXR-compatible application running on a local or remote server with the CloudXR Runtime, including:

+
    +
  • NVIDIA Isaac Lab - A unified and modular framework for robot learning that aims to simplify common workflows in robotics research.
  • +
  • OpenXR applications - Any OpenXR-compatible application running with the CloudXR Runtime.
  • +
+

Our current release is optimized for Meta Quest 3.

+

We provide several web examples to integrate with CloudXR.js. Please follow our Getting Started Guide.

+

Learn how to configure your client device and web application hosting. Includes browser setup for Meta Quest 3, HTTP vs HTTPS hosting modes, and connection architecture. See Client Setup Guide for configuration examples and best practices.

+

Learn how to manage CloudXR streaming sessions, including connection lifecycle, configuration options, and event handling. See Session API Guide for comprehensive API documentation.

+

Configure your network for optimal CloudXR streaming performance. We provide example configurations for firewall rules, WiFi optimization, and WebSocket proxy deployments. See Networking Setup Guide for sample configurations and recommendations.

+

Resolve common issues with CloudXR.js applications, including connection problems, streaming issues, and performance optimization. See Troubleshooting Guide for solutions to common problems.

+

CloudXR.js incorporates third-party open source libraries. The following dependencies require attention for license compliance:

+ +
    +
  • gl-matrix - Mathematics library for graphics programming. Licensed under MIT License.
  • +
  • long.js - A Long class for representing a 64-bit two's-complement integer value. Used for accurate handling of uint64 fields in protobuf messages. Licensed under Apache License 2.0.
  • +
+

For a complete list of all dependencies and their licenses, refer to the package.json file in the root directory.

+

This project is licensed under the NVIDIA CloudXR License.

+
+

Important: This is an evaluation license for internal test and evaluation purposes only. For commercial use, please contact NVIDIA for appropriate licensing terms.

+

Please review the license terms before using this SDK in your projects.

+
+
diff --git a/deps/cloudxr/docs/interfaces/CloudXR.Session.html b/deps/cloudxr/docs/interfaces/CloudXR.Session.html new file mode 100644 index 0000000..ddef67c --- /dev/null +++ b/deps/cloudxr/docs/interfaces/CloudXR.Session.html @@ -0,0 +1,70 @@ +Session | CloudXR.js SDK Documentation - v6.0.0-beta

Defines the interface for CloudXR streaming sessions.

+

Provides the core functionality for managing CloudXR streaming +sessions, including connection management, rendering, and communication with +the CloudXR Runtime.

+
// Create a session
const session = createSession(sessionOptions, delegates);

// Connect to CloudXR Runtime
if (session.connect()) {
console.info('Connection initiated');
}

// In your render loop
function renderFrame(time, frame) {
try {
// Send tracking data
session.sendTrackingStateToServer(time, frame);

// Render CloudXR content
session.render(time, frame, xrWebGLLayer);
} catch (error) {
console.error('Error during frame rendering:', error);
}
} +
+ +
interface Session {
    state: SessionState;
    connect(): void;
    disconnect(): void;
    sendTrackingStateToServer(timestamp: number, frame: XRFrame): boolean;
    render(timestamp: number, frame: XRFrame, layer: XRWebGLLayer): void;
    sendServerMessage(message: any): void;
}

Properties

Current state of the session.

+

This readonly property provides the current state of the session, +which can be used to determine what operations are available +and to monitor the session lifecycle.

+
// Check if session is ready for rendering
if (session.state === SessionState.Connected) {
// Safe to call render() and sendTrackingStateToServer()
}

// Monitor state changes
console.info('Session state:', session.state); +
+ +

Methods

  • Connect to the CloudXR server and start streaming.

    +

    Initiates connection to the CloudXR Runtime and transitions the session +to SessionState.Connecting, then SessionState.Connected once streaming is active.

    +

    Returns void

    If called when session is not in Initialized or Disconnected state

    +
    try {
    session.connect();
    console.info('Connection initiated');
    } catch (error) {
    console.error('Failed to initiate connection:', error.message);
    } +
    + +
  • Disconnects from the CloudXR Runtime and terminates any streams.

    +

    Gracefully disconnects from the CloudXR Runtime and cleans up resources. +The session transitions through the following states:

    +
      +
    1. SessionState.Disconnecting - Disconnection in progress
    2. +
    3. SessionState.Disconnected - Successfully disconnected
    4. +
    +

    After disconnection, the session can be reconnected by calling connect() again.

    +

    Returns void

    // Disconnect when done
    session.disconnect();
    console.info('Disconnection initiated');

    // Disconnect on user action
    document.getElementById('disconnect-btn').onclick = () => {
    session.disconnect();
    }; +
    + +
  • Sends the view pose and input tracking data to the CloudXR Runtime.

    +

    Sends the current viewer pose (head position/orientation) +and input tracking data (controllers, hand tracking) to the CloudXR +Runtime. This data is essential for the Runtime to render the correct +view and handle user input.

    +

    Parameters

    • timestamp: number

      The current timestamp (DOMHighResTimeStamp) from the XR frame

      +
    • frame: XRFrame

      The XR frame containing tracking data to send to the Runtime

      +

    Returns boolean

    True if the tracking data was sent successfully, false otherwise. +Returns false if the session is not in Connected state.

    +
    // In your WebXR render loop
    function renderFrame(time, frame) {
    try {
    // Send tracking data first
    if (!session.sendTrackingStateToServer(time, frame)) {
    console.warn('Failed to send tracking state');
    return;
    }

    // Then render the frame
    session.render(time, frame, xrWebGLLayer);
    } catch (error) {
    console.error('Error in render frame:', error);
    }
    }

    // Start the render loop
    xrSession.requestAnimationFrame(renderFrame); +
    + +
  • Renders a frame from CloudXR.

    +

    Renders the current frame received from the CloudXR Runtime +into the specified WebXR layer. Call this method every frame in your +WebXR render loop after sending tracking data.

    +

    Parameters

    • timestamp: number

      The current timestamp (DOMHighResTimeStamp) from the XR frame

      +
    • frame: XRFrame

      The XR frame to render

      +
    • layer: XRWebGLLayer

      The WebXR layer to render into (typically xrSession.renderState.baseLayer)

      +

    Returns void

    // Complete render loop
    function renderFrame(time, frame) {
    try {
    // Send tracking data
    session.sendTrackingStateToServer(time, frame);

    // Render CloudXR content
    session.render(time, frame, xrSession.renderState.baseLayer);
    } catch (error) {
    console.error('Error in render frame:', error);
    }

    // Continue the loop
    xrSession.requestAnimationFrame(renderFrame);
    }

    // Start rendering
    xrSession.requestAnimationFrame(renderFrame); +
    + +
  • Send a custom message to the CloudXR server.

    +

    Sends a custom JSON message to the server through the CloudXR protocol. +The message is serialized and sent via the streaming connection.

    +

    Parameters

    • message: any

      The message object to send to the server (must be a valid JSON object)

      +

    Returns void

    If session is not connected

    +

    If message is not a valid JSON object (null, primitive, or array)

    +
    try {
    const customMessage = {
    type: 'userAction',
    action: 'buttonPress',
    data: { buttonId: 1 }
    };
    session.sendServerMessage(customMessage);
    console.info('Message sent successfully');
    } catch (error) {
    console.error('Failed to send message:', error.message);
    } +
    + +
diff --git a/deps/cloudxr/docs/interfaces/CloudXR.SessionDelegates.html b/deps/cloudxr/docs/interfaces/CloudXR.SessionDelegates.html new file mode 100644 index 0000000..fc86dea --- /dev/null +++ b/deps/cloudxr/docs/interfaces/CloudXR.SessionDelegates.html @@ -0,0 +1,47 @@ +SessionDelegates | CloudXR.js SDK Documentation - v6.0.0-beta

Defines callbacks for CloudXR session events.

+

Applications implement these callbacks to receive notifications about +session lifecycle events and WebGL state changes. All callbacks are optional.

+
const delegates: SessionDelegates = {
onStreamStarted: () => {
console.info('CloudXR streaming started');
// Update UI to show streaming status
},
onStreamStopped: (error) => {
if (error) {
console.error('Streaming stopped due to error:', error);
} else {
console.info('Streaming stopped normally');
}
}
}; +
+ +
interface SessionDelegates {
    onStreamStarted?: () => void;
    onStreamStopped?: (error?: Error) => void;
    onWebGLStateChangeBegin?: () => void;
    onWebGLStateChangeEnd?: () => void;
    onServerMessageReceived?: (messageData: Uint8Array) => void;
}

Properties

onStreamStarted?: () => void

Invoked when streaming connects successfully.

+

Called when the session transitions to the Connected state +and streaming is ready to begin. At this point, the session is ready for +rendering and all WebGL resources have been initialized.

+
onStreamStarted: () => {
console.info('Ready to render CloudXR content');
// Start your render loop here
} +
+ +
onStreamStopped?: (error?: Error) => void

Invoked when streaming stops (either by client or server).

+

Called when the session stops streaming, either due to +a normal disconnection or an error condition. Check the error parameter +to determine the reason for the stop.

+

Type declaration

    • (error?: Error): void
    • Parameters

      • Optionalerror: Error

        Optional error object if streaming stopped due to an error. +If undefined, the stop was intentional (e.g., disconnect() called).

        +

      Returns void

onStreamStopped: (error) => {
if (error) {
console.error('Streaming error:', error.message);
// Handle error condition
} else {
console.info('Streaming stopped normally');
// Handle normal disconnection
}
} +
+ +
onWebGLStateChangeBegin?: () => void

Invoked before the session changes any WebGL state.

+

Called before the session modifies WebGL state during +rendering operations. Use this to save the current WebGL state if needed +for your application's rendering pipeline.

+
onWebGLStateChangeBegin: () => {
// Save current WebGL state
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
} +
+ +
onWebGLStateChangeEnd?: () => void

Invoked after the session changes any WebGL state.

+

Called after the session has completed its WebGL state +modifications during rendering operations. Use this to restore any WebGL +state that your application needs.

+
onWebGLStateChangeEnd: () => {
// Restore WebGL state
gl.bindFramebuffer(gl.FRAMEBUFFER, myFramebuffer);
} +
+ +
onServerMessageReceived?: (messageData: Uint8Array) => void

Invoked when a server message is received through any opaque data channel.

+

Type declaration

    • (messageData: Uint8Array): void
    • Parameters

      • messageData: Uint8Array

        Raw message data from the server

        +

      Returns void

onServerMessageReceived: (messageData) => {
const messageString = new TextDecoder().decode(messageData);
console.log('Received message:', messageString);

// Parse JSON if expected
try {
const message = JSON.parse(messageString);
console.log('Parsed message:', message);
} catch (error) {
console.log('Raw binary data:', messageData);
}
} +
+ +
diff --git a/deps/cloudxr/docs/interfaces/CloudXR.SessionOptions.html b/deps/cloudxr/docs/interfaces/CloudXR.SessionOptions.html new file mode 100644 index 0000000..7826bee --- /dev/null +++ b/deps/cloudxr/docs/interfaces/CloudXR.SessionOptions.html @@ -0,0 +1,131 @@ +SessionOptions | CloudXR.js SDK Documentation - v6.0.0-beta

Defines configuration options for a CloudXR streaming session.

+

Defines all configuration parameters needed to create +and configure a CloudXR streaming session. Required parameters must be +provided, while optional parameters have sensible defaults.

+
const sessionOptions: SessionOptions = {
// Required parameters
serverAddress: '192.168.1.100',
serverPort: 49100,
useSecureConnection: false,
gl: webglContext,
perEyeWidth: 2048,
perEyeHeight: 1792,
referenceSpace: xrReferenceSpace,

// Optional parameters with defaults
deviceFrameRate: 90,
maxStreamingBitrateKbps: 150000,
codec: 'av1'
}; +
+ +
interface SessionOptions {
    serverAddress: string;
    serverPort: number;
    useSecureConnection: boolean;
    gl: WebGL2RenderingContext;
    perEyeWidth: number;
    perEyeHeight: number;
    referenceSpace: XRReferenceSpace;
    glBinding?: XRWebGLBinding;
    codec?: string;
    deviceFrameRate?: number;
    maxStreamingBitrateKbps?: number;
    telemetry?: {
        enabled?: boolean;
        appInfo?: { version?: string; product?: string };
    };
    enablePoseSmoothing?: boolean;
    posePredictionFactor?: number;
}

Properties

serverAddress: string

Address of the CloudXR Runtime.

+

Can be an IP address (e.g., '192.168.1.100') or hostname. +For local development, use 'localhost' or '127.0.0.1'.

+
serverAddress: '192.168.1.100'  // IP address
serverAddress: 'cloudxr-server.local' // Hostname
serverAddress: 'localhost' // Local development +
+ +
serverPort: number

Port of the CloudXR Runtime.

+

The default CloudXR Runtime port is 49100. Ensure this port is +accessible and not blocked by firewalls.

+
49100
+
+ +
serverPort: 49100  // Default CloudXR port
+
+ +
useSecureConnection: boolean

Connect using secure connection (WSS/HTTPS).

+

When true, uses secure WebSocket (WSS) connection. When false, +uses unsecured WebSocket (WS) connection. For production deployments, +secure connections are recommended.

+
false
+
+ +
useSecureConnection: false  // Development
useSecureConnection: true // Production +
+ +
gl: WebGL2RenderingContext

WebGL context for rendering.

+

Must be a WebGL2RenderingContext obtained from a canvas element. +This context will be used for all CloudXR rendering operations.

+
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');
// Use gl in sessionOptions +
+ +
perEyeWidth: number

Width of each eye in pixels.

+

This should match the per-eye resolution you want to render. +Must be a multiple of 16 for optimal performance. +The actual stream width will be calculated as perEyeWidth * 2.

+
perEyeWidth: 2048  // Max width if using H264 codec
+
+ +
perEyeHeight: number

Height of each eye in pixels.

+

This should match the per-eye resolution you want to render. +Must be a multiple of 16 for optimal performance. +The actual stream height will be calculated as perEyeHeight * 9 / 4.

+
perEyeHeight: 1792  // Max height if using H264 codec
+
+ +
referenceSpace: XRReferenceSpace

XR reference space to use for coordinate system calculations.

+

This is used for getting the viewer pose from the XR frame and +should be obtained from the WebXR session.

+
const referenceSpace = await xrSession.requestReferenceSpace('local-floor');
+
+ +
glBinding?: XRWebGLBinding

XR WebGL binding used to query viewport information for each eye.

+

Optional binding that provides additional viewport information. +If not provided, default viewport calculations will be used.

+
const glBinding = new XRWebGLBinding(xrSession, gl);
+
+ +
codec?: string

Video codec for streaming.

+

Supported codecs: 'h264', 'av1'. AV1 provides better compression +but requires more CPU/GPU resources for encoding/decoding.

+
'av1'
+
+ +
codec: 'av1'   // Better compression, more CPU intensive
codec: 'h264' // Faster encoding/decoding, larger bandwidth +
+ +
deviceFrameRate?: number

Device frame rate (maximum FPS).

+

The server will treat this as a maximum FPS and choose an appropriate +streaming frame rate that is lower than this value. Higher frame rates +provide smoother motion but require more bandwidth.

+
90
+
+ +
deviceFrameRate: 90   // Quest 3 standard
deviceFrameRate: 120 // High refresh rate
deviceFrameRate: 72 // Lower power mode +
+ +
maxStreamingBitrateKbps?: number

Maximum streaming bitrate in Kilobits per second.

+

Controls the maximum bandwidth used for streaming. Higher bitrates +provide better quality but require more network bandwidth.

+
150000
+
+ +
maxStreamingBitrateKbps: 150000  // 150 Mbps
maxStreamingBitrateKbps: 100000 // 100 Mbps (lower bandwidth)
maxStreamingBitrateKbps: 200000 // 200 Mbps (high quality) +
+ +
telemetry?: {
    enabled?: boolean;
    appInfo?: { version?: string; product?: string };
}

Telemetry configuration options

+

Type declaration

  • Optionalenabled?: boolean

    Enable telemetry collection. Default is true.

    +
  • OptionalappInfo?: { version?: string; product?: string }

    Application information for telemetry

    +
    • Optionalversion?: string

      Application version (e.g., "1.0.0")

      +
    • Optionalproduct?: string

      Product name (e.g., "MyApp")

      +
enablePoseSmoothing?: boolean

Enable secondary smoothing on predicted positions.

+

When enabled, applies an additional smoothing pass to reduce jitter +in predicted positions. This only affects position, not orientation.

+
true
+
+ +
enablePoseSmoothing: false  // Disable position smoothing
+
+ +
posePredictionFactor?: number

Pose prediction factor (0.0 to 1.0) that scales the prediction horizon.

+

This multiplier is applied to the calculated prediction horizon for both +position and orientation. A value of 1.0 uses full prediction, 0.5 uses +half the prediction horizon, and 0.0 disables prediction entirely.

+
1.0
+
+ +
posePredictionFactor: 0.5  // Use 50% of calculated prediction
posePredictionFactor: 0.0 // Disable prediction +
+ +
diff --git a/deps/cloudxr/docs/media/HEROICONS_LICENSE b/deps/cloudxr/docs/media/HEROICONS_LICENSE new file mode 100644 index 0000000..d6a8229 --- /dev/null +++ b/deps/cloudxr/docs/media/HEROICONS_LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Tailwind Labs, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/deps/cloudxr/docs/media/LICENSE b/deps/cloudxr/docs/media/LICENSE new file mode 100644 index 0000000..5e53852 --- /dev/null +++ b/deps/cloudxr/docs/media/LICENSE @@ -0,0 +1,307 @@ +NVIDIA SOFTWARE EVALUATION LICENSE AGREEMENT + +IMPORTANT NOTICE – PLEASE READ AND AGREE BEFORE USING THE SOFTWARE + +This software evaluation license agreement (“Agreement”) is a legal agreement between you, whether an +individual or entity, (“you”) and NVIDIA Corporation and its affiliates (“NVIDIA”) and governs the use of certain +NVIDIA CloudXR software and documentation that NVIDIA delivers to you under this Agreement (“Software”). +NVIDIA and you are each a “party” and collectively the “parties.” +This Agreement can be accepted only by an adult of legal age of majority in the country in which the Software is +used. If you don’t have the required age or authority to accept this Agreement, or if you don’t accept all the +terms and conditions of this Agreement, do not use the Software. + +1. License Grants. + +1.1 License Grant to You. The Software is licensed, not sold. Subject to the terms of this Agreement, +NVIDIA grants you a limited, non-exclusive, revocable, non-transferable, non-sublicensable (except +as expressly granted in this Agreement), license to: +(a) access, install and use copies of the Software, +(b) configure the Software using configuration files provided (if applicable), +(c) modify and create derivative works of any source code NVIDIA delivers to you as part of the +Software (“Derivatives”) (if applicable). + +All the foregoing grants are only for internal test and evaluation purposes and, as applicable, for use (a) in +client systems, or (b) in server systems with NVIDIA GPUs (“Purpose”). + +1.2 License Grant to NVIDIA. Subject to the terms of this Agreement, you grant NVIDIA a non-exclusive, +perpetual, irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, +under your intellectual property rights, to publicly perform, publicly display, reproduce, use, make, +have made, sell, offer for sale, distribute (through multiple tiers of distribution), import, create +derivative works of and otherwise commercialize and exploit at NVIDIA’s discretion any Derivatives +created by or for you. You may, but are not required to, deliver any Derivatives to NVIDIA. + +2. License Restrictions. Your license to use the Software and Derivatives is restricted as stated in this “License +Restrictions” Section. You will cooperate with NVIDIA and, upon NVIDIA’s written request, you will confirm +in writing and provide reasonably requested information to verify your compliance with the terms of this +Agreement. You may not: + +2.1 Use the Software or Derivatives for any purpose other than the Purpose, including but not limited to +in production; + +2.2 Sell, rent, sublicense, transfer, distribute or otherwise make available to others (except Authorized +Users as stated in the “Authorized Users” Section) any portion of the Software or Derivatives, except +as expressly granted in Section 1.1 (“License Grant to You”); + +2.3 Reverse engineer, decompile, or disassemble the Software components provided in binary form, nor +attempt in any other manner to obtain source code of such Software; + +2.4 Modify or create derivative works of the Software, except as expressly granted in Section 1.1 +(“License Grant to You”); + +2.5 Change or remove copyright or other proprietary notices in the Software; + +2.6 Bypass, disable, or circumvent any technical limitation, encryption, security, digital rights +management or authentication mechanism in the Software; + +2.7 Use the Software or Derivatives in any manner that would cause them to become subject to an open +source software license; subject to the terms in Section 7 (“Components Under Other Licenses”); + +2.8 Use the Software or Derivatives for the purpose of developing competing products or technologies +or assist a third party in such activities; + +2.9 Replace any Software components governed by this Agreement with other software that +implements NVIDIA APIs; + +2.10 Use the Software or Derivatives in violation of any applicable law or regulation in the relevant +jurisdictions; or + +2.11 Use the Software in or with any system or application where the use or failure of such system or +application developed or deployed with Software could result in injury, death or catastrophic +damage (“Mission Critical Applications”). NVIDIA will not be liable to you or any third party, in whole +or in part, for any claims or damages arising from uses in Mission Critical Applications. + +2.12 Disclose any evaluation or test results regarding the Software or Derivatives without NVIDIA’s prior +written consent. + +3. Authorized Users. You may allow employees and contractors of your entity or of your subsidiary(ies), and +for educational institutions also enrolled students, to internally access and use the Software as authorized +by this Agreement from your secure network to perform the work authorized by this Agreement on your +behalf. You are responsible for the compliance with the terms of this Agreement by your authorized users. +Any act or omission that if committed by you would constitute a breach of this Agreement will be deemed to +constitute a breach of this Agreement if committed by your authorized users. + +4. Pre-Release. Software versions identified as alpha, beta, preview, early access or otherwise as pre-release +may not be fully functional, may contain errors or design flaws, and may have reduced or different security, +privacy, availability and reliability standards relative to NVIDIA commercial offerings. You use pre-release +Software at your own risk. NVIDIA did not design or test the Software for use in production or business +critical systems. NVIDIA may choose not to make available a commercial version of pre-release Software. +NVIDIA may also choose to abandon development and terminate the availability of pre-release Software at +any time without liability. + +5. Your Privacy: Collection and Use of Information. + +5.1 Privacy Policy. Please review the NVIDIA Privacy Policy, located at https://www.nvidia.com/enus/about-nvidia/privacy-policy, which explains NVIDIA’s policy for collecting and using data, as well +as visit the NVIDIA Privacy Center, located at https://www.nvidia.com/en-us/privacy-center, to +manage your consent and privacy preferences. + +5.2 Collection Purposes. You also acknowledge that the Software collects data for the following +purposes: (a) properly configure and optimize products for use with Software; and (b) improve +NVIDIA products and services. Information collected by the Software includes: (i) application +configuration; (ii) browser version; (iii) and session metadata (i.e. performance and usage +statistics). Additionally, NVIDIA may collect certain personal information, such as your name +and email address or those of your authorized users, and other information necessary to +authenticate and enable you or your authorized users’ access to the Software. Where appropriate +you will disclose to, and obtain any necessary consent from, your authorized users to allow NVIDIA +to collect such information. + +5.3 Third Party Privacy Practices. The Software may contain links to third party websites and services. +NVIDIA encourages you to review the privacy statements on those sites and services that you choose +to visit to understand how they may collect, use and share your data. NVIDIA is not responsible for +the privacy statements or practices of third-party sites or services. + +6. Updates. NVIDIA may at any time and at its option, change, discontinue, or deprecate any part, or all, of the +Software, or change or remove features or functionality, or make available patches, workarounds or other +updates to the Software. Unless the updates are provided with their separate governing terms, they are +deemed part of the Software licensed to you under this Agreement, and your continued use of the Software +is deemed acceptance of such changes. + +7. Components Under Other Licenses. The Software may include or be distributed with components provided +with separate legal notices or terms that accompany the components, such as open source software licenses +and other license terms (“Other Licenses”). The components are subject to the applicable Other Licenses, +including any proprietary notices, disclaimers, requirements and extended use rights; except that this +Agreement will prevail regarding the use of third-party open source software, unless a third-party open +source software license requires its license terms to prevail. Open source software license means any +software, data or documentation subject to any license identified as an open source license by the Open +Source Initiative (http://opensource.org), Free Software Foundation (http://www.fsf.org) or other similar +open source organization or listed by the Software Package Data Exchange (SPDX) Workgroup under the +Linux Foundation (http://www.spdx.org). + +8. Ownership. + +8.1 NVIDIA Ownership. The Software, including all intellectual property rights, is and will remain the sole +and exclusive property of NVIDIA or its licensors. Except as expressly granted in this Agreement, +(a) NVIDIA reserves all rights, interests and remedies in connection with the Software, and (b) no +other license or right is granted to you by implication, estoppel or otherwise. + +8.2 Your Ownership. Subject to the rights of NVIDIA and its suppliers in the Software, which continue to +be licensed as stated in this Agreement, even when incorporated in your products, and the extent +permitted by applicable law, as between you and NVIDIA, you hold all rights, title and interest in and +to your services, applications and Derivatives you develop as permitted in this Agreement including +their respective intellectual property rights. + +9. Feedback. You may, but are not obligated to, provide suggestions, requests, fixes, modifications, +enhancements or other feedback regarding your use of the Software (“Feedback”). Feedback, even if +designated as confidential by you, will not create any confidentiality obligation for NVIDIA or its affiliates. If +you provide Feedback, you hereby grant NVIDIA, its affiliates and its designees a nonexclusive, perpetual, +irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, under your +intellectual property rights, to publicly perform, publicly display, reproduce, use, make, have made, sell, +offer for sale, distribute (through multiple tiers of distribution), import, create derivative works of and +otherwise commercialize and exploit the Feedback at NVIDIA’s discretion. + +10. Confidentiality. You may use confidential information only to exercise your rights and perform your +obligations under this Agreement. You will not disclose, nor authorize others to disclose NVIDIA Confidential +Information to any third party, except as expressly authorized in this Agreement and as necessary for the +Purpose, without obtaining NVIDIA’s prior written approval. Each recipient of confidential information must +be subject to a written agreement that includes confidentiality obligations consistent with these terms and +must have a need to know for the Purpose. You will protect the NVIDIA Confidential Information with at +least the same degree of care that you use to protect your own similar confidential and proprietary +information, but no less than a reasonable degree of care. Confidential information includes, but is not +limited to, the Software, including its features and functionality, Derivatives, and any results of +benchmarking or other competitive analysis or regression or performance data relating to the Software. +No Publicity. You may not issue any public statements about this Agreement, disclose the Software or +Derivatives, or any information or results related to your use of the Software, without prior written approval +of NVIDIA. + +11. Term and Termination. + +11.1 Term. This Agreement has a duration of twelve (12) months starting from the date of initial +download (even if you download the same version or updates of the Software later and it is +accompanied by this Agreement or another Agreement), unless terminated earlier in accordance +with this Agreement. + +11.2 Termination for Convenience. Either party may terminate this Agreement at any time with thirty (30) +days’ advance written notice to the other party. + +11.3 Termination for Cause. If you commence or participate in any legal proceeding against NVIDIA with +respect to the Software, this Agreement will terminate immediately without notice. Either party may +terminate this Agreement upon notice for cause if: +(a) the other party fails to cure a material breach of this Agreement within ten (10) days of the +non-breaching party’s notice of the breach; or +(b) the other party breaches its confidentiality obligations or license rights under this +Agreement, which termination will be effective immediately upon written notice. + +11.4 Effect of Termination. Upon any expiration or termination of this Agreement, you will promptly +(a) stop using and return, delete or destroy NVIDIA confidential information and all Software +received under this Agreement, and (b) delete or destroy Derivatives created under this Agreement, +unless an authorized NVIDIA representative provides prior written approval that you may keep a +copy of the Derivatives solely for archival purposes. Upon written request, you will certify in writing +that you have complied with your obligations under this “Effect of Termination” Section. + +11.5 Survival. The “License Grant to NVIDIA”, “Updates”, “Components Under Other Licenses”, +“Ownership”, “Feedback”, “Confidentiality”, “No Publicity”, “Effect of Termination”, “Survival”, +“Disclaimer of Warranties”, “Limitation of Liability”, “Indemnity” and all “General” Sections of this +Agreement will survive any expiration or termination of this Agreement. + +12. Disclaimer of Warranties. THE SOFTWARE IS PROVIDED BY NVIDIA AS-IS AND WITH ALL FAULTS. TO THE +MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, NVIDIA DISCLAIMS ALL WARRANTIES AND +REPRESENTATIONS OF ANY KIND, WHETHER EXPRESS, IMPLIED OR STATUTORY, RELATING TO OR ARISING +UNDER THIS AGREEMENT, INCLUDING, WITHOUT LIMITATION, THE WARRANTIES OF TITLE, +NONINFRINGEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, USAGE OF TRADE AND +COURSE OF DEALING. NVIDIA DOES NOT WARRANT OR ASSUME RESPONSIBILITY FOR THE ACCURACY OR +COMPLETENESS OF ANY THIRD-PARTY INFORMATION, TEXT, GRAPHICS, LINKS CONTAINED IN THE +SOFTWARE. WITHOUT LIMITING THE FOREGOING, NVIDIA DOES NOT WARRANT THAT THE SOFTWARE WILL +MEET YOUR REQUIREMENTS, ANY DEFECTS OR ERRORS WILL BE CORRECTED, ANY CERTAIN CONTENT WILL +BE AVAILABLE; OR THAT THE SOFTWARE IS FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS. NO +INFORMATION OR ADVICE GIVEN BY NVIDIA WILL IN ANY WAY INCREASE THE SCOPE OF ANY WARRANTY +EXPRESSLY PROVIDED IN THIS AGREEMENT. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE +APPROPRIATENESS OF USING THE SOFTWARE OR DERIVATIVES AND ASSUME ANY RISKS ASSOCIATED WITH +YOUR USE OF THE SOFTWARE OR DERIVATIVES. + +13. Limitations of Liability. + +13.1 EXCLUSIONS. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL +NVIDIA BE LIABLE FOR ANY (A) INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL OR CONSEQUENTIAL +DAMAGES, OR (B) DAMAGES FOR THE (I) COST OF PROCURING SUBSTITUTE GOODS OR (II) LOSS OF +PROFITS, REVENUES, USE, DATA OR GOODWILL ARISING OUT OF OR RELATED TO THIS AGREEMENT, +WHETHER BASED ON BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, OR +OTHERWISE, AND EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND +EVEN IF A PARTY’S REMEDIES FAIL THEIR ESSENTIAL PURPOSE. + +13.2 DAMAGES CAP. ADDITIONALLY, TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, +NVIDIA’S TOTAL CUMULATIVE AGGREGATE LIABILITY FOR ANY AND ALL LIABILITIES, OBLIGATIONS OR +CLAIMS ARISING OUT OF OR RELATED TO THIS AGREEMENT WILL NOT EXCEED ONE HUNDRED U.S. +DOLLARS (US$100). + +14. Indemnity. You will defend, indemnify and hold harmless NVIDIA and its affiliates, and their respective +employees, contractors, agents, officers and directors, from and against any and all third party claims, +damages, obligations, losses, liabilities, costs or debt, fines, restitutions and expenses (including but not +limited to attorney’s fees and costs incident to establishing the right of indemnification) arising out of use of +the Software and Derivatives outside of the scope of this Agreement or in breach of the terms of this +Agreement. + +15. General. + +15.1 Governing Law and Jurisdiction. This Agreement will be governed in all respects by the laws of the +United States and the laws of the State of Delaware, without regard to conflict of laws principles or +the United Nations Convention on Contracts for the International Sale of Goods. The state and +federal courts residing in Santa Clara County, California will have exclusive jurisdiction over any +dispute or claim arising out of or related to this Agreement, and the parties irrevocably consent to +personal jurisdiction and venue in those courts; except that either party may apply for injunctive +remedies or an equivalent type of urgent legal relief in any jurisdiction. + +15.2 Independent Contractors. The parties are independent contractors, and this Agreement does not +create a joint venture, partnership, agency or other form of business association between the +parties. Neither party will have the power to bind the other party or incur any obligation on its +behalf without the other party’s prior written consent. Nothing in this Agreement prevents either +party from participating in similar arrangements with third parties. + +15.3 No Assignment. NVIDIA may assign, delegate or transfer its rights or obligations under this +Agreement by any means or operation of law. You may not, without NVIDIA’s prior written consent, +assign, delegate or transfer any of your rights or obligations under this Agreement by any means or +operation of law, and any attempt to do so is null and void. + +15.4 No Waiver. No failure or delay by a party to enforce any term or obligation of this Agreement will +operate as a waiver by that party, or prevent the enforcement of such term or obligation later. + +15.5 Trade Compliance. You agree to comply with all applicable export, import, trade and economic +sanctions laws and regulations, as amended, including without limitation U.S. Export Administration +Regulations and Office of Foreign Assets Control regulations. You confirm (a) your understanding +that export or reexport of certain NVIDIA products or technologies may require a license or other +approval from appropriate authorities and (b) that you will not export or reexport any products or +technology, directly or indirectly, without first obtaining any required license or other approval from +appropriate authorities, (i) to any countries that are subject to any U.S. or local export restrictions +(currently including, but not necessarily limited to, Belarus, Cuba, Iran, North Korea, Russia, Syria, +the Region of Crimea, Donetsk People’s Republic Region and Luhansk People’s Republic Region); (ii) +to any end-user who you know or have reason to know will utilize them in the design, development +or production of nuclear, chemical or biological weapons, missiles, rocket systems, unmanned air +vehicles capable of a maximum range of at least 300 kilometers, regardless of payload, or intended +for military end-use, or any weapons of mass destruction; (iii) to any end-user who has been +prohibited from participating in the U.S. or local export transactions by any governing authority; or +(iv) to any known military or military-intelligence end-user or for any known military or military- +intelligence end-use in accordance with U.S. trade compliance laws and regulations. + +15.6 Government Rights. The Software, documentation and technology (“Protected Items”) are +“Commercial products” as this term is defined at 48 C.F.R. 2.101, consisting of “commercial +computer software” and “commercial computer software documentation” as such terms are used +in, respectively, 48 C.F.R. 12.212 and 48 C.F.R. 227.7202 & 252.227-7014(a)(1). Before any Protected +Items are supplied to the U.S. Government, you will (i) inform the U.S. Government in writing that +the Protected Items are and must be treated as commercial computer software and commercial +computer software documentation developed at private expense; (ii) inform the U.S. Government +that the Protected Items are provided subject to the terms of the Agreement; and (iii) mark the +Protected Items as commercial computer software and commercial computer software +documentation developed at private expense. In no event will you permit the U.S. Government to +acquire rights in Protected Items beyond those specified in 48 C.F.R. 52.227-19(b)(1)-(2) or 252.227- +7013(c) except as expressly approved by NVIDIA in writing. + +15.7 Notices. Please direct your legal notices or other correspondence to legalnotices@nvidia.com with a +copy mailed to NVIDIA Corporation, 2788 San Tomas Expressway, Santa Clara, California 95051, +United States of America, Attention: Legal Department. If NVIDIA needs to contact you about the +Software, you consent to receive the notices by email and agree that such notices will satisfy any +legal communication requirements. + +15.8 Severability. If a court of competent jurisdiction rules that a provision of this Agreement is +unenforceable, that provision will be deemed modified to the extent necessary to make it +enforceable and the remainder of this Agreement will continue in full force and effect. + +15.9 Amendment. Any amendment to this Agreement must be in writing and signed by authorized +representatives of both parties. + +15.10 Entire Agreement. Regarding the subject matter of this Agreement, the parties agree that (a) this +Agreement constitutes the entire and exclusive agreement between the parties and supersedes all +prior and contemporaneous communications and (b) any additional or different terms or conditions, +whether contained in purchase orders, order acknowledgments, invoices or otherwise, will not be +binding and are null and void. + +(v. February 25, 2025) + +NVIDIA Confidential \ No newline at end of file diff --git a/deps/cloudxr/docs/media/LICENSE-1 b/deps/cloudxr/docs/media/LICENSE-1 new file mode 100644 index 0000000..5e53852 --- /dev/null +++ b/deps/cloudxr/docs/media/LICENSE-1 @@ -0,0 +1,307 @@ +NVIDIA SOFTWARE EVALUATION LICENSE AGREEMENT + +IMPORTANT NOTICE – PLEASE READ AND AGREE BEFORE USING THE SOFTWARE + +This software evaluation license agreement (“Agreement”) is a legal agreement between you, whether an +individual or entity, (“you”) and NVIDIA Corporation and its affiliates (“NVIDIA”) and governs the use of certain +NVIDIA CloudXR software and documentation that NVIDIA delivers to you under this Agreement (“Software”). +NVIDIA and you are each a “party” and collectively the “parties.” +This Agreement can be accepted only by an adult of legal age of majority in the country in which the Software is +used. If you don’t have the required age or authority to accept this Agreement, or if you don’t accept all the +terms and conditions of this Agreement, do not use the Software. + +1. License Grants. + +1.1 License Grant to You. The Software is licensed, not sold. Subject to the terms of this Agreement, +NVIDIA grants you a limited, non-exclusive, revocable, non-transferable, non-sublicensable (except +as expressly granted in this Agreement), license to: +(a) access, install and use copies of the Software, +(b) configure the Software using configuration files provided (if applicable), +(c) modify and create derivative works of any source code NVIDIA delivers to you as part of the +Software (“Derivatives”) (if applicable). + +All the foregoing grants are only for internal test and evaluation purposes and, as applicable, for use (a) in +client systems, or (b) in server systems with NVIDIA GPUs (“Purpose”). + +1.2 License Grant to NVIDIA. Subject to the terms of this Agreement, you grant NVIDIA a non-exclusive, +perpetual, irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, +under your intellectual property rights, to publicly perform, publicly display, reproduce, use, make, +have made, sell, offer for sale, distribute (through multiple tiers of distribution), import, create +derivative works of and otherwise commercialize and exploit at NVIDIA’s discretion any Derivatives +created by or for you. You may, but are not required to, deliver any Derivatives to NVIDIA. + +2. License Restrictions. Your license to use the Software and Derivatives is restricted as stated in this “License +Restrictions” Section. You will cooperate with NVIDIA and, upon NVIDIA’s written request, you will confirm +in writing and provide reasonably requested information to verify your compliance with the terms of this +Agreement. You may not: + +2.1 Use the Software or Derivatives for any purpose other than the Purpose, including but not limited to +in production; + +2.2 Sell, rent, sublicense, transfer, distribute or otherwise make available to others (except Authorized +Users as stated in the “Authorized Users” Section) any portion of the Software or Derivatives, except +as expressly granted in Section 1.1 (“License Grant to You”); + +2.3 Reverse engineer, decompile, or disassemble the Software components provided in binary form, nor +attempt in any other manner to obtain source code of such Software; + +2.4 Modify or create derivative works of the Software, except as expressly granted in Section 1.1 +(“License Grant to You”); + +2.5 Change or remove copyright or other proprietary notices in the Software; + +2.6 Bypass, disable, or circumvent any technical limitation, encryption, security, digital rights +management or authentication mechanism in the Software; + +2.7 Use the Software or Derivatives in any manner that would cause them to become subject to an open +source software license; subject to the terms in Section 7 (“Components Under Other Licenses”); + +2.8 Use the Software or Derivatives for the purpose of developing competing products or technologies +or assist a third party in such activities; + +2.9 Replace any Software components governed by this Agreement with other software that +implements NVIDIA APIs; + +2.10 Use the Software or Derivatives in violation of any applicable law or regulation in the relevant +jurisdictions; or + +2.11 Use the Software in or with any system or application where the use or failure of such system or +application developed or deployed with Software could result in injury, death or catastrophic +damage (“Mission Critical Applications”). NVIDIA will not be liable to you or any third party, in whole +or in part, for any claims or damages arising from uses in Mission Critical Applications. + +2.12 Disclose any evaluation or test results regarding the Software or Derivatives without NVIDIA’s prior +written consent. + +3. Authorized Users. You may allow employees and contractors of your entity or of your subsidiary(ies), and +for educational institutions also enrolled students, to internally access and use the Software as authorized +by this Agreement from your secure network to perform the work authorized by this Agreement on your +behalf. You are responsible for the compliance with the terms of this Agreement by your authorized users. +Any act or omission that if committed by you would constitute a breach of this Agreement will be deemed to +constitute a breach of this Agreement if committed by your authorized users. + +4. Pre-Release. Software versions identified as alpha, beta, preview, early access or otherwise as pre-release +may not be fully functional, may contain errors or design flaws, and may have reduced or different security, +privacy, availability and reliability standards relative to NVIDIA commercial offerings. You use pre-release +Software at your own risk. NVIDIA did not design or test the Software for use in production or business +critical systems. NVIDIA may choose not to make available a commercial version of pre-release Software. +NVIDIA may also choose to abandon development and terminate the availability of pre-release Software at +any time without liability. + +5. Your Privacy: Collection and Use of Information. + +5.1 Privacy Policy. Please review the NVIDIA Privacy Policy, located at https://www.nvidia.com/enus/about-nvidia/privacy-policy, which explains NVIDIA’s policy for collecting and using data, as well +as visit the NVIDIA Privacy Center, located at https://www.nvidia.com/en-us/privacy-center, to +manage your consent and privacy preferences. + +5.2 Collection Purposes. You also acknowledge that the Software collects data for the following +purposes: (a) properly configure and optimize products for use with Software; and (b) improve +NVIDIA products and services. Information collected by the Software includes: (i) application +configuration; (ii) browser version; (iii) and session metadata (i.e. performance and usage +statistics). Additionally, NVIDIA may collect certain personal information, such as your name +and email address or those of your authorized users, and other information necessary to +authenticate and enable you or your authorized users’ access to the Software. Where appropriate +you will disclose to, and obtain any necessary consent from, your authorized users to allow NVIDIA +to collect such information. + +5.3 Third Party Privacy Practices. The Software may contain links to third party websites and services. +NVIDIA encourages you to review the privacy statements on those sites and services that you choose +to visit to understand how they may collect, use and share your data. NVIDIA is not responsible for +the privacy statements or practices of third-party sites or services. + +6. Updates. NVIDIA may at any time and at its option, change, discontinue, or deprecate any part, or all, of the +Software, or change or remove features or functionality, or make available patches, workarounds or other +updates to the Software. Unless the updates are provided with their separate governing terms, they are +deemed part of the Software licensed to you under this Agreement, and your continued use of the Software +is deemed acceptance of such changes. + +7. Components Under Other Licenses. The Software may include or be distributed with components provided +with separate legal notices or terms that accompany the components, such as open source software licenses +and other license terms (“Other Licenses”). The components are subject to the applicable Other Licenses, +including any proprietary notices, disclaimers, requirements and extended use rights; except that this +Agreement will prevail regarding the use of third-party open source software, unless a third-party open +source software license requires its license terms to prevail. Open source software license means any +software, data or documentation subject to any license identified as an open source license by the Open +Source Initiative (http://opensource.org), Free Software Foundation (http://www.fsf.org) or other similar +open source organization or listed by the Software Package Data Exchange (SPDX) Workgroup under the +Linux Foundation (http://www.spdx.org). + +8. Ownership. + +8.1 NVIDIA Ownership. The Software, including all intellectual property rights, is and will remain the sole +and exclusive property of NVIDIA or its licensors. Except as expressly granted in this Agreement, +(a) NVIDIA reserves all rights, interests and remedies in connection with the Software, and (b) no +other license or right is granted to you by implication, estoppel or otherwise. + +8.2 Your Ownership. Subject to the rights of NVIDIA and its suppliers in the Software, which continue to +be licensed as stated in this Agreement, even when incorporated in your products, and the extent +permitted by applicable law, as between you and NVIDIA, you hold all rights, title and interest in and +to your services, applications and Derivatives you develop as permitted in this Agreement including +their respective intellectual property rights. + +9. Feedback. You may, but are not obligated to, provide suggestions, requests, fixes, modifications, +enhancements or other feedback regarding your use of the Software (“Feedback”). Feedback, even if +designated as confidential by you, will not create any confidentiality obligation for NVIDIA or its affiliates. If +you provide Feedback, you hereby grant NVIDIA, its affiliates and its designees a nonexclusive, perpetual, +irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, under your +intellectual property rights, to publicly perform, publicly display, reproduce, use, make, have made, sell, +offer for sale, distribute (through multiple tiers of distribution), import, create derivative works of and +otherwise commercialize and exploit the Feedback at NVIDIA’s discretion. + +10. Confidentiality. You may use confidential information only to exercise your rights and perform your +obligations under this Agreement. You will not disclose, nor authorize others to disclose NVIDIA Confidential +Information to any third party, except as expressly authorized in this Agreement and as necessary for the +Purpose, without obtaining NVIDIA’s prior written approval. Each recipient of confidential information must +be subject to a written agreement that includes confidentiality obligations consistent with these terms and +must have a need to know for the Purpose. You will protect the NVIDIA Confidential Information with at +least the same degree of care that you use to protect your own similar confidential and proprietary +information, but no less than a reasonable degree of care. Confidential information includes, but is not +limited to, the Software, including its features and functionality, Derivatives, and any results of +benchmarking or other competitive analysis or regression or performance data relating to the Software. +No Publicity. You may not issue any public statements about this Agreement, disclose the Software or +Derivatives, or any information or results related to your use of the Software, without prior written approval +of NVIDIA. + +11. Term and Termination. + +11.1 Term. This Agreement has a duration of twelve (12) months starting from the date of initial +download (even if you download the same version or updates of the Software later and it is +accompanied by this Agreement or another Agreement), unless terminated earlier in accordance +with this Agreement. + +11.2 Termination for Convenience. Either party may terminate this Agreement at any time with thirty (30) +days’ advance written notice to the other party. + +11.3 Termination for Cause. If you commence or participate in any legal proceeding against NVIDIA with +respect to the Software, this Agreement will terminate immediately without notice. Either party may +terminate this Agreement upon notice for cause if: +(a) the other party fails to cure a material breach of this Agreement within ten (10) days of the +non-breaching party’s notice of the breach; or +(b) the other party breaches its confidentiality obligations or license rights under this +Agreement, which termination will be effective immediately upon written notice. + +11.4 Effect of Termination. Upon any expiration or termination of this Agreement, you will promptly +(a) stop using and return, delete or destroy NVIDIA confidential information and all Software +received under this Agreement, and (b) delete or destroy Derivatives created under this Agreement, +unless an authorized NVIDIA representative provides prior written approval that you may keep a +copy of the Derivatives solely for archival purposes. Upon written request, you will certify in writing +that you have complied with your obligations under this “Effect of Termination” Section. + +11.5 Survival. The “License Grant to NVIDIA”, “Updates”, “Components Under Other Licenses”, +“Ownership”, “Feedback”, “Confidentiality”, “No Publicity”, “Effect of Termination”, “Survival”, +“Disclaimer of Warranties”, “Limitation of Liability”, “Indemnity” and all “General” Sections of this +Agreement will survive any expiration or termination of this Agreement. + +12. Disclaimer of Warranties. THE SOFTWARE IS PROVIDED BY NVIDIA AS-IS AND WITH ALL FAULTS. TO THE +MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, NVIDIA DISCLAIMS ALL WARRANTIES AND +REPRESENTATIONS OF ANY KIND, WHETHER EXPRESS, IMPLIED OR STATUTORY, RELATING TO OR ARISING +UNDER THIS AGREEMENT, INCLUDING, WITHOUT LIMITATION, THE WARRANTIES OF TITLE, +NONINFRINGEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, USAGE OF TRADE AND +COURSE OF DEALING. NVIDIA DOES NOT WARRANT OR ASSUME RESPONSIBILITY FOR THE ACCURACY OR +COMPLETENESS OF ANY THIRD-PARTY INFORMATION, TEXT, GRAPHICS, LINKS CONTAINED IN THE +SOFTWARE. WITHOUT LIMITING THE FOREGOING, NVIDIA DOES NOT WARRANT THAT THE SOFTWARE WILL +MEET YOUR REQUIREMENTS, ANY DEFECTS OR ERRORS WILL BE CORRECTED, ANY CERTAIN CONTENT WILL +BE AVAILABLE; OR THAT THE SOFTWARE IS FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS. NO +INFORMATION OR ADVICE GIVEN BY NVIDIA WILL IN ANY WAY INCREASE THE SCOPE OF ANY WARRANTY +EXPRESSLY PROVIDED IN THIS AGREEMENT. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE +APPROPRIATENESS OF USING THE SOFTWARE OR DERIVATIVES AND ASSUME ANY RISKS ASSOCIATED WITH +YOUR USE OF THE SOFTWARE OR DERIVATIVES. + +13. Limitations of Liability. + +13.1 EXCLUSIONS. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL +NVIDIA BE LIABLE FOR ANY (A) INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL OR CONSEQUENTIAL +DAMAGES, OR (B) DAMAGES FOR THE (I) COST OF PROCURING SUBSTITUTE GOODS OR (II) LOSS OF +PROFITS, REVENUES, USE, DATA OR GOODWILL ARISING OUT OF OR RELATED TO THIS AGREEMENT, +WHETHER BASED ON BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, OR +OTHERWISE, AND EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND +EVEN IF A PARTY’S REMEDIES FAIL THEIR ESSENTIAL PURPOSE. + +13.2 DAMAGES CAP. ADDITIONALLY, TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, +NVIDIA’S TOTAL CUMULATIVE AGGREGATE LIABILITY FOR ANY AND ALL LIABILITIES, OBLIGATIONS OR +CLAIMS ARISING OUT OF OR RELATED TO THIS AGREEMENT WILL NOT EXCEED ONE HUNDRED U.S. +DOLLARS (US$100). + +14. Indemnity. You will defend, indemnify and hold harmless NVIDIA and its affiliates, and their respective +employees, contractors, agents, officers and directors, from and against any and all third party claims, +damages, obligations, losses, liabilities, costs or debt, fines, restitutions and expenses (including but not +limited to attorney’s fees and costs incident to establishing the right of indemnification) arising out of use of +the Software and Derivatives outside of the scope of this Agreement or in breach of the terms of this +Agreement. + +15. General. + +15.1 Governing Law and Jurisdiction. This Agreement will be governed in all respects by the laws of the +United States and the laws of the State of Delaware, without regard to conflict of laws principles or +the United Nations Convention on Contracts for the International Sale of Goods. The state and +federal courts residing in Santa Clara County, California will have exclusive jurisdiction over any +dispute or claim arising out of or related to this Agreement, and the parties irrevocably consent to +personal jurisdiction and venue in those courts; except that either party may apply for injunctive +remedies or an equivalent type of urgent legal relief in any jurisdiction. + +15.2 Independent Contractors. The parties are independent contractors, and this Agreement does not +create a joint venture, partnership, agency or other form of business association between the +parties. Neither party will have the power to bind the other party or incur any obligation on its +behalf without the other party’s prior written consent. Nothing in this Agreement prevents either +party from participating in similar arrangements with third parties. + +15.3 No Assignment. NVIDIA may assign, delegate or transfer its rights or obligations under this +Agreement by any means or operation of law. You may not, without NVIDIA’s prior written consent, +assign, delegate or transfer any of your rights or obligations under this Agreement by any means or +operation of law, and any attempt to do so is null and void. + +15.4 No Waiver. No failure or delay by a party to enforce any term or obligation of this Agreement will +operate as a waiver by that party, or prevent the enforcement of such term or obligation later. + +15.5 Trade Compliance. You agree to comply with all applicable export, import, trade and economic +sanctions laws and regulations, as amended, including without limitation U.S. Export Administration +Regulations and Office of Foreign Assets Control regulations. You confirm (a) your understanding +that export or reexport of certain NVIDIA products or technologies may require a license or other +approval from appropriate authorities and (b) that you will not export or reexport any products or +technology, directly or indirectly, without first obtaining any required license or other approval from +appropriate authorities, (i) to any countries that are subject to any U.S. or local export restrictions +(currently including, but not necessarily limited to, Belarus, Cuba, Iran, North Korea, Russia, Syria, +the Region of Crimea, Donetsk People’s Republic Region and Luhansk People’s Republic Region); (ii) +to any end-user who you know or have reason to know will utilize them in the design, development +or production of nuclear, chemical or biological weapons, missiles, rocket systems, unmanned air +vehicles capable of a maximum range of at least 300 kilometers, regardless of payload, or intended +for military end-use, or any weapons of mass destruction; (iii) to any end-user who has been +prohibited from participating in the U.S. or local export transactions by any governing authority; or +(iv) to any known military or military-intelligence end-user or for any known military or military- +intelligence end-use in accordance with U.S. trade compliance laws and regulations. + +15.6 Government Rights. The Software, documentation and technology (“Protected Items”) are +“Commercial products” as this term is defined at 48 C.F.R. 2.101, consisting of “commercial +computer software” and “commercial computer software documentation” as such terms are used +in, respectively, 48 C.F.R. 12.212 and 48 C.F.R. 227.7202 & 252.227-7014(a)(1). Before any Protected +Items are supplied to the U.S. Government, you will (i) inform the U.S. Government in writing that +the Protected Items are and must be treated as commercial computer software and commercial +computer software documentation developed at private expense; (ii) inform the U.S. Government +that the Protected Items are provided subject to the terms of the Agreement; and (iii) mark the +Protected Items as commercial computer software and commercial computer software +documentation developed at private expense. In no event will you permit the U.S. Government to +acquire rights in Protected Items beyond those specified in 48 C.F.R. 52.227-19(b)(1)-(2) or 252.227- +7013(c) except as expressly approved by NVIDIA in writing. + +15.7 Notices. Please direct your legal notices or other correspondence to legalnotices@nvidia.com with a +copy mailed to NVIDIA Corporation, 2788 San Tomas Expressway, Santa Clara, California 95051, +United States of America, Attention: Legal Department. If NVIDIA needs to contact you about the +Software, you consent to receive the notices by email and agree that such notices will satisfy any +legal communication requirements. + +15.8 Severability. If a court of competent jurisdiction rules that a provision of this Agreement is +unenforceable, that provision will be deemed modified to the extent necessary to make it +enforceable and the remainder of this Agreement will continue in full force and effect. + +15.9 Amendment. Any amendment to this Agreement must be in writing and signed by authorized +representatives of both parties. + +15.10 Entire Agreement. Regarding the subject matter of this Agreement, the parties agree that (a) this +Agreement constitutes the entire and exclusive agreement between the parties and supersedes all +prior and contemporaneous communications and (b) any additional or different terms or conditions, +whether contained in purchase orders, order acknowledgments, invoices or otherwise, will not be +binding and are null and void. + +(v. February 25, 2025) + +NVIDIA Confidential \ No newline at end of file diff --git a/deps/cloudxr/docs/media/LICENSE-2 b/deps/cloudxr/docs/media/LICENSE-2 new file mode 100644 index 0000000..5e53852 --- /dev/null +++ b/deps/cloudxr/docs/media/LICENSE-2 @@ -0,0 +1,307 @@ +NVIDIA SOFTWARE EVALUATION LICENSE AGREEMENT + +IMPORTANT NOTICE – PLEASE READ AND AGREE BEFORE USING THE SOFTWARE + +This software evaluation license agreement (“Agreement”) is a legal agreement between you, whether an +individual or entity, (“you”) and NVIDIA Corporation and its affiliates (“NVIDIA”) and governs the use of certain +NVIDIA CloudXR software and documentation that NVIDIA delivers to you under this Agreement (“Software”). +NVIDIA and you are each a “party” and collectively the “parties.” +This Agreement can be accepted only by an adult of legal age of majority in the country in which the Software is +used. If you don’t have the required age or authority to accept this Agreement, or if you don’t accept all the +terms and conditions of this Agreement, do not use the Software. + +1. License Grants. + +1.1 License Grant to You. The Software is licensed, not sold. Subject to the terms of this Agreement, +NVIDIA grants you a limited, non-exclusive, revocable, non-transferable, non-sublicensable (except +as expressly granted in this Agreement), license to: +(a) access, install and use copies of the Software, +(b) configure the Software using configuration files provided (if applicable), +(c) modify and create derivative works of any source code NVIDIA delivers to you as part of the +Software (“Derivatives”) (if applicable). + +All the foregoing grants are only for internal test and evaluation purposes and, as applicable, for use (a) in +client systems, or (b) in server systems with NVIDIA GPUs (“Purpose”). + +1.2 License Grant to NVIDIA. Subject to the terms of this Agreement, you grant NVIDIA a non-exclusive, +perpetual, irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, +under your intellectual property rights, to publicly perform, publicly display, reproduce, use, make, +have made, sell, offer for sale, distribute (through multiple tiers of distribution), import, create +derivative works of and otherwise commercialize and exploit at NVIDIA’s discretion any Derivatives +created by or for you. You may, but are not required to, deliver any Derivatives to NVIDIA. + +2. License Restrictions. Your license to use the Software and Derivatives is restricted as stated in this “License +Restrictions” Section. You will cooperate with NVIDIA and, upon NVIDIA’s written request, you will confirm +in writing and provide reasonably requested information to verify your compliance with the terms of this +Agreement. You may not: + +2.1 Use the Software or Derivatives for any purpose other than the Purpose, including but not limited to +in production; + +2.2 Sell, rent, sublicense, transfer, distribute or otherwise make available to others (except Authorized +Users as stated in the “Authorized Users” Section) any portion of the Software or Derivatives, except +as expressly granted in Section 1.1 (“License Grant to You”); + +2.3 Reverse engineer, decompile, or disassemble the Software components provided in binary form, nor +attempt in any other manner to obtain source code of such Software; + +2.4 Modify or create derivative works of the Software, except as expressly granted in Section 1.1 +(“License Grant to You”); + +2.5 Change or remove copyright or other proprietary notices in the Software; + +2.6 Bypass, disable, or circumvent any technical limitation, encryption, security, digital rights +management or authentication mechanism in the Software; + +2.7 Use the Software or Derivatives in any manner that would cause them to become subject to an open +source software license; subject to the terms in Section 7 (“Components Under Other Licenses”); + +2.8 Use the Software or Derivatives for the purpose of developing competing products or technologies +or assist a third party in such activities; + +2.9 Replace any Software components governed by this Agreement with other software that +implements NVIDIA APIs; + +2.10 Use the Software or Derivatives in violation of any applicable law or regulation in the relevant +jurisdictions; or + +2.11 Use the Software in or with any system or application where the use or failure of such system or +application developed or deployed with Software could result in injury, death or catastrophic +damage (“Mission Critical Applications”). NVIDIA will not be liable to you or any third party, in whole +or in part, for any claims or damages arising from uses in Mission Critical Applications. + +2.12 Disclose any evaluation or test results regarding the Software or Derivatives without NVIDIA’s prior +written consent. + +3. Authorized Users. You may allow employees and contractors of your entity or of your subsidiary(ies), and +for educational institutions also enrolled students, to internally access and use the Software as authorized +by this Agreement from your secure network to perform the work authorized by this Agreement on your +behalf. You are responsible for the compliance with the terms of this Agreement by your authorized users. +Any act or omission that if committed by you would constitute a breach of this Agreement will be deemed to +constitute a breach of this Agreement if committed by your authorized users. + +4. Pre-Release. Software versions identified as alpha, beta, preview, early access or otherwise as pre-release +may not be fully functional, may contain errors or design flaws, and may have reduced or different security, +privacy, availability and reliability standards relative to NVIDIA commercial offerings. You use pre-release +Software at your own risk. NVIDIA did not design or test the Software for use in production or business +critical systems. NVIDIA may choose not to make available a commercial version of pre-release Software. +NVIDIA may also choose to abandon development and terminate the availability of pre-release Software at +any time without liability. + +5. Your Privacy: Collection and Use of Information. + +5.1 Privacy Policy. Please review the NVIDIA Privacy Policy, located at https://www.nvidia.com/enus/about-nvidia/privacy-policy, which explains NVIDIA’s policy for collecting and using data, as well +as visit the NVIDIA Privacy Center, located at https://www.nvidia.com/en-us/privacy-center, to +manage your consent and privacy preferences. + +5.2 Collection Purposes. You also acknowledge that the Software collects data for the following +purposes: (a) properly configure and optimize products for use with Software; and (b) improve +NVIDIA products and services. Information collected by the Software includes: (i) application +configuration; (ii) browser version; (iii) and session metadata (i.e. performance and usage +statistics). Additionally, NVIDIA may collect certain personal information, such as your name +and email address or those of your authorized users, and other information necessary to +authenticate and enable you or your authorized users’ access to the Software. Where appropriate +you will disclose to, and obtain any necessary consent from, your authorized users to allow NVIDIA +to collect such information. + +5.3 Third Party Privacy Practices. The Software may contain links to third party websites and services. +NVIDIA encourages you to review the privacy statements on those sites and services that you choose +to visit to understand how they may collect, use and share your data. NVIDIA is not responsible for +the privacy statements or practices of third-party sites or services. + +6. Updates. NVIDIA may at any time and at its option, change, discontinue, or deprecate any part, or all, of the +Software, or change or remove features or functionality, or make available patches, workarounds or other +updates to the Software. Unless the updates are provided with their separate governing terms, they are +deemed part of the Software licensed to you under this Agreement, and your continued use of the Software +is deemed acceptance of such changes. + +7. Components Under Other Licenses. The Software may include or be distributed with components provided +with separate legal notices or terms that accompany the components, such as open source software licenses +and other license terms (“Other Licenses”). The components are subject to the applicable Other Licenses, +including any proprietary notices, disclaimers, requirements and extended use rights; except that this +Agreement will prevail regarding the use of third-party open source software, unless a third-party open +source software license requires its license terms to prevail. Open source software license means any +software, data or documentation subject to any license identified as an open source license by the Open +Source Initiative (http://opensource.org), Free Software Foundation (http://www.fsf.org) or other similar +open source organization or listed by the Software Package Data Exchange (SPDX) Workgroup under the +Linux Foundation (http://www.spdx.org). + +8. Ownership. + +8.1 NVIDIA Ownership. The Software, including all intellectual property rights, is and will remain the sole +and exclusive property of NVIDIA or its licensors. Except as expressly granted in this Agreement, +(a) NVIDIA reserves all rights, interests and remedies in connection with the Software, and (b) no +other license or right is granted to you by implication, estoppel or otherwise. + +8.2 Your Ownership. Subject to the rights of NVIDIA and its suppliers in the Software, which continue to +be licensed as stated in this Agreement, even when incorporated in your products, and the extent +permitted by applicable law, as between you and NVIDIA, you hold all rights, title and interest in and +to your services, applications and Derivatives you develop as permitted in this Agreement including +their respective intellectual property rights. + +9. Feedback. You may, but are not obligated to, provide suggestions, requests, fixes, modifications, +enhancements or other feedback regarding your use of the Software (“Feedback”). Feedback, even if +designated as confidential by you, will not create any confidentiality obligation for NVIDIA or its affiliates. If +you provide Feedback, you hereby grant NVIDIA, its affiliates and its designees a nonexclusive, perpetual, +irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, under your +intellectual property rights, to publicly perform, publicly display, reproduce, use, make, have made, sell, +offer for sale, distribute (through multiple tiers of distribution), import, create derivative works of and +otherwise commercialize and exploit the Feedback at NVIDIA’s discretion. + +10. Confidentiality. You may use confidential information only to exercise your rights and perform your +obligations under this Agreement. You will not disclose, nor authorize others to disclose NVIDIA Confidential +Information to any third party, except as expressly authorized in this Agreement and as necessary for the +Purpose, without obtaining NVIDIA’s prior written approval. Each recipient of confidential information must +be subject to a written agreement that includes confidentiality obligations consistent with these terms and +must have a need to know for the Purpose. You will protect the NVIDIA Confidential Information with at +least the same degree of care that you use to protect your own similar confidential and proprietary +information, but no less than a reasonable degree of care. Confidential information includes, but is not +limited to, the Software, including its features and functionality, Derivatives, and any results of +benchmarking or other competitive analysis or regression or performance data relating to the Software. +No Publicity. You may not issue any public statements about this Agreement, disclose the Software or +Derivatives, or any information or results related to your use of the Software, without prior written approval +of NVIDIA. + +11. Term and Termination. + +11.1 Term. This Agreement has a duration of twelve (12) months starting from the date of initial +download (even if you download the same version or updates of the Software later and it is +accompanied by this Agreement or another Agreement), unless terminated earlier in accordance +with this Agreement. + +11.2 Termination for Convenience. Either party may terminate this Agreement at any time with thirty (30) +days’ advance written notice to the other party. + +11.3 Termination for Cause. If you commence or participate in any legal proceeding against NVIDIA with +respect to the Software, this Agreement will terminate immediately without notice. Either party may +terminate this Agreement upon notice for cause if: +(a) the other party fails to cure a material breach of this Agreement within ten (10) days of the +non-breaching party’s notice of the breach; or +(b) the other party breaches its confidentiality obligations or license rights under this +Agreement, which termination will be effective immediately upon written notice. + +11.4 Effect of Termination. Upon any expiration or termination of this Agreement, you will promptly +(a) stop using and return, delete or destroy NVIDIA confidential information and all Software +received under this Agreement, and (b) delete or destroy Derivatives created under this Agreement, +unless an authorized NVIDIA representative provides prior written approval that you may keep a +copy of the Derivatives solely for archival purposes. Upon written request, you will certify in writing +that you have complied with your obligations under this “Effect of Termination” Section. + +11.5 Survival. The “License Grant to NVIDIA”, “Updates”, “Components Under Other Licenses”, +“Ownership”, “Feedback”, “Confidentiality”, “No Publicity”, “Effect of Termination”, “Survival”, +“Disclaimer of Warranties”, “Limitation of Liability”, “Indemnity” and all “General” Sections of this +Agreement will survive any expiration or termination of this Agreement. + +12. Disclaimer of Warranties. THE SOFTWARE IS PROVIDED BY NVIDIA AS-IS AND WITH ALL FAULTS. TO THE +MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, NVIDIA DISCLAIMS ALL WARRANTIES AND +REPRESENTATIONS OF ANY KIND, WHETHER EXPRESS, IMPLIED OR STATUTORY, RELATING TO OR ARISING +UNDER THIS AGREEMENT, INCLUDING, WITHOUT LIMITATION, THE WARRANTIES OF TITLE, +NONINFRINGEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, USAGE OF TRADE AND +COURSE OF DEALING. NVIDIA DOES NOT WARRANT OR ASSUME RESPONSIBILITY FOR THE ACCURACY OR +COMPLETENESS OF ANY THIRD-PARTY INFORMATION, TEXT, GRAPHICS, LINKS CONTAINED IN THE +SOFTWARE. WITHOUT LIMITING THE FOREGOING, NVIDIA DOES NOT WARRANT THAT THE SOFTWARE WILL +MEET YOUR REQUIREMENTS, ANY DEFECTS OR ERRORS WILL BE CORRECTED, ANY CERTAIN CONTENT WILL +BE AVAILABLE; OR THAT THE SOFTWARE IS FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS. NO +INFORMATION OR ADVICE GIVEN BY NVIDIA WILL IN ANY WAY INCREASE THE SCOPE OF ANY WARRANTY +EXPRESSLY PROVIDED IN THIS AGREEMENT. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE +APPROPRIATENESS OF USING THE SOFTWARE OR DERIVATIVES AND ASSUME ANY RISKS ASSOCIATED WITH +YOUR USE OF THE SOFTWARE OR DERIVATIVES. + +13. Limitations of Liability. + +13.1 EXCLUSIONS. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL +NVIDIA BE LIABLE FOR ANY (A) INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL OR CONSEQUENTIAL +DAMAGES, OR (B) DAMAGES FOR THE (I) COST OF PROCURING SUBSTITUTE GOODS OR (II) LOSS OF +PROFITS, REVENUES, USE, DATA OR GOODWILL ARISING OUT OF OR RELATED TO THIS AGREEMENT, +WHETHER BASED ON BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, OR +OTHERWISE, AND EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND +EVEN IF A PARTY’S REMEDIES FAIL THEIR ESSENTIAL PURPOSE. + +13.2 DAMAGES CAP. ADDITIONALLY, TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, +NVIDIA’S TOTAL CUMULATIVE AGGREGATE LIABILITY FOR ANY AND ALL LIABILITIES, OBLIGATIONS OR +CLAIMS ARISING OUT OF OR RELATED TO THIS AGREEMENT WILL NOT EXCEED ONE HUNDRED U.S. +DOLLARS (US$100). + +14. Indemnity. You will defend, indemnify and hold harmless NVIDIA and its affiliates, and their respective +employees, contractors, agents, officers and directors, from and against any and all third party claims, +damages, obligations, losses, liabilities, costs or debt, fines, restitutions and expenses (including but not +limited to attorney’s fees and costs incident to establishing the right of indemnification) arising out of use of +the Software and Derivatives outside of the scope of this Agreement or in breach of the terms of this +Agreement. + +15. General. + +15.1 Governing Law and Jurisdiction. This Agreement will be governed in all respects by the laws of the +United States and the laws of the State of Delaware, without regard to conflict of laws principles or +the United Nations Convention on Contracts for the International Sale of Goods. The state and +federal courts residing in Santa Clara County, California will have exclusive jurisdiction over any +dispute or claim arising out of or related to this Agreement, and the parties irrevocably consent to +personal jurisdiction and venue in those courts; except that either party may apply for injunctive +remedies or an equivalent type of urgent legal relief in any jurisdiction. + +15.2 Independent Contractors. The parties are independent contractors, and this Agreement does not +create a joint venture, partnership, agency or other form of business association between the +parties. Neither party will have the power to bind the other party or incur any obligation on its +behalf without the other party’s prior written consent. Nothing in this Agreement prevents either +party from participating in similar arrangements with third parties. + +15.3 No Assignment. NVIDIA may assign, delegate or transfer its rights or obligations under this +Agreement by any means or operation of law. You may not, without NVIDIA’s prior written consent, +assign, delegate or transfer any of your rights or obligations under this Agreement by any means or +operation of law, and any attempt to do so is null and void. + +15.4 No Waiver. No failure or delay by a party to enforce any term or obligation of this Agreement will +operate as a waiver by that party, or prevent the enforcement of such term or obligation later. + +15.5 Trade Compliance. You agree to comply with all applicable export, import, trade and economic +sanctions laws and regulations, as amended, including without limitation U.S. Export Administration +Regulations and Office of Foreign Assets Control regulations. You confirm (a) your understanding +that export or reexport of certain NVIDIA products or technologies may require a license or other +approval from appropriate authorities and (b) that you will not export or reexport any products or +technology, directly or indirectly, without first obtaining any required license or other approval from +appropriate authorities, (i) to any countries that are subject to any U.S. or local export restrictions +(currently including, but not necessarily limited to, Belarus, Cuba, Iran, North Korea, Russia, Syria, +the Region of Crimea, Donetsk People’s Republic Region and Luhansk People’s Republic Region); (ii) +to any end-user who you know or have reason to know will utilize them in the design, development +or production of nuclear, chemical or biological weapons, missiles, rocket systems, unmanned air +vehicles capable of a maximum range of at least 300 kilometers, regardless of payload, or intended +for military end-use, or any weapons of mass destruction; (iii) to any end-user who has been +prohibited from participating in the U.S. or local export transactions by any governing authority; or +(iv) to any known military or military-intelligence end-user or for any known military or military- +intelligence end-use in accordance with U.S. trade compliance laws and regulations. + +15.6 Government Rights. The Software, documentation and technology (“Protected Items”) are +“Commercial products” as this term is defined at 48 C.F.R. 2.101, consisting of “commercial +computer software” and “commercial computer software documentation” as such terms are used +in, respectively, 48 C.F.R. 12.212 and 48 C.F.R. 227.7202 & 252.227-7014(a)(1). Before any Protected +Items are supplied to the U.S. Government, you will (i) inform the U.S. Government in writing that +the Protected Items are and must be treated as commercial computer software and commercial +computer software documentation developed at private expense; (ii) inform the U.S. Government +that the Protected Items are provided subject to the terms of the Agreement; and (iii) mark the +Protected Items as commercial computer software and commercial computer software +documentation developed at private expense. In no event will you permit the U.S. Government to +acquire rights in Protected Items beyond those specified in 48 C.F.R. 52.227-19(b)(1)-(2) or 252.227- +7013(c) except as expressly approved by NVIDIA in writing. + +15.7 Notices. Please direct your legal notices or other correspondence to legalnotices@nvidia.com with a +copy mailed to NVIDIA Corporation, 2788 San Tomas Expressway, Santa Clara, California 95051, +United States of America, Attention: Legal Department. If NVIDIA needs to contact you about the +Software, you consent to receive the notices by email and agree that such notices will satisfy any +legal communication requirements. + +15.8 Severability. If a court of competent jurisdiction rules that a provision of this Agreement is +unenforceable, that provision will be deemed modified to the extent necessary to make it +enforceable and the remainder of this Agreement will continue in full force and effect. + +15.9 Amendment. Any amendment to this Agreement must be in writing and signed by authorized +representatives of both parties. + +15.10 Entire Agreement. Regarding the subject matter of this Agreement, the parties agree that (a) this +Agreement constitutes the entire and exclusive agreement between the parties and supersedes all +prior and contemporaneous communications and (b) any additional or different terms or conditions, +whether contained in purchase orders, order acknowledgments, invoices or otherwise, will not be +binding and are null and void. + +(v. February 25, 2025) + +NVIDIA Confidential \ No newline at end of file diff --git a/deps/cloudxr/docs/media/accept-cert.png b/deps/cloudxr/docs/media/accept-cert.png new file mode 100644 index 0000000..cce2a7f Binary files /dev/null and b/deps/cloudxr/docs/media/accept-cert.png differ diff --git a/deps/cloudxr/docs/media/accept-proxy-cert.png b/deps/cloudxr/docs/media/accept-proxy-cert.png new file mode 100644 index 0000000..4775b26 Binary files /dev/null and b/deps/cloudxr/docs/media/accept-proxy-cert.png differ diff --git a/deps/cloudxr/docs/media/cloudxr_viewport.jpg b/deps/cloudxr/docs/media/cloudxr_viewport.jpg new file mode 100644 index 0000000..8c41294 Binary files /dev/null and b/deps/cloudxr/docs/media/cloudxr_viewport.jpg differ diff --git a/deps/cloudxr/docs/media/iwer-buttons.png b/deps/cloudxr/docs/media/iwer-buttons.png new file mode 100644 index 0000000..196548e Binary files /dev/null and b/deps/cloudxr/docs/media/iwer-buttons.png differ diff --git a/deps/cloudxr/docs/media/iwer-message.png b/deps/cloudxr/docs/media/iwer-message.png new file mode 100644 index 0000000..d370940 Binary files /dev/null and b/deps/cloudxr/docs/media/iwer-message.png differ diff --git a/deps/cloudxr/docs/media/ov-ar-panel.png b/deps/cloudxr/docs/media/ov-ar-panel.png new file mode 100644 index 0000000..8ffb439 Binary files /dev/null and b/deps/cloudxr/docs/media/ov-ar-panel.png differ diff --git a/deps/cloudxr/docs/media/react-isaac-sample-controls-countdown.jpg b/deps/cloudxr/docs/media/react-isaac-sample-controls-countdown.jpg new file mode 100644 index 0000000..e15fa17 Binary files /dev/null and b/deps/cloudxr/docs/media/react-isaac-sample-controls-countdown.jpg differ diff --git a/deps/cloudxr/docs/media/react-isaac-sample-controls-running.jpg b/deps/cloudxr/docs/media/react-isaac-sample-controls-running.jpg new file mode 100644 index 0000000..703d9e3 Binary files /dev/null and b/deps/cloudxr/docs/media/react-isaac-sample-controls-running.jpg differ diff --git a/deps/cloudxr/docs/media/react-isaac-sample-controls-start.jpg b/deps/cloudxr/docs/media/react-isaac-sample-controls-start.jpg new file mode 100644 index 0000000..8140f22 Binary files /dev/null and b/deps/cloudxr/docs/media/react-isaac-sample-controls-start.jpg differ diff --git a/deps/cloudxr/docs/media/startar.png b/deps/cloudxr/docs/media/startar.png new file mode 100644 index 0000000..4e30339 Binary files /dev/null and b/deps/cloudxr/docs/media/startar.png differ diff --git a/deps/cloudxr/docs/modules.html b/deps/cloudxr/docs/modules.html new file mode 100644 index 0000000..1e756f9 --- /dev/null +++ b/deps/cloudxr/docs/modules.html @@ -0,0 +1 @@ +CloudXR.js SDK Documentation - v6.0.0-beta
diff --git a/deps/cloudxr/docs/modules/CloudXR.html b/deps/cloudxr/docs/modules/CloudXR.html new file mode 100644 index 0000000..b2db824 --- /dev/null +++ b/deps/cloudxr/docs/modules/CloudXR.html @@ -0,0 +1 @@ +CloudXR | CloudXR.js SDK Documentation - v6.0.0-beta
diff --git a/deps/cloudxr/helpers/BrowserCapabilities.ts b/deps/cloudxr/helpers/BrowserCapabilities.ts new file mode 100644 index 0000000..dfa45d8 --- /dev/null +++ b/deps/cloudxr/helpers/BrowserCapabilities.ts @@ -0,0 +1,125 @@ +interface CapabilityCheck { + name: string; + required: boolean; + check: () => boolean | Promise; + message: string; +} + +const capabilities: CapabilityCheck[] = [ + { + name: 'WebGL2', + required: true, + check: () => { + const canvas = document.createElement('canvas'); + const gl = canvas.getContext('webgl2'); + return gl !== null; + }, + message: 'WebGL2 is required for rendering', + }, + { + name: 'WebXR', + required: true, + check: () => { + return 'xr' in navigator; + }, + message: 'WebXR is required for VR/AR functionality', + }, + { + name: 'RTCPeerConnection', + required: true, + check: () => { + return 'RTCPeerConnection' in window; + }, + message: 'RTCPeerConnection is required for WebRTC streaming', + }, + { + name: 'requestVideoFrameCallback', + required: true, + check: () => { + const video = document.createElement('video'); + return typeof video.requestVideoFrameCallback === 'function'; + }, + message: 'HTMLVideoElement.requestVideoFrameCallback is required for video frame processing', + }, + { + name: 'Canvas.captureStream', + required: true, + check: () => { + const canvas = document.createElement('canvas'); + return typeof canvas.captureStream === 'function'; + }, + message: 'Canvas.captureStream is required for video streaming', + }, + { + name: 'AV1 Codec Support', + required: false, + check: async () => { + try { + // Check if MediaCapabilities API is available + if (!navigator.mediaCapabilities) { + return false; + } + + // Check MediaCapabilities for AV1 decoding support + const config = { + type: 'webrtc' as MediaDecodingType, + video: { + contentType: 'video/av1', + width: 1920, + height: 1080, + framerate: 60, + bitrate: 15000000, // 15 Mbps + }, + }; + + const result = await navigator.mediaCapabilities.decodingInfo(config); + return result.supported; + } catch (error) { + console.warn('Error checking AV1 support:', error); + return false; + } + }, + message: 'AV1 codec support is recommended for optimal streaming quality', + }, +]; + +export async function checkCapabilities(): Promise<{ + success: boolean; + failures: string[]; + warnings: string[]; +}> { + const failures: string[] = []; + const warnings: string[] = []; + const requiredFailures: string[] = []; + + for (const capability of capabilities) { + try { + const result = await Promise.resolve(capability.check()); + if (!result) { + if (capability.required) { + requiredFailures.push(capability.message); + console.error(`Required capability missing: ${capability.message}`); + } else { + warnings.push(capability.message); + console.warn(`Optional capability missing: ${capability.message}`); + } + failures.push(capability.message); + } + } catch (error) { + if (capability.required) { + requiredFailures.push(capability.message); + console.error(`Error checking required capability ${capability.name}:`, error); + } else { + warnings.push(capability.message); + console.warn(`Error checking optional capability ${capability.name}:`, error); + } + failures.push(capability.message); + } + } + + return { + success: requiredFailures.length === 0, + failures, + warnings, + }; +} diff --git a/deps/cloudxr/helpers/LoadIWER.ts b/deps/cloudxr/helpers/LoadIWER.ts new file mode 100644 index 0000000..479e5b4 --- /dev/null +++ b/deps/cloudxr/helpers/LoadIWER.ts @@ -0,0 +1,98 @@ +const IWER_version = '2.1.1'; +const IWER_DEVUI_version = '1.1.2'; + +export interface IWERLoadResult { + supportsImmersive: boolean; + iwerLoaded: boolean; +} + +export async function loadIWERIfNeeded(): Promise { + let supportsImmersive = false; + let iwerLoaded = false; + + if ('xr' in navigator) { + try { + const vr = await (navigator.xr as XRSystem).isSessionSupported?.('immersive-vr'); + const ar = await (navigator.xr as XRSystem).isSessionSupported?.('immersive-ar'); + supportsImmersive = Boolean(vr || ar); + } catch (_) {} + } + + if (!supportsImmersive) { + console.info('Immersive mode not supported, loading IWER as fallback.'); + + // Load IWER first + const script = document.createElement('script'); + script.src = `https://unpkg.com/iwer@${IWER_version}/build/iwer.min.js`; + script.async = true; + script.integrity = 'sha384-ZOdYbNlfA4q9jkBGcdmjy2ZYmjxy2uzncU6it3cPOHi12/WF048bamSU0Z5N+V5u'; + script.crossOrigin = 'anonymous'; + + await new Promise(resolve => { + script.onload = async () => { + console.info('IWER loaded as fallback.'); + const IWERGlobal = (window as any).IWER || (globalThis as any).IWER; + if (!IWERGlobal) { + console.warn('IWER global not found after script load.'); + supportsImmersive = false; + resolve(); + return; + } + + // Load iwer-devui after IWER + const devUIScript = document.createElement('script'); + devUIScript.src = `https://unpkg.com/@iwer/devui@${IWER_DEVUI_version}/build/iwer-devui.min.js`; + devUIScript.async = true; + devUIScript.integrity = + 'sha384-CG/gISX6PadiSzc8i2paU7CYLVsnVJaJ0tgoVnAPq/gyiTX6bddG5rwOgMDGlq74'; + devUIScript.crossOrigin = 'anonymous'; + + await new Promise(devUIResolve => { + devUIScript.onload = () => { + console.info('IWER DevUI loaded.'); + devUIResolve(); + }; + devUIScript.onerror = error => { + console.warn('Failed to load IWER DevUI:', error); + devUIResolve(); + }; + document.head.appendChild(devUIScript); + }); + + try { + // Create XRDevice with Meta Quest 3 profile + const xrDevice = new IWERGlobal.XRDevice(IWERGlobal.metaQuest3); + + // Initialize DevUI with the XR device + const IWER_DevUI = (window as any).IWER_DevUI || (globalThis as any).IWER_DevUI; + if (IWER_DevUI?.DevUI) { + xrDevice.installDevUI(IWER_DevUI.DevUI); + console.info('IWER DevUI initialized with XR device.'); + } else { + console.warn('IWER DevUI not found after script load, continuing without DevUI.'); + } + + // Install the runtime and wait for it to be ready + const maybePromise = xrDevice.installRuntime?.(); + if (maybePromise && typeof maybePromise.then === 'function') { + await maybePromise; + } + supportsImmersive = true; + iwerLoaded = true; + } catch (e) { + console.warn('IWER runtime install failed:', e); + supportsImmersive = false; + } + resolve(); + }; + script.onerror = () => { + console.warn('Failed to load IWER.'); + supportsImmersive = false; + resolve(); + }; + document.head.appendChild(script); + }); + } + + return { supportsImmersive, iwerLoaded }; +} diff --git a/deps/cloudxr/helpers/WebGLState.ts b/deps/cloudxr/helpers/WebGLState.ts new file mode 100644 index 0000000..638c31f --- /dev/null +++ b/deps/cloudxr/helpers/WebGLState.ts @@ -0,0 +1,2685 @@ +/** + * Sentinel value to represent undefined GL state. + * This allows distinguishing between "not set" (GLUndefined) and "set to null" (null). + */ +export const GLUndefined = {} as const; + +/** + * Type representing the GLUndefined sentinel value. + */ +export type GLUndefined = typeof GLUndefined; + +/** + * Checks if a value is defined (not GLUndefined). + * @param val - The value to check + * @returns true if the value is defined, false if it's GLUndefined + */ +export function isDefined(val: any): boolean { + return val !== GLUndefined; +} + +/** + * WebGL maximum array sizes + * These are conservative minimum values guaranteed by the WebGL spec + */ +export const GL_MAX_VERTEX_ATTRIBS = 16; // Minimum guaranteed by WebGL 2.0 +export const GL_MAX_UNIFORM_BUFFER_BINDINGS = 36; // Minimum guaranteed by WebGL 2.0 +export const GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = 4; // Minimum guaranteed by WebGL 2.0 +export const GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = 32; // Minimum guaranteed by WebGL 2.0 +export const GL_MAX_COLOR_ATTACHMENTS = 8; // Minimum guaranteed by WebGL 2.0 (typically 4, but commonly 8+) + +/** + * Generic dictionary/array container for indexed state with cloning support. + * Used for storing WebGL state indexed by number keys. + * @template T - The type of values stored in the dictionary, must have a clone() method + */ +export class GLAttributeArray { + [index: number]: T | GLUndefined; + private _size: number; + + constructor(size: number) { + this._size = size; + for (let i = 0; i < size; i++) { + this[i] = GLUndefined; + } + } + + get(index: number): T | GLUndefined { + return this[index]; + } + + set(index: number, value: T | GLUndefined): void { + this[index] = value; + } + + clone(): GLAttributeArray { + const cloned = new GLAttributeArray(this._size); + for (let i = 0; i < this._size; i++) { + cloned[i] = this[i] !== GLUndefined ? (this[i] as T).clone() : this[i]; + } + return cloned; + } + + equals(other: GLAttributeArray): boolean { + if (this._size !== other._size) return false; + for (let i = 0; i < this._size; i++) { + const thisVal = this[i]; + const otherVal = other[i]; + if (thisVal === GLUndefined && otherVal === GLUndefined) continue; + if (thisVal === GLUndefined || otherVal === GLUndefined) return false; + if (!(thisVal as T).equals(otherVal as T)) return false; + } + return true; + } +} + +/** + * Generic dictionary container for string-keyed state with cloning support. + * Used for storing WebGL state indexed by string keys (e.g., texture unit names). + * @template T - The type of values stored in the dictionary, must have a clone() method + */ +export class GLUnitMap { + [key: string]: T | any; + + get(key: string): T | GLUndefined { + return this[key] !== undefined ? this[key] : GLUndefined; + } + + set(key: string, value: T): void { + this[key] = value; + } + + clone(): GLUnitMap { + const cloned = new GLUnitMap(); + for (const [key, value] of Object.entries(this)) { + if (value && typeof value === 'object' && 'clone' in value) { + cloned[key] = value.clone(); + } + } + return cloned; + } + + equals(other: GLUnitMap): boolean { + const thisKeys = Object.keys(this); + const otherKeys = Object.keys(other); + if (thisKeys.length !== otherKeys.length) return false; + return thisKeys.every(key => { + const thisVal = this.get(key); + const otherVal = other.get(key); + if (thisVal === GLUndefined && otherVal === GLUndefined) return true; + if (thisVal === GLUndefined || otherVal === GLUndefined) return false; + return (thisVal as T).equals(otherVal as T); + }); + } +} + +/** + * WebGL State interfaces and state-only WebGL context + * + * This file contains the comprehensive WebGL state structure and a WebGLStateTracker + * class that mimics the WebGL2RenderingContext interface but only updates state + * without making actual WebGL calls. + * + * IMPORTANT: This tracker only tracks the state associated with DEFAULT/NULL objects. + * + * Per the WebGL 2.0 spec, state falls into three categories: + * + * 1. ALWAYS TRACKED (Global Context State): + * - Active texture unit (ACTIVE_TEXTURE) + * - Bound objects: VAO, program, framebuffer, renderbuffer, transform feedback + * - Buffer bindings: ARRAY_BUFFER, UNIFORM_BUFFER, etc. (except ELEMENT_ARRAY_BUFFER) + * - Texture bindings per unit (TEXTURE_BINDING_2D, etc.) + * - Viewport and scissor box + * - Clear colors (color, depth, stencil) + * - Enable/disable capabilities (BLEND, DEPTH_TEST, etc.) + * - Pixel store parameters (PACK_ALIGNMENT, UNPACK_FLIP_Y_WEBGL, etc.) + * - Blend state (equations, functions, color) + * - Depth state (func, range, mask) + * - Stencil state (func, ops, masks) + * - Color write mask + * - Cull face mode and front face + * - Line width + * - Polygon offset + * - Sample coverage + * + * 2. ONLY TRACKED WHEN DEFAULT VAO IS BOUND (Per-VAO State): + * - ELEMENT_ARRAY_BUFFER binding + * - Vertex attribute arrays (enabled/disabled per attribute index) + * - Vertex attribute pointers (buffer binding, size, type, stride, offset, normalized, divisor) + * + * IMPORTANT: ARRAY_BUFFER is NOT per-VAO! It's always global. + * However, the buffer associated with each vertex attribute (captured when + * vertexAttribPointer is called) IS per-VAO state. + * + * Note: When a non-default VAO is bound, we DON'T track these as we don't maintain + * per-VAO state. State updates to these will be silently accepted but not tracked. + * + * 3. ONLY TRACKED WHEN DEFAULT FRAMEBUFFER IS BOUND (Per-Framebuffer State): + * - Attachments (COLOR_ATTACHMENT0-15, DEPTH_ATTACHMENT, STENCIL_ATTACHMENT, DEPTH_STENCIL_ATTACHMENT) + * via framebufferTexture2D, framebufferRenderbuffer, framebufferTextureLayer + * - Draw buffers (drawBuffers) - which color attachments are written to + * - Read buffer (readBuffer) - which color attachment is read from + * Note: When a non-default framebuffer is bound, we DON'T track these as we don't + * maintain per-framebuffer state. State updates will be silently accepted but not tracked. + * + * 4. INDEXED BUFFER BINDINGS (Global Context State, Always Tracked): + * - Uniform buffer indexed bindings (bindBufferBase/Range with UNIFORM_BUFFER) + * - Transform feedback buffer indexed bindings (bindBufferBase/Range with TRANSFORM_FEEDBACK_BUFFER) + * Note: These are GLOBAL state, not per-object. bindBufferBase/Range updates BOTH + * the indexed binding and the generic binding. + * + * This is a deliberate simplification for tracking "current active context state" without + * the complexity of per-object state management. + */ + +/** + * Comprehensive WebGL state structure + */ +export class WebGLTextureUnitState { + texture2D: WebGLTexture | null | GLUndefined = GLUndefined; + textureCubeMap: WebGLTexture | null | GLUndefined = GLUndefined; + texture3D: WebGLTexture | null | GLUndefined = GLUndefined; + texture2DArray: WebGLTexture | null | GLUndefined = GLUndefined; + + clone(): WebGLTextureUnitState { + const cloned = new WebGLTextureUnitState(); + cloned.texture2D = this.texture2D; + cloned.textureCubeMap = this.textureCubeMap; + cloned.texture3D = this.texture3D; + cloned.texture2DArray = this.texture2DArray; + return cloned; + } + + equals(other: WebGLTextureUnitState): boolean { + return ( + this.texture2D === other.texture2D && + this.textureCubeMap === other.textureCubeMap && + this.texture3D === other.texture3D && + this.texture2DArray === other.texture2DArray + ); + } +} + +export class WebGLTextureState { + activeTexture: number | GLUndefined = GLUndefined; + textureUnits = new GLUnitMap(); + + clone(): WebGLTextureState { + const cloned = new WebGLTextureState(); + cloned.activeTexture = this.activeTexture; + cloned.textureUnits = this.textureUnits.clone(); + return cloned; + } + + equals(other: WebGLTextureState): boolean { + return ( + this.activeTexture === other.activeTexture && this.textureUnits.equals(other.textureUnits) + ); + } +} + +export class WebGLIndexedBufferBinding { + buffer: WebGLBuffer | null | GLUndefined = GLUndefined; + offset: number | GLUndefined = GLUndefined; + size: number | GLUndefined = GLUndefined; + + clone(): WebGLIndexedBufferBinding { + const cloned = new WebGLIndexedBufferBinding(); + cloned.buffer = this.buffer; + cloned.offset = this.offset; + cloned.size = this.size; + return cloned; + } + + equals(other: WebGLIndexedBufferBinding): boolean { + return this.buffer === other.buffer && this.offset === other.offset && this.size === other.size; + } +} + +export class WebGLBufferState { + arrayBuffer: WebGLBuffer | null | GLUndefined = GLUndefined; + + // Note: ELEMENT_ARRAY_BUFFER is NOT stored here! + // Per OpenGL ES 3.0 spec section 2.10, ELEMENT_ARRAY_BUFFER binding is per-VAO state. + // It is stored in WebGLPerVAOState.elementArrayBuffer instead. + + // Generic bindings (also affected by bindBufferBase/Range) + uniformBuffer: WebGLBuffer | null | GLUndefined = GLUndefined; + transformFeedbackBuffer: WebGLBuffer | null | GLUndefined = GLUndefined; + + // Indexed bindings (WebGL 2.0) + // These are global context state, but bindBufferBase/Range also updates the generic binding + uniformBufferBindings = new GLAttributeArray( + GL_MAX_UNIFORM_BUFFER_BINDINGS + ); + transformFeedbackBufferBindings = new GLAttributeArray( + GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS + ); + + // Other global bindings + pixelPackBuffer: WebGLBuffer | null | GLUndefined = GLUndefined; + pixelUnpackBuffer: WebGLBuffer | null | GLUndefined = GLUndefined; + copyReadBuffer: WebGLBuffer | null | GLUndefined = GLUndefined; + copyWriteBuffer: WebGLBuffer | null | GLUndefined = GLUndefined; + + clone(): WebGLBufferState { + const cloned = new WebGLBufferState(); + cloned.arrayBuffer = this.arrayBuffer; + cloned.uniformBuffer = this.uniformBuffer; + cloned.transformFeedbackBuffer = this.transformFeedbackBuffer; + cloned.pixelPackBuffer = this.pixelPackBuffer; + cloned.pixelUnpackBuffer = this.pixelUnpackBuffer; + cloned.copyReadBuffer = this.copyReadBuffer; + cloned.copyWriteBuffer = this.copyWriteBuffer; + cloned.uniformBufferBindings = this.uniformBufferBindings.clone(); + cloned.transformFeedbackBufferBindings = this.transformFeedbackBufferBindings.clone(); + return cloned; + } + + equals(other: WebGLBufferState): boolean { + return ( + this.arrayBuffer === other.arrayBuffer && + this.uniformBuffer === other.uniformBuffer && + this.transformFeedbackBuffer === other.transformFeedbackBuffer && + this.pixelPackBuffer === other.pixelPackBuffer && + this.pixelUnpackBuffer === other.pixelUnpackBuffer && + this.copyReadBuffer === other.copyReadBuffer && + this.copyWriteBuffer === other.copyWriteBuffer && + this.uniformBufferBindings.equals(other.uniformBufferBindings) && + this.transformFeedbackBufferBindings.equals(other.transformFeedbackBufferBindings) + ); + } +} + +export class WebGLProgramState { + currentProgram: WebGLProgram | null | GLUndefined = GLUndefined; + + clone(): WebGLProgramState { + const cloned = new WebGLProgramState(); + cloned.currentProgram = this.currentProgram; + return cloned; + } + + equals(other: WebGLProgramState): boolean { + return this.currentProgram === other.currentProgram; + } +} + +export class WebGLFramebufferAttachment { + attachmentType: 'texture' | 'renderbuffer' | null | GLUndefined = GLUndefined; + texture: WebGLTexture | null | GLUndefined = GLUndefined; + renderbuffer: WebGLRenderbuffer | null | GLUndefined = GLUndefined; + textureLevel: number | GLUndefined = GLUndefined; + textureLayer: number | GLUndefined = GLUndefined; // For 3D textures or texture arrays + textureCubeFace: number | GLUndefined = GLUndefined; // For cubemap faces + + clone(): WebGLFramebufferAttachment { + const cloned = new WebGLFramebufferAttachment(); + cloned.attachmentType = this.attachmentType; + cloned.texture = this.texture; + cloned.renderbuffer = this.renderbuffer; + cloned.textureLevel = this.textureLevel; + cloned.textureLayer = this.textureLayer; + cloned.textureCubeFace = this.textureCubeFace; + return cloned; + } + + equals(other: WebGLFramebufferAttachment): boolean { + return ( + this.attachmentType === other.attachmentType && + this.texture === other.texture && + this.renderbuffer === other.renderbuffer && + this.textureLevel === other.textureLevel && + this.textureLayer === other.textureLayer && + this.textureCubeFace === other.textureCubeFace + ); + } +} + +export class WebGLFramebufferAttachments { + // Color attachments (typically 0-7, but can query MAX_COLOR_ATTACHMENTS) + [key: string]: WebGLFramebufferAttachment; // e.g., "COLOR_ATTACHMENT0" +} + +export class WebGLDefaultFramebufferState { + // Attachments for the default framebuffer (only valid when framebuffer === null) + colorAttachments: WebGLFramebufferAttachments = new WebGLFramebufferAttachments(); + depthAttachment: WebGLFramebufferAttachment | null | GLUndefined = GLUndefined; + stencilAttachment: WebGLFramebufferAttachment | null | GLUndefined = GLUndefined; + depthStencilAttachment: WebGLFramebufferAttachment | null | GLUndefined = GLUndefined; + // Draw buffers array (which color attachments are written to) + drawBuffers: number[] | GLUndefined = GLUndefined; + // Read buffer (which color attachment is read from) + readBuffer: number | GLUndefined = GLUndefined; + + clone(): WebGLDefaultFramebufferState { + const cloned = new WebGLDefaultFramebufferState(); + // Clone color attachments + for (const [key, value] of Object.entries(this.colorAttachments)) { + if (value instanceof WebGLFramebufferAttachment) { + cloned.colorAttachments[key] = value.clone(); + } + } + cloned.depthAttachment = this.depthAttachment; + cloned.stencilAttachment = this.stencilAttachment; + cloned.depthStencilAttachment = this.depthStencilAttachment; + cloned.drawBuffers = Array.isArray(this.drawBuffers) ? [...this.drawBuffers] : this.drawBuffers; + cloned.readBuffer = this.readBuffer; + return cloned; + } + + equals(other: WebGLDefaultFramebufferState): boolean { + // Compare color attachments + const thisKeys = Object.keys(this.colorAttachments); + const otherKeys = Object.keys(other.colorAttachments); + if (thisKeys.length !== otherKeys.length) return false; + const colorAttachmentsEqual = thisKeys.every(key => { + const thisAttach = this.colorAttachments[key]; + const otherAttach = other.colorAttachments[key]; + if (!thisAttach || !otherAttach) return thisAttach === otherAttach; + return thisAttach.equals(otherAttach); + }); + + // Compare draw buffers + const drawBuffersEqual = + this.drawBuffers === other.drawBuffers || + (Array.isArray(this.drawBuffers) && + Array.isArray(other.drawBuffers) && + this.drawBuffers.length === other.drawBuffers.length && + this.drawBuffers.every((v, i) => v === (other.drawBuffers as number[])[i])); + + return ( + colorAttachmentsEqual && + this.depthAttachment === other.depthAttachment && + this.stencilAttachment === other.stencilAttachment && + this.depthStencilAttachment === other.depthStencilAttachment && + drawBuffersEqual && + this.readBuffer === other.readBuffer + ); + } +} + +export class WebGLFramebufferState { + drawFramebuffer: WebGLFramebuffer | null | GLUndefined = GLUndefined; + readFramebuffer: WebGLFramebuffer | null | GLUndefined = GLUndefined; + framebuffer: WebGLFramebuffer | null | GLUndefined = GLUndefined; + // State for the default framebuffer (only tracked when framebuffer === null) + defaultFramebufferState: WebGLDefaultFramebufferState = new WebGLDefaultFramebufferState(); + + clone(): WebGLFramebufferState { + const cloned = new WebGLFramebufferState(); + cloned.drawFramebuffer = this.drawFramebuffer; + cloned.readFramebuffer = this.readFramebuffer; + cloned.framebuffer = this.framebuffer; + cloned.defaultFramebufferState = this.defaultFramebufferState.clone(); + return cloned; + } + + equals(other: WebGLFramebufferState): boolean { + return ( + this.drawFramebuffer === other.drawFramebuffer && + this.readFramebuffer === other.readFramebuffer && + this.framebuffer === other.framebuffer && + this.defaultFramebufferState.equals(other.defaultFramebufferState) + ); + } +} + +export class WebGLVertexAttribState { + enabled: boolean | GLUndefined = GLUndefined; + buffer: WebGLBuffer | null | GLUndefined = GLUndefined; + size: number | GLUndefined = GLUndefined; + type: number | GLUndefined = GLUndefined; + normalized: boolean | GLUndefined = GLUndefined; + stride: number | GLUndefined = GLUndefined; + offset: number | GLUndefined = GLUndefined; + divisor: number | GLUndefined = GLUndefined; + + clone(): WebGLVertexAttribState { + const cloned = new WebGLVertexAttribState(); + cloned.enabled = this.enabled; + cloned.buffer = this.buffer; + cloned.size = this.size; + cloned.type = this.type; + cloned.normalized = this.normalized; + cloned.stride = this.stride; + cloned.offset = this.offset; + cloned.divisor = this.divisor; + return cloned; + } + + equals(other: WebGLVertexAttribState): boolean { + return ( + this.enabled === other.enabled && + this.buffer === other.buffer && + this.size === other.size && + this.type === other.type && + this.normalized === other.normalized && + this.stride === other.stride && + this.offset === other.offset && + this.divisor === other.divisor + ); + } +} + +/** + * Per-VAO state - according to OpenGL ES 3.0 spec section 2.10, + * ELEMENT_ARRAY_BUFFER binding is part of VAO state + */ +export class WebGLPerVAOState { + // ELEMENT_ARRAY_BUFFER binding is per-VAO (spec: section 2.10, table 6.2) + elementArrayBuffer: WebGLBuffer | null | GLUndefined = GLUndefined; + // Vertex attribute state is also per-VAO + attributes = new GLAttributeArray(GL_MAX_VERTEX_ATTRIBS); + + clone(): WebGLPerVAOState { + const cloned = new WebGLPerVAOState(); + cloned.elementArrayBuffer = this.elementArrayBuffer; + cloned.attributes = this.attributes.clone(); + return cloned; + } + + equals(other: WebGLPerVAOState): boolean { + return ( + this.elementArrayBuffer === other.elementArrayBuffer && + this.attributes.equals(other.attributes) + ); + } +} + +export class WebGLVertexArrayState { + // Currently bound VAO (null = default VAO) + vertexArrayObject: WebGLVertexArrayObject | null | GLUndefined = GLUndefined; + + // Per-VAO state storage + // Key: VAO object (null represents default VAO) + // Value: Per-VAO state including ELEMENT_ARRAY_BUFFER and attributes + vaoStates: Map = new Map(); + + clone(): WebGLVertexArrayState { + const cloned = new WebGLVertexArrayState(); + cloned.vertexArrayObject = this.vertexArrayObject; + + // Clone all VAO states + for (const [vao, state] of this.vaoStates.entries()) { + cloned.vaoStates.set(vao, state.clone()); + } + + return cloned; + } + + equals(other: WebGLVertexArrayState): boolean { + if (this.vertexArrayObject !== other.vertexArrayObject) return false; + if (this.vaoStates.size !== other.vaoStates.size) return false; + + // Compare all VAO states + for (const [vao, state] of this.vaoStates.entries()) { + const otherState = other.vaoStates.get(vao); + if (!otherState || !state.equals(otherState)) return false; + } + + return true; + } + + /** + * Get or create the state for the currently bound VAO + */ + getCurrentVAOState(): WebGLPerVAOState { + const vao = this.vertexArrayObject === GLUndefined ? null : this.vertexArrayObject; + + if (!this.vaoStates.has(vao)) { + this.vaoStates.set(vao, new WebGLPerVAOState()); + } + + return this.vaoStates.get(vao)!; + } +} + +export class WebGLViewportState { + viewport: Int32Array | GLUndefined = GLUndefined; + scissorBox: Int32Array | GLUndefined = GLUndefined; + + clone(): WebGLViewportState { + const cloned = new WebGLViewportState(); + cloned.viewport = + this.viewport !== GLUndefined ? new Int32Array(this.viewport as Int32Array) : this.viewport; + cloned.scissorBox = + this.scissorBox !== GLUndefined + ? new Int32Array(this.scissorBox as Int32Array) + : this.scissorBox; + return cloned; + } + + equals(other: WebGLViewportState): boolean { + const viewportEqual = + this.viewport === other.viewport || + (this.viewport !== GLUndefined && + other.viewport !== GLUndefined && + (this.viewport as Int32Array).every((v, i) => v === (other.viewport as Int32Array)[i])); + const scissorEqual = + this.scissorBox === other.scissorBox || + (this.scissorBox !== GLUndefined && + other.scissorBox !== GLUndefined && + (this.scissorBox as Int32Array).every((v, i) => v === (other.scissorBox as Int32Array)[i])); + return viewportEqual && scissorEqual; + } +} + +export class WebGLClearState { + colorClearValue: Float32Array | GLUndefined = GLUndefined; + depthClearValue: number | GLUndefined = GLUndefined; + stencilClearValue: number | GLUndefined = GLUndefined; + + clone(): WebGLClearState { + const cloned = new WebGLClearState(); + cloned.colorClearValue = + this.colorClearValue !== GLUndefined + ? new Float32Array(this.colorClearValue as Float32Array) + : this.colorClearValue; + cloned.depthClearValue = this.depthClearValue; + cloned.stencilClearValue = this.stencilClearValue; + return cloned; + } + + equals(other: WebGLClearState): boolean { + const colorEqual = + this.colorClearValue === other.colorClearValue || + (this.colorClearValue !== GLUndefined && + other.colorClearValue !== GLUndefined && + (this.colorClearValue as Float32Array).every( + (v, i) => v === (other.colorClearValue as Float32Array)[i] + )); + return ( + colorEqual && + this.depthClearValue === other.depthClearValue && + this.stencilClearValue === other.stencilClearValue + ); + } +} + +export class WebGLCapabilityState { + blend: boolean | GLUndefined = GLUndefined; + cullFace: boolean | GLUndefined = GLUndefined; + depthTest: boolean | GLUndefined = GLUndefined; + dither: boolean | GLUndefined = GLUndefined; + polygonOffsetFill: boolean | GLUndefined = GLUndefined; + sampleAlphaToCoverage: boolean | GLUndefined = GLUndefined; + sampleCoverage: boolean | GLUndefined = GLUndefined; + scissorTest: boolean | GLUndefined = GLUndefined; + stencilTest: boolean | GLUndefined = GLUndefined; + rasterDiscard: boolean | GLUndefined = GLUndefined; + + clone(): WebGLCapabilityState { + const cloned = new WebGLCapabilityState(); + Object.assign(cloned, this); + return cloned; + } + + equals(other: WebGLCapabilityState): boolean { + return ( + this.blend === other.blend && + this.cullFace === other.cullFace && + this.depthTest === other.depthTest && + this.dither === other.dither && + this.polygonOffsetFill === other.polygonOffsetFill && + this.sampleAlphaToCoverage === other.sampleAlphaToCoverage && + this.sampleCoverage === other.sampleCoverage && + this.scissorTest === other.scissorTest && + this.stencilTest === other.stencilTest && + this.rasterDiscard === other.rasterDiscard + ); + } +} + +export class WebGLPixelStoreState { + packAlignment: number | GLUndefined = GLUndefined; + unpackAlignment: number | GLUndefined = GLUndefined; + unpackFlipY: boolean | GLUndefined = GLUndefined; + unpackPremultiplyAlpha: boolean | GLUndefined = GLUndefined; + packRowLength: number | GLUndefined = GLUndefined; + packSkipPixels: number | GLUndefined = GLUndefined; + packSkipRows: number | GLUndefined = GLUndefined; + unpackRowLength: number | GLUndefined = GLUndefined; + unpackImageHeight: number | GLUndefined = GLUndefined; + unpackSkipPixels: number | GLUndefined = GLUndefined; + unpackSkipRows: number | GLUndefined = GLUndefined; + unpackSkipImages: number | GLUndefined = GLUndefined; + + clone(): WebGLPixelStoreState { + const cloned = new WebGLPixelStoreState(); + Object.assign(cloned, this); + return cloned; + } + + equals(other: WebGLPixelStoreState): boolean { + return ( + this.packAlignment === other.packAlignment && + this.unpackAlignment === other.unpackAlignment && + this.unpackFlipY === other.unpackFlipY && + this.unpackPremultiplyAlpha === other.unpackPremultiplyAlpha && + this.packRowLength === other.packRowLength && + this.packSkipPixels === other.packSkipPixels && + this.packSkipRows === other.packSkipRows && + this.unpackRowLength === other.unpackRowLength && + this.unpackImageHeight === other.unpackImageHeight && + this.unpackSkipPixels === other.unpackSkipPixels && + this.unpackSkipRows === other.unpackSkipRows && + this.unpackSkipImages === other.unpackSkipImages + ); + } +} + +export class WebGLBlendState { + blendEquationRgb: number | GLUndefined = GLUndefined; + blendEquationAlpha: number | GLUndefined = GLUndefined; + blendSrcRgb: number | GLUndefined = GLUndefined; + blendDstRgb: number | GLUndefined = GLUndefined; + blendSrcAlpha: number | GLUndefined = GLUndefined; + blendDstAlpha: number | GLUndefined = GLUndefined; + blendColor: Float32Array | GLUndefined = GLUndefined; + + clone(): WebGLBlendState { + const cloned = new WebGLBlendState(); + cloned.blendEquationRgb = this.blendEquationRgb; + cloned.blendEquationAlpha = this.blendEquationAlpha; + cloned.blendSrcRgb = this.blendSrcRgb; + cloned.blendDstRgb = this.blendDstRgb; + cloned.blendSrcAlpha = this.blendSrcAlpha; + cloned.blendDstAlpha = this.blendDstAlpha; + cloned.blendColor = + this.blendColor !== GLUndefined + ? new Float32Array(this.blendColor as Float32Array) + : this.blendColor; + return cloned; + } + + equals(other: WebGLBlendState): boolean { + const blendColorEqual = + this.blendColor === other.blendColor || + (this.blendColor !== GLUndefined && + other.blendColor !== GLUndefined && + (this.blendColor as Float32Array).every( + (v, i) => v === (other.blendColor as Float32Array)[i] + )); + return ( + this.blendEquationRgb === other.blendEquationRgb && + this.blendEquationAlpha === other.blendEquationAlpha && + this.blendSrcRgb === other.blendSrcRgb && + this.blendDstRgb === other.blendDstRgb && + this.blendSrcAlpha === other.blendSrcAlpha && + this.blendDstAlpha === other.blendDstAlpha && + blendColorEqual + ); + } +} + +export class WebGLDepthState { + depthFunc: number | GLUndefined = GLUndefined; + depthRange: Float32Array | GLUndefined = GLUndefined; + depthWritemask: boolean | GLUndefined = GLUndefined; + + clone(): WebGLDepthState { + const cloned = new WebGLDepthState(); + cloned.depthFunc = this.depthFunc; + cloned.depthRange = + this.depthRange !== GLUndefined + ? new Float32Array(this.depthRange as Float32Array) + : this.depthRange; + cloned.depthWritemask = this.depthWritemask; + return cloned; + } + + equals(other: WebGLDepthState): boolean { + const depthRangeEqual = + this.depthRange === other.depthRange || + (this.depthRange !== GLUndefined && + other.depthRange !== GLUndefined && + (this.depthRange as Float32Array).every( + (v, i) => v === (other.depthRange as Float32Array)[i] + )); + return ( + this.depthFunc === other.depthFunc && + depthRangeEqual && + this.depthWritemask === other.depthWritemask + ); + } +} + +export class WebGLStencilState { + stencilFunc: number | GLUndefined = GLUndefined; + stencilRef: number | GLUndefined = GLUndefined; + stencilValueMask: number | GLUndefined = GLUndefined; + stencilWritemask: number | GLUndefined = GLUndefined; + stencilFail: number | GLUndefined = GLUndefined; + stencilPassDepthFail: number | GLUndefined = GLUndefined; + stencilPassDepthPass: number | GLUndefined = GLUndefined; + stencilBackFunc: number | GLUndefined = GLUndefined; + stencilBackRef: number | GLUndefined = GLUndefined; + stencilBackValueMask: number | GLUndefined = GLUndefined; + stencilBackWritemask: number | GLUndefined = GLUndefined; + stencilBackFail: number | GLUndefined = GLUndefined; + stencilBackPassDepthFail: number | GLUndefined = GLUndefined; + stencilBackPassDepthPass: number | GLUndefined = GLUndefined; + + clone(): WebGLStencilState { + const cloned = new WebGLStencilState(); + Object.assign(cloned, this); + return cloned; + } + + equals(other: WebGLStencilState): boolean { + return ( + this.stencilFunc === other.stencilFunc && + this.stencilRef === other.stencilRef && + this.stencilValueMask === other.stencilValueMask && + this.stencilWritemask === other.stencilWritemask && + this.stencilFail === other.stencilFail && + this.stencilPassDepthFail === other.stencilPassDepthFail && + this.stencilPassDepthPass === other.stencilPassDepthPass && + this.stencilBackFunc === other.stencilBackFunc && + this.stencilBackRef === other.stencilBackRef && + this.stencilBackValueMask === other.stencilBackValueMask && + this.stencilBackWritemask === other.stencilBackWritemask && + this.stencilBackFail === other.stencilBackFail && + this.stencilBackPassDepthFail === other.stencilBackPassDepthFail && + this.stencilBackPassDepthPass === other.stencilBackPassDepthPass + ); + } +} + +export class WebGLColorState { + colorWritemask: boolean[] | GLUndefined = GLUndefined; + + clone(): WebGLColorState { + const cloned = new WebGLColorState(); + cloned.colorWritemask = + this.colorWritemask !== GLUndefined + ? [...(this.colorWritemask as boolean[])] + : this.colorWritemask; + return cloned; + } + + equals(other: WebGLColorState): boolean { + return ( + this.colorWritemask === other.colorWritemask || + (this.colorWritemask !== GLUndefined && + other.colorWritemask !== GLUndefined && + (this.colorWritemask as boolean[]).every( + (v, i) => v === (other.colorWritemask as boolean[])[i] + )) + ); + } +} + +export class WebGLCullingState { + cullFaceMode: number | GLUndefined = GLUndefined; + frontFace: number | GLUndefined = GLUndefined; + + clone(): WebGLCullingState { + const cloned = new WebGLCullingState(); + Object.assign(cloned, this); + return cloned; + } + + equals(other: WebGLCullingState): boolean { + return this.cullFaceMode === other.cullFaceMode && this.frontFace === other.frontFace; + } +} + +export class WebGLLineState { + lineWidth: number | GLUndefined = GLUndefined; + + clone(): WebGLLineState { + const cloned = new WebGLLineState(); + cloned.lineWidth = this.lineWidth; + return cloned; + } + + equals(other: WebGLLineState): boolean { + return this.lineWidth === other.lineWidth; + } +} + +export class WebGLPolygonOffsetState { + polygonOffsetFactor: number | GLUndefined = GLUndefined; + polygonOffsetUnits: number | GLUndefined = GLUndefined; + + clone(): WebGLPolygonOffsetState { + const cloned = new WebGLPolygonOffsetState(); + Object.assign(cloned, this); + return cloned; + } + + equals(other: WebGLPolygonOffsetState): boolean { + return ( + this.polygonOffsetFactor === other.polygonOffsetFactor && + this.polygonOffsetUnits === other.polygonOffsetUnits + ); + } +} + +export class WebGLSampleState { + sampleCoverageValue: number | GLUndefined = GLUndefined; + sampleCoverageInvert: boolean | GLUndefined = GLUndefined; + + clone(): WebGLSampleState { + const cloned = new WebGLSampleState(); + Object.assign(cloned, this); + return cloned; + } + + equals(other: WebGLSampleState): boolean { + return ( + this.sampleCoverageValue === other.sampleCoverageValue && + this.sampleCoverageInvert === other.sampleCoverageInvert + ); + } +} + +export class WebGLTransformFeedbackState { + transformFeedback: WebGLTransformFeedback | null | GLUndefined = GLUndefined; + transformFeedbackActive: boolean | GLUndefined = GLUndefined; + transformFeedbackPaused: boolean | GLUndefined = GLUndefined; + + clone(): WebGLTransformFeedbackState { + const cloned = new WebGLTransformFeedbackState(); + Object.assign(cloned, this); + return cloned; + } + + equals(other: WebGLTransformFeedbackState): boolean { + return ( + this.transformFeedback === other.transformFeedback && + this.transformFeedbackActive === other.transformFeedbackActive && + this.transformFeedbackPaused === other.transformFeedbackPaused + ); + } +} + +export class WebGLRenderbufferState { + renderbuffer: WebGLRenderbuffer | null | GLUndefined = GLUndefined; + + clone(): WebGLRenderbufferState { + const cloned = new WebGLRenderbufferState(); + cloned.renderbuffer = this.renderbuffer; + return cloned; + } + + equals(other: WebGLRenderbufferState): boolean { + return this.renderbuffer === other.renderbuffer; + } +} + +export class WebGLSamplerState { + // Sampler bindings per texture unit (WebGL 2.0) + samplerBindings: { [unit: number]: WebGLSampler | null | GLUndefined } = {}; + + clone(): WebGLSamplerState { + const cloned = new WebGLSamplerState(); + // Shallow copy is fine since samplerBindings contains WebGL objects (not cloneable state objects) + Object.assign(cloned.samplerBindings, this.samplerBindings); + return cloned; + } + + equals(other: WebGLSamplerState): boolean { + const thisKeys = Object.keys(this.samplerBindings); + const otherKeys = Object.keys(other.samplerBindings); + if (thisKeys.length !== otherKeys.length) return false; + return thisKeys.every( + key => this.samplerBindings[Number(key)] === other.samplerBindings[Number(key)] + ); + } +} + +export class WebGLQueryState { + // Active queries per target (WebGL 2.0) + // Only one query can be active per target at a time + currentOcclusionQuery: WebGLQuery | null | GLUndefined = GLUndefined; + currentTransformFeedbackPrimitivesWritten: WebGLQuery | null | GLUndefined = GLUndefined; + currentAnySamplesPassed: WebGLQuery | null | GLUndefined = GLUndefined; + currentAnySamplesPassedConservative: WebGLQuery | null | GLUndefined = GLUndefined; + + clone(): WebGLQueryState { + const cloned = new WebGLQueryState(); + Object.assign(cloned, this); + return cloned; + } + + equals(other: WebGLQueryState): boolean { + return ( + this.currentOcclusionQuery === other.currentOcclusionQuery && + this.currentTransformFeedbackPrimitivesWritten === + other.currentTransformFeedbackPrimitivesWritten && + this.currentAnySamplesPassed === other.currentAnySamplesPassed && + this.currentAnySamplesPassedConservative === other.currentAnySamplesPassedConservative + ); + } +} + +export class WebGLState { + // Texture state + textures?: WebGLTextureState; + + // Buffer state + buffers?: WebGLBufferState; + + // Program state + programs?: WebGLProgramState; + + // Framebuffer state + framebuffers?: WebGLFramebufferState; + + // Vertex array state + vertexArrays?: WebGLVertexArrayState; + + // Viewport and scissor + viewport?: WebGLViewportState; + + // Clear values + clear?: WebGLClearState; + + // Capabilities (enable/disable state) + capabilities?: WebGLCapabilityState; + + // Pixel store parameters + pixelStore?: WebGLPixelStoreState; + + // Blend state + blend?: WebGLBlendState; + + // Depth state + depth?: WebGLDepthState; + + // Stencil state + stencil?: WebGLStencilState; + + // Color state + color?: WebGLColorState; + + // Face culling + culling?: WebGLCullingState; + + // Line rendering + line?: WebGLLineState; + + // Polygon offset + polygonOffset?: WebGLPolygonOffsetState; + + // Sample coverage + sample?: WebGLSampleState; + + // Transform feedback (WebGL 2.0) + transformFeedback?: WebGLTransformFeedbackState; + + // Renderbuffer + renderbuffer?: WebGLRenderbufferState; + + // Sampler state (WebGL 2.0) + samplers?: WebGLSamplerState; + + // Query state (WebGL 2.0) + queries?: WebGLQueryState; + + // Buffer lifecycle tracking (for validation without GPU calls) + validBuffers = new Set(); + + readonly constants = new WebGLConstants(); + + // Lazy getters for state components + getTexturesState(): WebGLTextureState { + if (!this.textures) { + this.textures = new WebGLTextureState(); + this.textures.activeTexture = this.constants.TEXTURE0; + } + return this.textures; + } + + getOrCreateTextureUnit(unit: string): WebGLTextureUnitState { + const textures = this.getTexturesState(); + if (!textures.textureUnits[unit]) { + textures.textureUnits[unit] = new WebGLTextureUnitState(); + } + return textures.textureUnits[unit]; + } + + getBuffersState(): WebGLBufferState { + if (!this.buffers) { + this.buffers = new WebGLBufferState(); + } + return this.buffers; + } + + getProgramsState(): WebGLProgramState { + if (!this.programs) { + this.programs = new WebGLProgramState(); + } + return this.programs; + } + + getFramebuffersState(): WebGLFramebufferState { + if (!this.framebuffers) { + this.framebuffers = new WebGLFramebufferState(); + this.framebuffers.defaultFramebufferState.drawBuffers = [this.constants.BACK || 0x0405]; // Default to BACK + this.framebuffers.defaultFramebufferState.readBuffer = this.constants.BACK || 0x0405; + } + return this.framebuffers; + } + + getVertexArraysState(): WebGLVertexArrayState { + if (!this.vertexArrays) { + this.vertexArrays = new WebGLVertexArrayState(); + } + return this.vertexArrays; + } + + getOrCreateVertexAttrib(index: number): WebGLVertexAttribState { + const vertexArrays = this.getVertexArraysState(); + const vaoState = vertexArrays.getCurrentVAOState(); + const existing = vaoState.attributes.get(index); + if (!existing || existing === GLUndefined) { + const newAttrib = new WebGLVertexAttribState(); + vaoState.attributes.set(index, newAttrib); + return newAttrib; + } + return existing as WebGLVertexAttribState; + } + + getViewportState(): WebGLViewportState { + if (!this.viewport) { + this.viewport = new WebGLViewportState(); + } + return this.viewport; + } + + getClearState(): WebGLClearState { + if (!this.clear) { + this.clear = new WebGLClearState(); + } + return this.clear; + } + + getCapabilitiesState(): WebGLCapabilityState { + if (!this.capabilities) { + this.capabilities = new WebGLCapabilityState(); + } + return this.capabilities; + } + + getPixelStoreState(): WebGLPixelStoreState { + if (!this.pixelStore) { + this.pixelStore = new WebGLPixelStoreState(); + } + return this.pixelStore; + } + + getBlendState(): WebGLBlendState { + if (!this.blend) { + this.blend = new WebGLBlendState(); + } + return this.blend; + } + + getDepthState(): WebGLDepthState { + if (!this.depth) { + this.depth = new WebGLDepthState(); + } + return this.depth; + } + + getStencilState(): WebGLStencilState { + if (!this.stencil) { + this.stencil = new WebGLStencilState(); + } + return this.stencil; + } + + getColorState(): WebGLColorState { + if (!this.color) { + this.color = new WebGLColorState(); + } + return this.color; + } + + getCullingState(): WebGLCullingState { + if (!this.culling) { + this.culling = new WebGLCullingState(); + } + return this.culling; + } + + getLineState(): WebGLLineState { + if (!this.line) { + this.line = new WebGLLineState(); + } + return this.line; + } + + getPolygonOffsetState(): WebGLPolygonOffsetState { + if (!this.polygonOffset) { + this.polygonOffset = new WebGLPolygonOffsetState(); + } + return this.polygonOffset; + } + + getSampleState(): WebGLSampleState { + if (!this.sample) { + this.sample = new WebGLSampleState(); + } + return this.sample; + } + + getTransformFeedbackState(): WebGLTransformFeedbackState { + if (!this.transformFeedback) { + this.transformFeedback = new WebGLTransformFeedbackState(); + } + return this.transformFeedback; + } + + getRenderbufferState(): WebGLRenderbufferState { + if (!this.renderbuffer) { + this.renderbuffer = new WebGLRenderbufferState(); + } + return this.renderbuffer; + } + + getSamplersState(): WebGLSamplerState { + if (!this.samplers) { + this.samplers = new WebGLSamplerState(); + } + return this.samplers; + } + + getQueriesState(): WebGLQueryState { + if (!this.queries) { + this.queries = new WebGLQueryState(); + } + return this.queries; + } + + // Public state accessors + getState(): WebGLState { + return this; + } + + /** + * Clone the current state into a new WebGLState object + * This creates a deep clone of the state properties + * Note: WebGL objects (buffers, textures, etc.) are not cloned, only references + */ + clone(): WebGLState { + const cloned = new WebGLState(); + + // Clone each state property using their clone methods + if (this.buffers) cloned.buffers = this.buffers.clone(); + if (this.vertexArrays) cloned.vertexArrays = this.vertexArrays.clone(); + if (this.textures) cloned.textures = this.textures.clone(); + if (this.samplers) cloned.samplers = this.samplers.clone(); + if (this.programs) cloned.programs = this.programs.clone(); + if (this.framebuffers) cloned.framebuffers = this.framebuffers.clone(); + if (this.renderbuffer) cloned.renderbuffer = this.renderbuffer.clone(); + if (this.transformFeedback) cloned.transformFeedback = this.transformFeedback.clone(); + if (this.viewport) cloned.viewport = this.viewport.clone(); + if (this.capabilities) cloned.capabilities = this.capabilities.clone(); + if (this.clear) cloned.clear = this.clear.clone(); + if (this.blend) cloned.blend = this.blend.clone(); + if (this.depth) cloned.depth = this.depth.clone(); + if (this.stencil) cloned.stencil = this.stencil.clone(); + if (this.color) cloned.color = this.color.clone(); + if (this.culling) cloned.culling = this.culling.clone(); + if (this.line) cloned.line = this.line.clone(); + if (this.polygonOffset) cloned.polygonOffset = this.polygonOffset.clone(); + if (this.sample) cloned.sample = this.sample.clone(); + if (this.pixelStore) cloned.pixelStore = this.pixelStore.clone(); + if (this.queries) cloned.queries = this.queries.clone(); + + // Clone validBuffers Set + cloned.validBuffers = new Set(this.validBuffers); + + return cloned; + } +} + +/** + * WebGLStateTracker - A state-only WebGL context that mimics WebGL2RenderingContext + * + * This class provides the same interface as WebGL2RenderingContext but only updates + * internal state without making actual WebGL calls. Useful for testing, debugging, + * and state tracking without GPU dependencies. + */ +/** + * WebGL Constants + */ +export class WebGLConstants { + // Buffer targets + readonly ARRAY_BUFFER = 0x8892; + readonly ELEMENT_ARRAY_BUFFER = 0x8893; + readonly UNIFORM_BUFFER = 0x8a11; + readonly TRANSFORM_FEEDBACK_BUFFER = 0x8c8e; + readonly PIXEL_PACK_BUFFER = 0x88eb; + readonly PIXEL_UNPACK_BUFFER = 0x88ec; + readonly COPY_READ_BUFFER = 0x8f36; + readonly COPY_WRITE_BUFFER = 0x8f37; + + // Texture targets and units + readonly TEXTURE0 = 0x84c0; + readonly TEXTURE_2D = 0x0de1; + readonly TEXTURE_CUBE_MAP = 0x8513; + readonly TEXTURE_3D = 0x806f; + readonly TEXTURE_2D_ARRAY = 0x8c1a; + + // Framebuffer targets + readonly FRAMEBUFFER = 0x8d40; + readonly DRAW_FRAMEBUFFER = 0x8ca9; + readonly READ_FRAMEBUFFER = 0x8ca8; + + // Capabilities + readonly BLEND = 0x0be2; + readonly CULL_FACE = 0x0b44; + readonly DEPTH_TEST = 0x0b71; + readonly DITHER = 0x0bd0; + readonly POLYGON_OFFSET_FILL = 0x8037; + readonly SAMPLE_ALPHA_TO_COVERAGE = 0x809e; + readonly SAMPLE_COVERAGE = 0x80a0; + readonly SCISSOR_TEST = 0x0c11; + readonly STENCIL_TEST = 0x0b90; + readonly RASTERIZER_DISCARD = 0x8c89; + + // Blend equations + readonly FUNC_ADD = 0x8006; + readonly FUNC_SUBTRACT = 0x800a; + + // Blend functions + readonly ZERO = 0; + readonly ONE = 1; + readonly SRC_ALPHA = 0x0302; + readonly ONE_MINUS_SRC_ALPHA = 0x0303; + + // Depth functions + readonly NEVER = 0x0200; + readonly LESS = 0x0201; + readonly EQUAL = 0x0202; + readonly LEQUAL = 0x0203; + + // Stencil operations + readonly KEEP = 0x1e00; + readonly REPLACE = 0x1e01; + readonly INCR = 0x1e02; + + // Stencil functions + readonly ALWAYS = 0x0207; + + // Culling + readonly FRONT = 0x0404; + readonly BACK = 0x0405; + readonly FRONT_AND_BACK = 0x0408; + readonly CW = 0x0900; + readonly CCW = 0x0901; + + // Pixel store parameters + readonly PACK_ALIGNMENT = 0x0d05; + readonly UNPACK_ALIGNMENT = 0x0cf5; + readonly UNPACK_FLIP_Y_WEBGL = 0x9240; + readonly UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241; + readonly PACK_ROW_LENGTH = 0x0d02; + readonly PACK_SKIP_PIXELS = 0x0d04; + readonly PACK_SKIP_ROWS = 0x0d03; + readonly UNPACK_ROW_LENGTH = 0x0cf2; + readonly UNPACK_IMAGE_HEIGHT = 0x806e; + readonly UNPACK_SKIP_PIXELS = 0x0cf4; + readonly UNPACK_SKIP_ROWS = 0x0cf3; + readonly UNPACK_SKIP_IMAGES = 0x806d; + + // Framebuffer attachments + readonly COLOR_ATTACHMENT0 = 0x8ce0; + readonly DEPTH_ATTACHMENT = 0x8d00; + readonly STENCIL_ATTACHMENT = 0x8d20; + readonly DEPTH_STENCIL_ATTACHMENT = 0x821a; + + // Query targets + readonly ANY_SAMPLES_PASSED = 0x8c2f; + readonly ANY_SAMPLES_PASSED_CONSERVATIVE = 0x8d6a; + readonly TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN = 0x8c88; + + // Data types + readonly BYTE = 0x1400; + readonly UNSIGNED_BYTE = 0x1401; + readonly SHORT = 0x1402; + readonly UNSIGNED_SHORT = 0x1403; + readonly INT = 0x1404; + readonly UNSIGNED_INT = 0x1405; + readonly FLOAT = 0x1406; + + // Renderbuffer + readonly RENDERBUFFER = 0x8d41; + + // Transform feedback + readonly TRANSFORM_FEEDBACK = 0x8e22; + + // Texture parameters + readonly TEXTURE_MIN_FILTER = 0x2801; + readonly LINEAR = 0x2601; + + // Draw modes + readonly TRIANGLES = 0x0004; + readonly POINTS = 0x0000; + + // Buffer usage + readonly STATIC_DRAW = 0x88e4; +} + +export class WebGLStateTracker { + private state: WebGLState = new WebGLState(); + readonly constants = this.state.constants; + + constructor() { + // State is created in field initializer + } + + // Buffer lifecycle tracking methods + createBuffer(buffer: WebGLBuffer): void { + this.state.validBuffers.add(buffer); + } + + deleteBuffer(buffer: WebGLBuffer): void { + this.state.validBuffers.delete(buffer); + } + + isValidBuffer(buffer: WebGLBuffer | null): boolean { + if (!buffer) return false; + return this.state.validBuffers.has(buffer); + } + + // Buffer binding methods + bindBuffer(target: number, buffer: WebGLBuffer | null): void { + const buffers = this.state.getBuffersState(); + switch (target) { + case this.constants.ARRAY_BUFFER: + buffers.arrayBuffer = buffer; + break; + case this.constants.ELEMENT_ARRAY_BUFFER: + { + // ELEMENT_ARRAY_BUFFER is per-VAO state (OpenGL ES 3.0 spec section 2.10) + // Store it in the currently bound VAO's state + const vertexArrays = this.state.getVertexArraysState(); + const vaoState = vertexArrays.getCurrentVAOState(); + vaoState.elementArrayBuffer = buffer; + } + break; + case this.constants.UNIFORM_BUFFER: + buffers.uniformBuffer = buffer; + break; + case this.constants.TRANSFORM_FEEDBACK_BUFFER: + buffers.transformFeedbackBuffer = buffer; + break; + case this.constants.PIXEL_PACK_BUFFER: + buffers.pixelPackBuffer = buffer; + break; + case this.constants.PIXEL_UNPACK_BUFFER: + buffers.pixelUnpackBuffer = buffer; + break; + case this.constants.COPY_READ_BUFFER: + buffers.copyReadBuffer = buffer; + break; + case this.constants.COPY_WRITE_BUFFER: + buffers.copyWriteBuffer = buffer; + break; + } + } + + // Vertex Array Object methods + bindVertexArray(vao: WebGLVertexArrayObject | null): void { + const vertexArrays = this.state.getVertexArraysState(); + vertexArrays.vertexArrayObject = vao; + + // Ensure a vaoStates entry exists for this VAO + // This allows us to reliably detect if a VAO was deleted (missing from vaoStates) + if (!vertexArrays.vaoStates.has(vao)) { + vertexArrays.vaoStates.set(vao, new WebGLPerVAOState()); + } + + // Note: When switching VAOs, the ELEMENT_ARRAY_BUFFER binding is automatically + // switched to the new VAO's ELEMENT_ARRAY_BUFFER (per OpenGL ES 3.0 spec section 2.10) + // This happens automatically via the per-VAO state mechanism. + } + + deleteVertexArray(vao: WebGLVertexArrayObject | null): void { + if (!vao) return; + + const vertexArrays = this.state.getVertexArraysState(); + + // If the deleted VAO is currently bound, bind to null (default VAO) + // Per WebGL spec: "If the deleted object is currently bound, the binding reverts to 0" + if (vertexArrays.vertexArrayObject === vao) { + vertexArrays.vertexArrayObject = null; + } + + // Remove the VAO state to prevent memory leaks + vertexArrays.vaoStates.delete(vao); + } + + // Vertex attribute methods (per-VAO state - OpenGL ES 3.0 spec section 2.10) + // Now tracked for ALL VAOs, not just default + enableVertexAttribArray(index: number): void { + const attrib = this.state.getOrCreateVertexAttrib(index); + attrib.enabled = true; + } + + disableVertexAttribArray(index: number): void { + const attrib = this.state.getOrCreateVertexAttrib(index); + attrib.enabled = false; + } + + vertexAttribPointer( + index: number, + size: number, + type: number, + normalized: boolean, + stride: number, + offset: number + ): void { + const attrib = this.state.getOrCreateVertexAttrib(index); + // Capture the current ARRAY_BUFFER binding + const buffers = this.state.buffers; + attrib.buffer = buffers?.arrayBuffer || GLUndefined; + attrib.size = size; + attrib.type = type; + attrib.normalized = normalized; + attrib.stride = stride; + attrib.offset = offset; + } + + vertexAttribIPointer( + index: number, + size: number, + type: number, + stride: number, + offset: number + ): void { + // Same as vertexAttribPointer but for integer attributes + const attrib = this.state.getOrCreateVertexAttrib(index); + const buffers = this.state.buffers; + attrib.buffer = buffers?.arrayBuffer || GLUndefined; + attrib.size = size; + attrib.type = type; + attrib.normalized = false; // Integer attributes are never normalized + attrib.stride = stride; + attrib.offset = offset; + } + + vertexAttribDivisor(index: number, divisor: number): void { + const attrib = this.state.getOrCreateVertexAttrib(index); + attrib.divisor = divisor; + } + + // Texture methods + activeTexture(texture: number): void { + const textures = this.state.getTexturesState(); + const unitIndex = texture - this.constants.TEXTURE0; + + // Validate unit index is within valid range + if (unitIndex < 0 || unitIndex >= GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) { + console.warn(`Invalid activeTexture value: ${texture} (unit index: ${unitIndex})`); + return; + } + + textures.activeTexture = texture; + } + + bindTexture(target: number, texture: WebGLTexture | null): void { + const textures = this.state.getTexturesState(); + const activeTextureValue = + textures.activeTexture === GLUndefined + ? this.constants.TEXTURE0 + : (textures.activeTexture as number); + const unitIndex = activeTextureValue - this.constants.TEXTURE0; + + // Validate unit index is within valid range + if (unitIndex < 0 || unitIndex >= GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) { + console.warn( + `Invalid texture unit index: ${unitIndex} (activeTexture: ${activeTextureValue})` + ); + return; + } + + const unitKey = `TEXTURE${unitIndex}`; + + if (!textures.textureUnits[unitKey]) { + textures.textureUnits[unitKey] = new WebGLTextureUnitState(); + } + + switch (target) { + case this.constants.TEXTURE_2D: + textures.textureUnits[unitKey].texture2D = texture; + break; + case this.constants.TEXTURE_CUBE_MAP: + textures.textureUnits[unitKey].textureCubeMap = texture; + break; + case this.constants.TEXTURE_3D: + textures.textureUnits[unitKey].texture3D = texture; + break; + case this.constants.TEXTURE_2D_ARRAY: + textures.textureUnits[unitKey].texture2DArray = texture; + break; + } + } + + // Program methods + useProgram(program: WebGLProgram | null): void { + const programs = this.state.getProgramsState(); + programs.currentProgram = program; + } + + // Framebuffer methods + bindFramebuffer(target: number, framebuffer: WebGLFramebuffer | null): void { + const framebuffers = this.state.getFramebuffersState(); + switch (target) { + case this.constants.FRAMEBUFFER: + // FRAMEBUFFER target binds to both DRAW_FRAMEBUFFER and READ_FRAMEBUFFER + framebuffers.framebuffer = framebuffer; + framebuffers.drawFramebuffer = framebuffer; + framebuffers.readFramebuffer = framebuffer; + break; + case this.constants.DRAW_FRAMEBUFFER: + framebuffers.drawFramebuffer = framebuffer; + break; + case this.constants.READ_FRAMEBUFFER: + framebuffers.readFramebuffer = framebuffer; + break; + } + } + + // Capability methods + enable(cap: number): void { + const capabilities = this.state.getCapabilitiesState(); + switch (cap) { + case this.constants.BLEND: + capabilities.blend = true; + break; + case this.constants.CULL_FACE: + capabilities.cullFace = true; + break; + case this.constants.DEPTH_TEST: + capabilities.depthTest = true; + break; + case this.constants.DITHER: + capabilities.dither = true; + break; + case this.constants.POLYGON_OFFSET_FILL: + capabilities.polygonOffsetFill = true; + break; + case this.constants.SAMPLE_ALPHA_TO_COVERAGE: + capabilities.sampleAlphaToCoverage = true; + break; + case this.constants.SAMPLE_COVERAGE: + capabilities.sampleCoverage = true; + break; + case this.constants.SCISSOR_TEST: + capabilities.scissorTest = true; + break; + case this.constants.STENCIL_TEST: + capabilities.stencilTest = true; + break; + case this.constants.RASTERIZER_DISCARD: + capabilities.rasterDiscard = true; + break; + } + } + + disable(cap: number): void { + const capabilities = this.state.getCapabilitiesState(); + switch (cap) { + case this.constants.BLEND: + capabilities.blend = false; + break; + case this.constants.CULL_FACE: + capabilities.cullFace = false; + break; + case this.constants.DEPTH_TEST: + capabilities.depthTest = false; + break; + case this.constants.DITHER: + capabilities.dither = false; + break; + case this.constants.POLYGON_OFFSET_FILL: + capabilities.polygonOffsetFill = false; + break; + case this.constants.SAMPLE_ALPHA_TO_COVERAGE: + capabilities.sampleAlphaToCoverage = false; + break; + case this.constants.SAMPLE_COVERAGE: + capabilities.sampleCoverage = false; + break; + case this.constants.SCISSOR_TEST: + capabilities.scissorTest = false; + break; + case this.constants.STENCIL_TEST: + capabilities.stencilTest = false; + break; + case this.constants.RASTERIZER_DISCARD: + capabilities.rasterDiscard = false; + break; + } + } + + // Viewport methods + viewport(x: number, y: number, width: number, height: number): void { + const viewportState = this.state.getViewportState(); + if (!isDefined(viewportState.viewport)) { + viewportState.viewport = new Int32Array(4); + } + const arr = viewportState.viewport as Int32Array; + arr[0] = x; + arr[1] = y; + arr[2] = width; + arr[3] = height; + } + + scissor(x: number, y: number, width: number, height: number): void { + const viewportState = this.state.getViewportState(); + if (!isDefined(viewportState.scissorBox)) { + viewportState.scissorBox = new Int32Array(4); + } + const arr = viewportState.scissorBox as Int32Array; + arr[0] = x; + arr[1] = y; + arr[2] = width; + arr[3] = height; + } + + // Clear methods + clearColor(red: number, green: number, blue: number, alpha: number): void { + const clear = this.state.getClearState(); + if (!isDefined(clear.colorClearValue)) { + clear.colorClearValue = new Float32Array(4); + } + const arr = clear.colorClearValue as Float32Array; + arr[0] = red; + arr[1] = green; + arr[2] = blue; + arr[3] = alpha; + } + + clearDepth(depth: number): void { + const clear = this.state.getClearState(); + clear.depthClearValue = depth; + } + + clearStencil(stencil: number): void { + const clear = this.state.getClearState(); + clear.stencilClearValue = stencil; + } + + // Blend state methods + blendColor(red: number, green: number, blue: number, alpha: number): void { + const blend = this.state.getBlendState(); + if (!isDefined(blend.blendColor)) { + blend.blendColor = new Float32Array(4); + } + const arr = blend.blendColor as Float32Array; + arr[0] = red; + arr[1] = green; + arr[2] = blue; + arr[3] = alpha; + } + + blendEquation(mode: number): void { + const blend = this.state.getBlendState(); + blend.blendEquationRgb = mode; + blend.blendEquationAlpha = mode; + } + + blendEquationSeparate(modeRGB: number, modeAlpha: number): void { + const blend = this.state.getBlendState(); + blend.blendEquationRgb = modeRGB; + blend.blendEquationAlpha = modeAlpha; + } + + blendFunc(sfactor: number, dfactor: number): void { + const blend = this.state.getBlendState(); + blend.blendSrcRgb = sfactor; + blend.blendDstRgb = dfactor; + blend.blendSrcAlpha = sfactor; + blend.blendDstAlpha = dfactor; + } + + blendFuncSeparate(srcRGB: number, dstRGB: number, srcAlpha: number, dstAlpha: number): void { + const blend = this.state.getBlendState(); + blend.blendSrcRgb = srcRGB; + blend.blendDstRgb = dstRGB; + blend.blendSrcAlpha = srcAlpha; + blend.blendDstAlpha = dstAlpha; + } + + // Depth state methods + depthFunc(func: number): void { + const depth = this.state.getDepthState(); + depth.depthFunc = func; + } + + depthMask(flag: boolean): void { + const depth = this.state.getDepthState(); + depth.depthWritemask = flag; + } + + depthRange(zNear: number, zFar: number): void { + const depth = this.state.getDepthState(); + if (!isDefined(depth.depthRange)) { + depth.depthRange = new Float32Array(2); + } + const arr = depth.depthRange as Float32Array; + arr[0] = zNear; + arr[1] = zFar; + } + + // Stencil state methods + stencilFunc(func: number, ref: number, mask: number): void { + const stencil = this.state.getStencilState(); + stencil.stencilFunc = func; + stencil.stencilRef = ref; + stencil.stencilValueMask = mask; + stencil.stencilBackFunc = func; + stencil.stencilBackRef = ref; + stencil.stencilBackValueMask = mask; + } + + stencilFuncSeparate(face: number, func: number, ref: number, mask: number): void { + const stencil = this.state.getStencilState(); + if (face === this.constants.FRONT || face === this.constants.FRONT_AND_BACK) { + stencil.stencilFunc = func; + stencil.stencilRef = ref; + stencil.stencilValueMask = mask; + } + if (face === this.constants.BACK || face === this.constants.FRONT_AND_BACK) { + stencil.stencilBackFunc = func; + stencil.stencilBackRef = ref; + stencil.stencilBackValueMask = mask; + } + } + + stencilMask(mask: number): void { + const stencil = this.state.getStencilState(); + stencil.stencilWritemask = mask; + stencil.stencilBackWritemask = mask; + } + + stencilMaskSeparate(face: number, mask: number): void { + const stencil = this.state.getStencilState(); + if (face === this.constants.FRONT || face === this.constants.FRONT_AND_BACK) { + stencil.stencilWritemask = mask; + } + if (face === this.constants.BACK || face === this.constants.FRONT_AND_BACK) { + stencil.stencilBackWritemask = mask; + } + } + + stencilOp(fail: number, zfail: number, zpass: number): void { + const stencil = this.state.getStencilState(); + stencil.stencilFail = fail; + stencil.stencilPassDepthFail = zfail; + stencil.stencilPassDepthPass = zpass; + stencil.stencilBackFail = fail; + stencil.stencilBackPassDepthFail = zfail; + stencil.stencilBackPassDepthPass = zpass; + } + + stencilOpSeparate(face: number, fail: number, zfail: number, zpass: number): void { + const stencil = this.state.getStencilState(); + if (face === this.constants.FRONT || face === this.constants.FRONT_AND_BACK) { + stencil.stencilFail = fail; + stencil.stencilPassDepthFail = zfail; + stencil.stencilPassDepthPass = zpass; + } + if (face === this.constants.BACK || face === this.constants.FRONT_AND_BACK) { + stencil.stencilBackFail = fail; + stencil.stencilBackPassDepthFail = zfail; + stencil.stencilBackPassDepthPass = zpass; + } + } + + // Color state methods + colorMask(red: boolean, green: boolean, blue: boolean, alpha: boolean): void { + const color = this.state.getColorState(); + color.colorWritemask = [red, green, blue, alpha]; + } + + // Culling state methods + cullFace(mode: number): void { + const culling = this.state.getCullingState(); + culling.cullFaceMode = mode; + } + + frontFace(mode: number): void { + const culling = this.state.getCullingState(); + culling.frontFace = mode; + } + + // Line width + lineWidth(width: number): void { + const line = this.state.getLineState(); + line.lineWidth = width; + } + + // Polygon offset + polygonOffset(factor: number, units: number): void { + const polygonOffset = this.state.getPolygonOffsetState(); + polygonOffset.polygonOffsetFactor = factor; + polygonOffset.polygonOffsetUnits = units; + } + + // Sample coverage + sampleCoverage(value: number, invert: boolean): void { + const sample = this.state.getSampleState(); + sample.sampleCoverageValue = value; + sample.sampleCoverageInvert = invert; + } + + // Pixel store methods + pixelStorei(pname: number, param: number): void { + const pixelStore = this.state.getPixelStoreState(); + switch (pname) { + case this.constants.PACK_ALIGNMENT: + pixelStore.packAlignment = param; + break; + case this.constants.UNPACK_ALIGNMENT: + pixelStore.unpackAlignment = param; + break; + case this.constants.UNPACK_FLIP_Y_WEBGL: + pixelStore.unpackFlipY = !!param; + break; + case this.constants.UNPACK_PREMULTIPLY_ALPHA_WEBGL: + pixelStore.unpackPremultiplyAlpha = !!param; + break; + case this.constants.PACK_ROW_LENGTH: + pixelStore.packRowLength = param; + break; + case this.constants.PACK_SKIP_PIXELS: + pixelStore.packSkipPixels = param; + break; + case this.constants.PACK_SKIP_ROWS: + pixelStore.packSkipRows = param; + break; + case this.constants.UNPACK_ROW_LENGTH: + pixelStore.unpackRowLength = param; + break; + case this.constants.UNPACK_IMAGE_HEIGHT: + pixelStore.unpackImageHeight = param; + break; + case this.constants.UNPACK_SKIP_PIXELS: + pixelStore.unpackSkipPixels = param; + break; + case this.constants.UNPACK_SKIP_ROWS: + pixelStore.unpackSkipRows = param; + break; + case this.constants.UNPACK_SKIP_IMAGES: + pixelStore.unpackSkipImages = param; + break; + } + } + + // Renderbuffer methods + bindRenderbuffer(target: number, renderbuffer: WebGLRenderbuffer | null): void { + const renderbufferState = this.state.getRenderbufferState(); + renderbufferState.renderbuffer = renderbuffer; + } + + // Transform feedback methods + bindTransformFeedback(target: number, transformFeedback: WebGLTransformFeedback | null): void { + const tfState = this.state.getTransformFeedbackState(); + tfState.transformFeedback = transformFeedback; + } + + beginTransformFeedback(primitiveMode: number): void { + const tfState = this.state.getTransformFeedbackState(); + tfState.transformFeedbackActive = true; + tfState.transformFeedbackPaused = false; + } + + endTransformFeedback(): void { + const tfState = this.state.getTransformFeedbackState(); + tfState.transformFeedbackActive = false; + tfState.transformFeedbackPaused = false; + } + + pauseTransformFeedback(): void { + const tfState = this.state.getTransformFeedbackState(); + if (tfState.transformFeedbackActive) { + tfState.transformFeedbackPaused = true; + } + } + + resumeTransformFeedback(): void { + const tfState = this.state.getTransformFeedbackState(); + if (tfState.transformFeedbackActive) { + tfState.transformFeedbackPaused = false; + } + } + + // Buffer binding range methods + bindBufferBase(target: number, index: number, buffer: WebGLBuffer | null): void { + const buffers = this.state.getBuffersState(); + // Per WebGL spec: bindBufferBase updates BOTH the indexed binding AND the generic binding + switch (target) { + case this.constants.UNIFORM_BUFFER: + buffers.uniformBuffer = buffer; + const uniformBinding = new WebGLIndexedBufferBinding(); + uniformBinding.buffer = buffer; + uniformBinding.offset = 0; + uniformBinding.size = 0; // 0 means entire buffer + buffers.uniformBufferBindings.set(index, uniformBinding); + break; + case this.constants.TRANSFORM_FEEDBACK_BUFFER: + buffers.transformFeedbackBuffer = buffer; + const tfBinding = new WebGLIndexedBufferBinding(); + tfBinding.buffer = buffer; + tfBinding.offset = 0; + tfBinding.size = 0; // 0 means entire buffer + buffers.transformFeedbackBufferBindings.set(index, tfBinding); + break; + } + } + + bindBufferRange( + target: number, + index: number, + buffer: WebGLBuffer | null, + offset: number, + size: number + ): void { + const buffers = this.state.getBuffersState(); + // Per WebGL spec: bindBufferRange updates BOTH the indexed binding AND the generic binding + switch (target) { + case this.constants.UNIFORM_BUFFER: + buffers.uniformBuffer = buffer; + const uniformBinding = new WebGLIndexedBufferBinding(); + uniformBinding.buffer = buffer; + uniformBinding.offset = offset; + uniformBinding.size = size; + buffers.uniformBufferBindings.set(index, uniformBinding); + break; + case this.constants.TRANSFORM_FEEDBACK_BUFFER: + buffers.transformFeedbackBuffer = buffer; + const transformBinding = new WebGLIndexedBufferBinding(); + transformBinding.buffer = buffer; + transformBinding.offset = offset; + transformBinding.size = size; + buffers.transformFeedbackBufferBindings.set(index, transformBinding); + break; + } + } + + // Framebuffer attachment methods (per-framebuffer state, only tracked for default FB) + framebufferTexture2D( + target: number, + attachment: number, + textarget: number, + texture: WebGLTexture | null, + level: number + ): void { + const framebuffers = this.state.framebuffers; + const isDefaultFB = + !framebuffers || + (target === this.constants.FRAMEBUFFER && framebuffers.framebuffer === null) || + (target === this.constants.DRAW_FRAMEBUFFER && framebuffers.drawFramebuffer === null); + + if (isDefaultFB) { + const fbState = this.state.getFramebuffersState().defaultFramebufferState; + const attachmentObj = new WebGLFramebufferAttachment(); + attachmentObj.attachmentType = texture ? 'texture' : GLUndefined; + attachmentObj.texture = texture || GLUndefined; + attachmentObj.renderbuffer = GLUndefined; + attachmentObj.textureLevel = level; + attachmentObj.textureLayer = 0; + attachmentObj.textureCubeFace = textarget; + + this.setFramebufferAttachment(fbState, attachment, attachmentObj); + } + // else: Non-default framebuffer - don't track + } + + framebufferRenderbuffer( + target: number, + attachment: number, + renderbuffertarget: number, + renderbuffer: WebGLRenderbuffer | null + ): void { + const framebuffers = this.state.framebuffers; + const isDefaultFB = + !framebuffers || + (target === this.constants.FRAMEBUFFER && framebuffers.framebuffer === null) || + (target === this.constants.DRAW_FRAMEBUFFER && framebuffers.drawFramebuffer === null); + + if (isDefaultFB) { + const fbState = this.state.getFramebuffersState().defaultFramebufferState; + const attachmentObj = new WebGLFramebufferAttachment(); + attachmentObj.attachmentType = renderbuffer ? 'renderbuffer' : GLUndefined; + attachmentObj.texture = GLUndefined; + attachmentObj.renderbuffer = renderbuffer || GLUndefined; + attachmentObj.textureLevel = 0; + attachmentObj.textureLayer = 0; + attachmentObj.textureCubeFace = 0; + + this.setFramebufferAttachment(fbState, attachment, attachmentObj); + } + // else: Non-default framebuffer - don't track + } + + framebufferTextureLayer( + target: number, + attachment: number, + texture: WebGLTexture | null, + level: number, + layer: number + ): void { + const framebuffers = this.state.framebuffers; + const isDefaultFB = + !framebuffers || + (target === this.constants.FRAMEBUFFER && framebuffers.framebuffer === null) || + (target === this.constants.DRAW_FRAMEBUFFER && framebuffers.drawFramebuffer === null); + + if (isDefaultFB) { + const fbState = this.state.getFramebuffersState().defaultFramebufferState; + const attachmentObj = new WebGLFramebufferAttachment(); + attachmentObj.attachmentType = texture ? 'texture' : GLUndefined; + attachmentObj.texture = texture || GLUndefined; + attachmentObj.renderbuffer = GLUndefined; + attachmentObj.textureLevel = level; + attachmentObj.textureLayer = layer; + attachmentObj.textureCubeFace = 0; + + this.setFramebufferAttachment(fbState, attachment, attachmentObj); + } + // else: Non-default framebuffer - don't track + } + + private setFramebufferAttachment( + fbState: WebGLDefaultFramebufferState, + attachment: number, + attachmentObj: WebGLFramebufferAttachment + ): void { + if (attachment === this.constants.DEPTH_ATTACHMENT) { + fbState.depthAttachment = attachmentObj; + } else if (attachment === this.constants.STENCIL_ATTACHMENT) { + fbState.stencilAttachment = attachmentObj; + } else if (attachment === this.constants.DEPTH_STENCIL_ATTACHMENT) { + fbState.depthStencilAttachment = attachmentObj; + } else if ( + attachment >= this.constants.COLOR_ATTACHMENT0 && + attachment <= this.constants.COLOR_ATTACHMENT0 + 15 + ) { + const index = attachment - this.constants.COLOR_ATTACHMENT0; + fbState.colorAttachments[`COLOR_ATTACHMENT${index}`] = attachmentObj; + } + } + + // Draw buffer methods (per-framebuffer state, only tracked for default FB) + drawBuffers(buffers: number[]): void { + const framebuffers = this.state.framebuffers; + const isDefaultFB = !framebuffers || framebuffers.drawFramebuffer === null; + + if (isDefaultFB) { + const fbState = this.state.getFramebuffersState().defaultFramebufferState; + fbState.drawBuffers = [...buffers]; + } + // else: Non-default framebuffer - don't track + } + + readBuffer(mode: number): void { + const framebuffers = this.state.framebuffers; + const isDefaultFB = !framebuffers || framebuffers.readFramebuffer === null; + + if (isDefaultFB) { + const fbState = this.state.getFramebuffersState().defaultFramebufferState; + fbState.readBuffer = mode; + } + // else: Non-default framebuffer - don't track + } + + // Hint method + hint(target: number, mode: number): void { + // Hints don't typically affect state in a way we need to track + // This is a no-op but maintains interface compatibility + } + + // No-op state changing methods that don't affect our tracked state + // but are part of the WebGL2RenderingContext interface + + // Buffer data methods (don't affect binding state) + bufferData( + target: number, + sizeOrData: number | ArrayBufferView | ArrayBuffer | null, + usage: number, + srcOffset?: number, + length?: number + ): void { + // Data operations don't change binding state + } + + bufferSubData( + target: number, + dstByteOffset: number, + srcData: ArrayBufferView | ArrayBuffer, + srcOffset?: number, + length?: number + ): void { + // Data operations don't change binding state + } + + copyBufferSubData( + readTarget: number, + writeTarget: number, + readOffset: number, + writeOffset: number, + size: number + ): void { + // Copy operations don't change binding state + } + + // Texture parameter methods (don't affect binding state) + texParameterf(target: number, pname: number, param: number): void { + // Parameter changes don't affect binding state + } + + texParameteri(target: number, pname: number, param: number): void { + // Parameter changes don't affect binding state + } + + // Texture data methods (don't affect binding state) + texImage2D(...args: any[]): void { + // Data operations don't change binding state + } + + texSubImage2D(...args: any[]): void { + // Data operations don't change binding state + } + + texImage3D(...args: any[]): void { + // Data operations don't change binding state + } + + texSubImage3D(...args: any[]): void { + // Data operations don't change binding state + } + + compressedTexImage2D(...args: any[]): void { + // Data operations don't change binding state + } + + compressedTexSubImage2D(...args: any[]): void { + // Data operations don't change binding state + } + + compressedTexImage3D(...args: any[]): void { + // Data operations don't change binding state + } + + compressedTexSubImage3D(...args: any[]): void { + // Data operations don't change binding state + } + + texStorage2D( + target: number, + levels: number, + internalformat: number, + width: number, + height: number + ): void { + // Storage allocation doesn't change binding state + } + + texStorage3D( + target: number, + levels: number, + internalformat: number, + width: number, + height: number, + depth: number + ): void { + // Storage allocation doesn't change binding state + } + + copyTexImage2D( + target: number, + level: number, + internalformat: number, + x: number, + y: number, + width: number, + height: number, + border: number + ): void { + // Copy operations don't change binding state + } + + copyTexSubImage2D( + target: number, + level: number, + xoffset: number, + yoffset: number, + x: number, + y: number, + width: number, + height: number + ): void { + // Copy operations don't change binding state + } + + copyTexSubImage3D( + target: number, + level: number, + xoffset: number, + yoffset: number, + zoffset: number, + x: number, + y: number, + width: number, + height: number + ): void { + // Copy operations don't change binding state + } + + generateMipmap(target: number): void { + // Mipmap generation doesn't change binding state + } + + // Framebuffer methods (attachment doesn't affect binding state) + invalidateFramebuffer(target: number, attachments: number[]): void { + // Invalidation doesn't change binding state + } + + invalidateSubFramebuffer( + target: number, + attachments: number[], + x: number, + y: number, + width: number, + height: number + ): void { + // Invalidation doesn't change binding state + } + + // Renderbuffer storage (doesn't affect binding state) + renderbufferStorage(target: number, internalformat: number, width: number, height: number): void { + // Storage allocation doesn't change binding state + } + + renderbufferStorageMultisample( + target: number, + samples: number, + internalformat: number, + width: number, + height: number + ): void { + // Storage allocation doesn't change binding state + } + + // Sampler methods (WebGL 2.0) + bindSampler(unit: number, sampler: WebGLSampler | null): void { + const samplers = this.state.getSamplersState(); + samplers.samplerBindings[unit] = sampler; + } + + samplerParameteri(sampler: WebGLSampler, pname: number, param: number): void { + // Sampler parameters don't affect binding state + } + + samplerParameterf(sampler: WebGLSampler, pname: number, param: number): void { + // Sampler parameters don't affect binding state + } + + // Query methods (WebGL 2.0) + beginQuery(target: number, query: WebGLQuery): void { + const queries = this.state.getQueriesState(); + switch (target) { + case this.constants.ANY_SAMPLES_PASSED: + queries.currentAnySamplesPassed = query; + break; + case this.constants.ANY_SAMPLES_PASSED_CONSERVATIVE: + queries.currentAnySamplesPassedConservative = query; + break; + case this.constants.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: + queries.currentTransformFeedbackPrimitivesWritten = query; + break; + // SAMPLES_PASSED is WebGL 1.0 extension, we track as occlusion query + default: + queries.currentOcclusionQuery = query; + break; + } + } + + endQuery(target: number): void { + const queries = this.state.getQueriesState(); + switch (target) { + case this.constants.ANY_SAMPLES_PASSED: + queries.currentAnySamplesPassed = null; + break; + case this.constants.ANY_SAMPLES_PASSED_CONSERVATIVE: + queries.currentAnySamplesPassedConservative = null; + break; + case this.constants.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: + queries.currentTransformFeedbackPrimitivesWritten = null; + break; + default: + queries.currentOcclusionQuery = null; + break; + } + } + + // Generic vertex attribute setters (don't affect binding state) + vertexAttrib1f(index: number, x: number): void { + // Generic vertex attributes don't affect binding state + } + + vertexAttrib2f(index: number, x: number, y: number): void { + // Generic vertex attributes don't affect binding state + } + + vertexAttrib3f(index: number, x: number, y: number, z: number): void { + // Generic vertex attributes don't affect binding state + } + + vertexAttrib4f(index: number, x: number, y: number, z: number, w: number): void { + // Generic vertex attributes don't affect binding state + } + + vertexAttribI4i(index: number, x: number, y: number, z: number, w: number): void { + // Generic vertex attributes don't affect binding state + } + + vertexAttribI4ui(index: number, x: number, y: number, z: number, w: number): void { + // Generic vertex attributes don't affect binding state + } + + vertexAttrib1fv(index: number, values: Float32Array | number[]): void { + // Generic vertex attributes don't affect binding state + } + + vertexAttrib2fv(index: number, values: Float32Array | number[]): void { + // Generic vertex attributes don't affect binding state + } + + vertexAttrib3fv(index: number, values: Float32Array | number[]): void { + // Generic vertex attributes don't affect binding state + } + + vertexAttrib4fv(index: number, values: Float32Array | number[]): void { + // Generic vertex attributes don't affect binding state + } + + vertexAttribI4iv(index: number, values: Int32Array | number[]): void { + // Generic vertex attributes don't affect binding state + } + + vertexAttribI4uiv(index: number, values: Uint32Array | number[]): void { + // Generic vertex attributes don't affect binding state + } + + // Uniform methods (don't affect binding state, only change uniform values) + uniform1f(location: WebGLUniformLocation | null, x: number): void {} + uniform2f(location: WebGLUniformLocation | null, x: number, y: number): void {} + uniform3f(location: WebGLUniformLocation | null, x: number, y: number, z: number): void {} + uniform4f( + location: WebGLUniformLocation | null, + x: number, + y: number, + z: number, + w: number + ): void {} + + uniform1i(location: WebGLUniformLocation | null, x: number): void {} + uniform2i(location: WebGLUniformLocation | null, x: number, y: number): void {} + uniform3i(location: WebGLUniformLocation | null, x: number, y: number, z: number): void {} + uniform4i( + location: WebGLUniformLocation | null, + x: number, + y: number, + z: number, + w: number + ): void {} + + uniform1ui(location: WebGLUniformLocation | null, x: number): void {} + uniform2ui(location: WebGLUniformLocation | null, x: number, y: number): void {} + uniform3ui(location: WebGLUniformLocation | null, x: number, y: number, z: number): void {} + uniform4ui( + location: WebGLUniformLocation | null, + x: number, + y: number, + z: number, + w: number + ): void {} + + uniform1fv( + location: WebGLUniformLocation | null, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniform2fv( + location: WebGLUniformLocation | null, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniform3fv( + location: WebGLUniformLocation | null, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniform4fv( + location: WebGLUniformLocation | null, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + + uniform1iv( + location: WebGLUniformLocation | null, + data: Int32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniform2iv( + location: WebGLUniformLocation | null, + data: Int32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniform3iv( + location: WebGLUniformLocation | null, + data: Int32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniform4iv( + location: WebGLUniformLocation | null, + data: Int32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + + uniform1uiv( + location: WebGLUniformLocation | null, + data: Uint32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniform2uiv( + location: WebGLUniformLocation | null, + data: Uint32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniform3uiv( + location: WebGLUniformLocation | null, + data: Uint32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniform4uiv( + location: WebGLUniformLocation | null, + data: Uint32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + + uniformMatrix2fv( + location: WebGLUniformLocation | null, + transpose: boolean, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniformMatrix3fv( + location: WebGLUniformLocation | null, + transpose: boolean, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniformMatrix4fv( + location: WebGLUniformLocation | null, + transpose: boolean, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniformMatrix2x3fv( + location: WebGLUniformLocation | null, + transpose: boolean, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniformMatrix3x2fv( + location: WebGLUniformLocation | null, + transpose: boolean, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniformMatrix2x4fv( + location: WebGLUniformLocation | null, + transpose: boolean, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniformMatrix4x2fv( + location: WebGLUniformLocation | null, + transpose: boolean, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniformMatrix3x4fv( + location: WebGLUniformLocation | null, + transpose: boolean, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + uniformMatrix4x3fv( + location: WebGLUniformLocation | null, + transpose: boolean, + data: Float32Array | number[], + srcOffset?: number, + srcLength?: number + ): void {} + + // Drawing methods (don't affect state, only render) + clear(mask: number): void {} + drawArrays(mode: number, first: number, count: number): void {} + drawElements(mode: number, count: number, type: number, offset: number): void {} + drawArraysInstanced(mode: number, first: number, count: number, instanceCount: number): void {} + drawElementsInstanced( + mode: number, + count: number, + type: number, + offset: number, + instanceCount: number + ): void {} + drawRangeElements( + mode: number, + start: number, + end: number, + count: number, + type: number, + offset: number + ): void {} + + // Read pixels (doesn't change state) + readPixels(...args: any[]): void {} + + // Blit framebuffer (doesn't change binding state) + blitFramebuffer( + srcX0: number, + srcY0: number, + srcX1: number, + srcY1: number, + dstX0: number, + dstY0: number, + dstX1: number, + dstY1: number, + mask: number, + filter: number + ): void {} + + // Flush/Finish (doesn't change state) + flush(): void {} + finish(): void {} + + // State access methods - returns a clone to prevent external mutation + getState(): WebGLState { + return this.state.clone(); + } + + // Public getters that return state without lazy creation (tests use these) + getBufferState(): WebGLBufferState | undefined { + return this.state.buffers; + } + + getVertexArrayState(): WebGLVertexArrayState | undefined { + return this.state.vertexArrays; + } + + getTextureState(): WebGLTextureState | undefined { + return this.state.textures; + } + + getProgramState(): WebGLProgramState | undefined { + return this.state.programs; + } + + getFramebufferState(): WebGLFramebufferState | undefined { + return this.state.framebuffers; + } + + getCapabilityState(): WebGLCapabilityState | undefined { + return this.state.capabilities; + } + + getSamplerState(): WebGLSamplerState | undefined { + return this.state.samplers; + } + + getQueryState(): WebGLQueryState | undefined { + return this.state.queries; + } + + getStencilState(): WebGLStencilState | undefined { + return this.state.stencil; + } + + getPixelStoreState(): WebGLPixelStoreState | undefined { + return this.state.pixelStore; + } + + getTransformFeedbackState(): WebGLTransformFeedbackState | undefined { + return this.state.transformFeedback; + } + + // Reset state to defaults (clears all lazily created state) + reset(): void { + this.state = new WebGLState(); + } +} diff --git a/deps/cloudxr/helpers/WebGLStateApply.ts b/deps/cloudxr/helpers/WebGLStateApply.ts new file mode 100644 index 0000000..f2326ce --- /dev/null +++ b/deps/cloudxr/helpers/WebGLStateApply.ts @@ -0,0 +1,1220 @@ +import { + WebGLState, + WebGLBufferState, + WebGLTextureState, + WebGLTextureUnitState, + WebGLProgramState, + WebGLFramebufferState, + WebGLVertexArrayState, + WebGLVertexAttribState, + WebGLCapabilityState, + WebGLViewportState, + WebGLClearState, + WebGLBlendState, + WebGLDepthState, + WebGLStencilState, + WebGLColorState, + WebGLCullingState, + WebGLLineState, + WebGLPolygonOffsetState, + WebGLSampleState, + WebGLPixelStoreState, + WebGLTransformFeedbackState, + WebGLRenderbufferState, + WebGLSamplerState, + WebGLQueryState, + WebGLIndexedBufferBinding, + GL_MAX_VERTEX_ATTRIBS, + GL_MAX_UNIFORM_BUFFER_BINDINGS, + GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, + GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, + GL_MAX_COLOR_ATTACHMENTS, + GLUndefined, + isDefined, +} from './WebGLState'; + +/** + * Global flag to enable WebGL error checking after each state operation. + * Set to true for debugging to catch GL errors immediately after they occur. + * Default: false + */ +export let CHECK_ERRORS = false; + +/** + * Helper function to check if a property is defined on a state object, + * handling the case where the state object itself might be undefined + */ +function isPropertyDefined(state: T | undefined, prop: keyof T): boolean { + return state !== undefined && isDefined((state as any)[prop]); +} + +/** + * Helper function to determine if state should be applied based on saved vs current state. + * Returns true if the states differ or if one is undefined and the other is not. + * + * @param saved - The saved/desired state (may be undefined to reset to defaults) + * @param current - The current state (may be undefined if not tracked) + * @returns true if the state should be applied, false if no change needed + */ +function shouldApplyState( + saved: T | undefined, + current: T | undefined +): boolean { + // Both undefined - no change needed + if (saved === undefined && current === undefined) { + return false; + } + + // One is undefined and the other isn't - need to apply/reset + if (saved === undefined || current === undefined) { + return true; + } + + // Both defined - check if they're different using equals() + return !saved.equals(current); +} + +/** + * Helper function to check and log WebGL errors after state application + * @param gl - The WebGL2RenderingContext to check + * @param stepName - Name of the state application step for logging + */ +function checkGLError(gl: WebGL2RenderingContext, stepName: string): void { + if (!CHECK_ERRORS) return; + + const error = gl.getError(); + if (error !== gl.NO_ERROR) { + const errorName = getGLErrorName(gl, error); + const message = `[WebGLStateApply] GL error after ${stepName}: ${errorName} (0x${error.toString(16)})`; + + throw new Error(message); + } +} + +/** + * Get human-readable name for a WebGL error code + */ +function getGLErrorName(gl: WebGL2RenderingContext, error: number): string { + switch (error) { + case gl.INVALID_ENUM: + return 'INVALID_ENUM'; + case gl.INVALID_VALUE: + return 'INVALID_VALUE'; + case gl.INVALID_OPERATION: + return 'INVALID_OPERATION'; + case gl.INVALID_FRAMEBUFFER_OPERATION: + return 'INVALID_FRAMEBUFFER_OPERATION'; + case gl.OUT_OF_MEMORY: + return 'OUT_OF_MEMORY'; + case gl.CONTEXT_LOST_WEBGL: + return 'CONTEXT_LOST_WEBGL'; + default: + return 'UNKNOWN_ERROR'; + } +} + +/** + * Apply all defined state from a WebGLState object to a WebGL context + * @param gl - The WebGL2RenderingContext to apply state to + * @param state - The WebGLState object containing tracked state + * @param current - The current WebGLState for comparison + */ +export function apply(gl: WebGL2RenderingContext, state: WebGLState, current: WebGLState): void { + // Apply state in a specific order to handle dependencies + + // 2. Vertex Array Object binding (this may temporarily change ARRAY_BUFFER) + if (shouldApplyState(state.vertexArrays, current.vertexArrays)) { + applyVertexArrayState(gl, state.vertexArrays, current.vertexArrays); + checkGLError(gl, 'vertex array state'); + } + + // 1. Buffer bindings (needed before VAO state) + if (shouldApplyState(state.buffers, current.buffers)) { + applyBufferState(gl, state.buffers, current.buffers, state.validBuffers); + checkGLError(gl, 'buffer state'); + } + + // 4. Texture bindings + if (shouldApplyState(state.textures, current.textures)) { + applyTextureState(gl, state.textures, current.textures); + checkGLError(gl, 'texture state'); + } + + // 5. Sampler bindings + if (shouldApplyState(state.samplers, current.samplers)) { + applySamplerState(gl, state.samplers, current.samplers); + checkGLError(gl, 'sampler state'); + } + + // 6. Program binding + if (shouldApplyState(state.programs, current.programs)) { + applyProgramState(gl, state.programs, current.programs); + checkGLError(gl, 'program state'); + } + + // 7. Framebuffer bindings and attachments + if (shouldApplyState(state.framebuffers, current.framebuffers)) { + applyFramebufferState(gl, state.framebuffers, current.framebuffers); + checkGLError(gl, 'framebuffer state'); + } + + // 8. Renderbuffer binding + if (shouldApplyState(state.renderbuffer, current.renderbuffer)) { + applyRenderbufferState(gl, state.renderbuffer, current.renderbuffer); + checkGLError(gl, 'renderbuffer state'); + } + + // 9. Transform feedback + if (shouldApplyState(state.transformFeedback, current.transformFeedback)) { + applyTransformFeedbackState(gl, state.transformFeedback, current.transformFeedback); + checkGLError(gl, 'transform feedback state'); + } + + // 10. Viewport and scissor + if (shouldApplyState(state.viewport, current.viewport)) { + applyViewportState(gl, state.viewport, current.viewport); + checkGLError(gl, 'viewport state'); + } + + // 11. Capabilities (enable/disable) + if (shouldApplyState(state.capabilities, current.capabilities)) { + applyCapabilityState(gl, state.capabilities, current.capabilities); + checkGLError(gl, 'capability state'); + } + + // 12. Clear values + if (shouldApplyState(state.clear, current.clear)) { + applyClearState(gl, state.clear, current.clear); + checkGLError(gl, 'clear state'); + } + + // 13. Blend state + if (shouldApplyState(state.blend, current.blend)) { + applyBlendState(gl, state.blend, current.blend); + checkGLError(gl, 'blend state'); + } + + // 14. Depth state + if (shouldApplyState(state.depth, current.depth)) { + applyDepthState(gl, state.depth, current.depth); + checkGLError(gl, 'depth state'); + } + + // 15. Stencil state + if (shouldApplyState(state.stencil, current.stencil)) { + applyStencilState(gl, state.stencil, current.stencil); + checkGLError(gl, 'stencil state'); + } + + // 16. Color write mask + if (shouldApplyState(state.color, current.color)) { + applyColorState(gl, state.color, current.color); + checkGLError(gl, 'color state'); + } + + // 17. Culling state + if (shouldApplyState(state.culling, current.culling)) { + applyCullingState(gl, state.culling, current.culling); + checkGLError(gl, 'culling state'); + } + + // 18. Line width + if (shouldApplyState(state.line, current.line)) { + applyLineState(gl, state.line, current.line); + checkGLError(gl, 'line state'); + } + + // 19. Polygon offset + if (shouldApplyState(state.polygonOffset, current.polygonOffset)) { + applyPolygonOffsetState(gl, state.polygonOffset, current.polygonOffset); + checkGLError(gl, 'polygon offset state'); + } + + // 20. Sample coverage + if (shouldApplyState(state.sample, current.sample)) { + applySampleState(gl, state.sample, current.sample); + checkGLError(gl, 'sample state'); + } + + // 21. Pixel store parameters + if (shouldApplyState(state.pixelStore, current.pixelStore)) { + applyPixelStoreState(gl, state.pixelStore, current.pixelStore); + checkGLError(gl, 'pixel store state'); + } + + // 22. Query objects + if (state.queries && (!current.queries || !state.queries.equals(current.queries))) { + applyQueryState(gl, state.queries); + checkGLError(gl, 'query state'); + } +} + +/** + * Apply buffer binding state + */ +function applyBufferState( + gl: WebGL2RenderingContext, + buffers: WebGLBufferState | undefined, + current: WebGLBufferState | undefined, + validBuffers: Set +): void { + // Helper to validate a buffer + const validateBuffer = (buffer: WebGLBuffer | null, bufferName: string): boolean => { + if (!buffer) return true; + + if (!validBuffers.has(buffer)) { + console.warn( + `[WebGLStateApply] Cannot bind ${bufferName}: buffer has been deleted. Skipping.` + ); + return false; + } + return true; + }; + + if (isPropertyDefined(buffers, 'arrayBuffer')) { + if (validateBuffer(buffers!.arrayBuffer, 'ARRAY_BUFFER')) { + gl.bindBuffer(gl.ARRAY_BUFFER, buffers!.arrayBuffer); + } + } else if (isDefined(current?.arrayBuffer)) { + gl.bindBuffer(gl.ARRAY_BUFFER, null); + } + + // NOTE: ELEMENT_ARRAY_BUFFER is NOT restored here! + // Per OpenGL ES 3.0 spec section 2.10, ELEMENT_ARRAY_BUFFER binding is per-VAO state. + // It is automatically restored when binding the VAO in applyVertexArrayState() above. + + if (isPropertyDefined(buffers, 'uniformBuffer')) { + if (validateBuffer(buffers!.uniformBuffer, 'UNIFORM_BUFFER')) { + gl.bindBuffer(gl.UNIFORM_BUFFER, buffers!.uniformBuffer); + } + } else if (isDefined(current?.uniformBuffer)) { + gl.bindBuffer(gl.UNIFORM_BUFFER, null); + } + + if (isPropertyDefined(buffers, 'transformFeedbackBuffer')) { + if (validateBuffer(buffers!.transformFeedbackBuffer, 'TRANSFORM_FEEDBACK_BUFFER')) { + gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, buffers!.transformFeedbackBuffer); + } + } else if (isDefined(current?.transformFeedbackBuffer)) { + gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, null); + } + + if (isPropertyDefined(buffers, 'pixelPackBuffer')) { + if (validateBuffer(buffers!.pixelPackBuffer, 'PIXEL_PACK_BUFFER')) { + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buffers!.pixelPackBuffer); + } + } else if (isDefined(current?.pixelPackBuffer)) { + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null); + } + + if (isPropertyDefined(buffers, 'pixelUnpackBuffer')) { + if (validateBuffer(buffers!.pixelUnpackBuffer, 'PIXEL_UNPACK_BUFFER')) { + gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, buffers!.pixelUnpackBuffer); + } + } else if (isDefined(current?.pixelUnpackBuffer)) { + gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null); + } + + if (isPropertyDefined(buffers, 'copyReadBuffer')) { + if (validateBuffer(buffers!.copyReadBuffer, 'COPY_READ_BUFFER')) { + gl.bindBuffer(gl.COPY_READ_BUFFER, buffers!.copyReadBuffer); + } + } else if (isDefined(current?.copyReadBuffer)) { + gl.bindBuffer(gl.COPY_READ_BUFFER, null); + } + + if (isPropertyDefined(buffers, 'copyWriteBuffer')) { + if (validateBuffer(buffers!.copyWriteBuffer, 'COPY_WRITE_BUFFER')) { + gl.bindBuffer(gl.COPY_WRITE_BUFFER, buffers!.copyWriteBuffer); + } + } else if (isDefined(current?.copyWriteBuffer)) { + gl.bindBuffer(gl.COPY_WRITE_BUFFER, null); + } + + // Apply indexed buffer bindings + if (buffers?.uniformBufferBindings) { + for (let index = 0; index < GL_MAX_UNIFORM_BUFFER_BINDINGS; index++) { + const bindingOrUndefined = buffers.uniformBufferBindings.get(index); + if (!isDefined(bindingOrUndefined)) { + continue; + } + + const binding = bindingOrUndefined as WebGLIndexedBufferBinding; + if (isDefined(binding.buffer)) { + // Validate buffer exists + if (!validateBuffer(binding.buffer, `UNIFORM_BUFFER at index ${index}`)) { + continue; + } + + const size = binding.size as number; + const offset = binding.offset as number; + + // Per WebGLState.ts bindBufferBase logic: size=0 means entire buffer (bindBufferBase) + // size>0 means specific range (bindBufferRange) + if (size === 0) { + // bindBufferBase was used originally + gl.bindBufferBase(gl.UNIFORM_BUFFER, index, binding.buffer); + } else { + // bindBufferRange was used originally + gl.bindBufferRange(gl.UNIFORM_BUFFER, index, binding.buffer, offset, size); + } + } + } + } + + if (buffers?.transformFeedbackBufferBindings) { + for (let index = 0; index < GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS; index++) { + const bindingOrUndefined = buffers.transformFeedbackBufferBindings.get(index); + if (!isDefined(bindingOrUndefined)) { + continue; + } + + const binding = bindingOrUndefined as WebGLIndexedBufferBinding; + if (isDefined(binding.buffer)) { + // Validate buffer exists + if (!validateBuffer(binding.buffer, `TRANSFORM_FEEDBACK_BUFFER at index ${index}`)) { + continue; + } + + const size = binding.size as number; + const offset = binding.offset as number; + + // Per WebGLState.ts bindBufferBase logic: size=0 means entire buffer (bindBufferBase) + // size>0 means specific range (bindBufferRange) + if (size === 0) { + // bindBufferBase was used originally + gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, index, binding.buffer); + } else { + // bindBufferRange was used originally + gl.bindBufferRange(gl.TRANSFORM_FEEDBACK_BUFFER, index, binding.buffer, offset, size); + } + } + } + } +} + +/** + * Apply vertex array object state + */ +function applyVertexArrayState( + gl: WebGL2RenderingContext, + vertexArrays: WebGLVertexArrayState | undefined, + current?: WebGLVertexArrayState +): void { + // TODO: Currently only restores GL state for the currently bound VAO. + // This means if you have multiple VAOs configured, save state, switch VAOs, + // and restore, the non-current VAO states will not be restored in the tracker. + // To fix this, we would need to: + // 1. Copy all VAO states from saved state to current tracker's vaoStates Map + // 2. Ensure this doesn't break existing behavior or cause performance issues + // 3. Handle edge cases where VAOs are deleted between save and restore + + // Bind the VAO + if (isPropertyDefined(vertexArrays, 'vertexArrayObject')) { + const vaoToBind = vertexArrays!.vertexArrayObject; + + // Check if the VAO was deleted between save and restore + // Since bindVertexArray now ensures all bound VAOs have vaoStates entries, + // if the VAO is missing from current.vaoStates, it means it was deleted + if (vaoToBind !== null && current && !current.vaoStates.has(vaoToBind)) { + console.warn( + '[WebGLStateApply] Cannot restore VAO state: the saved VAO has been deleted. ' + + 'Binding will revert to default VAO (null).' + ); + gl.bindVertexArray(null); + return; + } + + gl.bindVertexArray(vaoToBind); + } else if (isDefined(current?.vertexArrayObject)) { + gl.bindVertexArray(null); + } + + // Restore per-VAO state for the currently bound VAO + // Per OpenGL ES 3.0 spec section 2.10: ELEMENT_ARRAY_BUFFER and vertex attributes are per-VAO state + const vao = + vertexArrays?.vertexArrayObject === GLUndefined ? null : vertexArrays?.vertexArrayObject; + const vaoState = vertexArrays?.vaoStates.get(vao!); + + if (vaoState) { + // Restore ELEMENT_ARRAY_BUFFER binding for this VAO + if (isPropertyDefined(vaoState, 'elementArrayBuffer')) { + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vaoState.elementArrayBuffer); + } + + // Restore vertex attribute state + if (vaoState.attributes) { + // Iterate through all possible vertex attribute indices + for (let idx = 0; idx < GL_MAX_VERTEX_ATTRIBS; idx++) { + const attribOrUndefined = vaoState.attributes.get(idx); + + // Skip undefined attributes (never been set) + if (!isDefined(attribOrUndefined)) { + continue; + } + + const attrib = attribOrUndefined as WebGLVertexAttribState; + + // Check if we have a complete vertex attribute configuration + const hasCompleteConfig = + isDefined(attrib.size) && + isDefined(attrib.type) && + isDefined(attrib.stride) && + isDefined(attrib.offset); + + // Only restore this attribute if we have complete configuration OR if we're explicitly disabling it + const shouldRestore = hasCompleteConfig || (isDefined(attrib.enabled) && !attrib.enabled); + + if (!shouldRestore) { + // Skip this attribute - incomplete configuration and not explicitly disabled + continue; + } + + // Bind the buffer that was associated with this attribute if defined + if (isDefined(attrib.buffer)) { + gl.bindBuffer(gl.ARRAY_BUFFER, attrib.buffer); + } + + // Restore vertexAttribPointer configuration if all required parameters are defined + if (hasCompleteConfig) { + // Restore the vertex attribute pointer + if (isDefined(attrib.normalized)) { + gl.vertexAttribPointer( + idx, + attrib.size as number, + attrib.type as number, + attrib.normalized as boolean, + attrib.stride as number, + attrib.offset as number + ); + } else { + // For vertexAttribIPointer (integer attributes) + gl.vertexAttribIPointer( + idx, + attrib.size as number, + attrib.type as number, + attrib.stride as number, + attrib.offset as number + ); + } + } + + // Handle enable/disable state + if (isDefined(attrib.enabled)) { + if (attrib.enabled) { + // Only enable if we have complete configuration + if (hasCompleteConfig) { + gl.enableVertexAttribArray(idx); + } + } else { + // Always allow disabling, even without complete configuration + gl.disableVertexAttribArray(idx); + } + } + + if (isDefined(attrib.divisor)) { + gl.vertexAttribDivisor(idx, attrib.divisor as number); + } + } + } // end if (vaoState.attributes) + } // end if (vaoState) +} + +/** + * Apply texture binding state + */ +function applyTextureState( + gl: WebGL2RenderingContext, + textures: WebGLTextureState | undefined, + current?: WebGLTextureState +): void { + // Set active texture unit first + if (isPropertyDefined(textures, 'activeTexture')) { + gl.activeTexture(textures!.activeTexture as number); + } else if (isDefined(current?.activeTexture)) { + gl.activeTexture(gl.TEXTURE0); + } + + // Bind textures to their respective units + if (textures?.textureUnits) { + for (let unitIndex = 0; unitIndex < GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS; unitIndex++) { + const unitKey = `TEXTURE${unitIndex}`; + const unitStateOrUndefined = textures.textureUnits.get(unitKey); + + if (!isDefined(unitStateOrUndefined)) { + continue; + } + + const unitState = unitStateOrUndefined as WebGLTextureUnitState; + + gl.activeTexture(gl.TEXTURE0 + unitIndex); + + if (isDefined(unitState.texture2D)) { + gl.bindTexture(gl.TEXTURE_2D, unitState.texture2D); + } + if (isDefined(unitState.textureCubeMap)) { + gl.bindTexture(gl.TEXTURE_CUBE_MAP, unitState.textureCubeMap); + } + if (isDefined(unitState.texture3D)) { + gl.bindTexture(gl.TEXTURE_3D, unitState.texture3D); + } + if (isDefined(unitState.texture2DArray)) { + gl.bindTexture(gl.TEXTURE_2D_ARRAY, unitState.texture2DArray); + } + } + + // Restore the active texture unit + if (isPropertyDefined(textures, 'activeTexture')) { + gl.activeTexture(textures!.activeTexture as number); + } + } +} + +/** + * Apply sampler binding state + */ +function applySamplerState( + gl: WebGL2RenderingContext, + samplers: WebGLSamplerState | undefined, + current?: WebGLSamplerState +): void { + if (samplers?.samplerBindings) { + for (let unit = 0; unit < GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS; unit++) { + const sampler = samplers.samplerBindings[unit]; + if (isDefined(sampler)) { + gl.bindSampler(unit, sampler); + } + } + } else if (current?.samplerBindings) { + // Reset all sampler bindings to null + for (let unit = 0; unit < GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS; unit++) { + if (isDefined(current.samplerBindings[unit])) { + gl.bindSampler(unit, null); + } + } + } +} + +/** + * Apply program binding state + */ +function applyProgramState( + gl: WebGL2RenderingContext, + programs: WebGLProgramState | undefined, + current?: WebGLProgramState +): void { + if (isPropertyDefined(programs, 'currentProgram')) { + gl.useProgram(programs!.currentProgram); + } else if (isDefined(current?.currentProgram)) { + gl.useProgram(null); + } +} + +/** + * Apply framebuffer binding and attachment state + */ +function applyFramebufferState( + gl: WebGL2RenderingContext, + framebuffers: WebGLFramebufferState | undefined, + current?: WebGLFramebufferState +): void { + // Bind framebuffers first + if (isPropertyDefined(framebuffers, 'framebuffer')) { + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers!.framebuffer); + } else if (isDefined(current?.framebuffer)) { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + + if (isPropertyDefined(framebuffers, 'drawFramebuffer')) { + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, framebuffers!.drawFramebuffer); + } else if (isDefined(current?.drawFramebuffer)) { + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null); + } + + if (isPropertyDefined(framebuffers, 'readFramebuffer')) { + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, framebuffers!.readFramebuffer); + } else if (isDefined(current?.readFramebuffer)) { + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null); + } + + // Apply default framebuffer state (only drawBuffers and readBuffer) + // NOTE: We do NOT apply attachments to the default framebuffer (null) because: + // 1. The default framebuffer is provided by the canvas/context + // 2. You cannot call framebufferTexture2D/framebufferRenderbuffer on the default framebuffer + // 3. Attempting to do so results in INVALID_OPERATION errors + const isDefaultDrawFB = framebuffers?.drawFramebuffer === null; + const hasDefaultFBState = framebuffers?.defaultFramebufferState; + + // Only apply drawBuffers and readBuffer for default framebuffer + if (isDefaultDrawFB && hasDefaultFBState) { + const fbState = framebuffers!.defaultFramebufferState; + + // Apply draw buffers (this IS valid for default framebuffer) + if (isDefined(fbState.drawBuffers)) { + gl.drawBuffers(fbState.drawBuffers as number[]); + } + + // Apply read buffer (this IS valid for default framebuffer) + if (isDefined(fbState.readBuffer)) { + gl.readBuffer(fbState.readBuffer as number); + } + } +} + +/** + * Apply renderbuffer binding state + */ +function applyRenderbufferState( + gl: WebGL2RenderingContext, + renderbuffer: WebGLRenderbufferState | undefined, + current?: WebGLRenderbufferState +): void { + if (isPropertyDefined(renderbuffer, 'renderbuffer')) { + gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer!.renderbuffer); + } else if (isDefined(current?.renderbuffer)) { + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + } +} + +/** + * Apply transform feedback state + */ +function applyTransformFeedbackState( + gl: WebGL2RenderingContext, + transformFeedback: WebGLTransformFeedbackState | undefined, + current?: WebGLTransformFeedbackState +): void { + if (isPropertyDefined(transformFeedback, 'transformFeedback')) { + gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedback!.transformFeedback); + } else if (isDefined(current?.transformFeedback)) { + gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null); + } + + if ( + isPropertyDefined(transformFeedback, 'transformFeedbackActive') && + transformFeedback!.transformFeedbackActive + ) { + // Note: beginTransformFeedback requires a primitive mode parameter + // which isn't tracked, so this is a simplified version + // gl.beginTransformFeedback(primitiveMode); + } + + if ( + isPropertyDefined(transformFeedback, 'transformFeedbackPaused') && + transformFeedback!.transformFeedbackPaused + ) { + gl.pauseTransformFeedback(); + } +} + +/** + * Apply viewport and scissor state + */ +function applyViewportState( + gl: WebGL2RenderingContext, + viewport: WebGLViewportState | undefined, + current?: WebGLViewportState +): void { + // Viewport - default: typically [0, 0, canvas.width, canvas.height] + if (isPropertyDefined(viewport, 'viewport')) { + const vp = viewport!.viewport as Int32Array; + gl.viewport(vp[0], vp[1], vp[2], vp[3]); + } else if (isDefined(current?.viewport)) { + // Reset to canvas dimensions + const canvas = gl.canvas; + gl.viewport(0, 0, canvas.width, canvas.height); + } + + // Scissor box - default: typically [0, 0, canvas.width, canvas.height] + if (isPropertyDefined(viewport, 'scissorBox')) { + const scissor = viewport!.scissorBox as Int32Array; + gl.scissor(scissor[0], scissor[1], scissor[2], scissor[3]); + } else if (isDefined(current?.scissorBox)) { + // Reset to canvas dimensions + const canvas = gl.canvas; + gl.scissor(0, 0, canvas.width, canvas.height); + } +} + +/** + * Apply capability enable/disable state + * @param gl - The WebGL2RenderingContext + * @param capabilities - The desired capability state (undefined resets all to defaults) + * @param currentCapabilities - Optional current capability state. If provided and a capability + * is currently enabled (true) but not defined in capabilities, + * it will be explicitly disabled. + */ +function applyCapabilityState( + gl: WebGL2RenderingContext, + capabilities: WebGLCapabilityState | undefined, + currentCapabilities?: WebGLCapabilityState +): void { + // Helper to apply a capability state + const applyCapability = (cap: keyof WebGLCapabilityState, glEnum: number) => { + const desired = capabilities?.[cap]; + const current = currentCapabilities?.[cap]; + + if (isDefined(desired)) { + // If desired state is defined, apply it + desired ? gl.enable(glEnum) : gl.disable(glEnum); + } else if (isDefined(current) && current === true) { + // If desired state is undefined but current is enabled, disable it + gl.disable(glEnum); + } + }; + + applyCapability('blend', gl.BLEND); + applyCapability('cullFace', gl.CULL_FACE); + applyCapability('depthTest', gl.DEPTH_TEST); + applyCapability('dither', gl.DITHER); + applyCapability('polygonOffsetFill', gl.POLYGON_OFFSET_FILL); + applyCapability('sampleAlphaToCoverage', gl.SAMPLE_ALPHA_TO_COVERAGE); + applyCapability('sampleCoverage', gl.SAMPLE_COVERAGE); + applyCapability('scissorTest', gl.SCISSOR_TEST); + applyCapability('stencilTest', gl.STENCIL_TEST); + applyCapability('rasterDiscard', gl.RASTERIZER_DISCARD); +} + +/** + * Apply clear value state + */ +function applyClearState( + gl: WebGL2RenderingContext, + clear: WebGLClearState | undefined, + current?: WebGLClearState +): void { + // ColorClearValue - default: [0, 0, 0, 0] + if (isPropertyDefined(clear, 'colorClearValue')) { + const color = clear!.colorClearValue as Float32Array; + gl.clearColor(color[0], color[1], color[2], color[3]); + } else if (isDefined(current?.colorClearValue)) { + gl.clearColor(0, 0, 0, 0); + } + + // DepthClearValue - default: 1 + if (isPropertyDefined(clear, 'depthClearValue')) { + gl.clearDepth(clear!.depthClearValue as number); + } else if (isDefined(current?.depthClearValue)) { + gl.clearDepth(1); + } + + // StencilClearValue - default: 0 + if (isPropertyDefined(clear, 'stencilClearValue')) { + gl.clearStencil(clear!.stencilClearValue as number); + } else if (isDefined(current?.stencilClearValue)) { + gl.clearStencil(0); + } +} + +/** + * Apply blend state + */ +function applyBlendState( + gl: WebGL2RenderingContext, + blend: WebGLBlendState | undefined, + current?: WebGLBlendState +): void { + // BlendColor - default: [0, 0, 0, 0] + if (isPropertyDefined(blend, 'blendColor')) { + const color = blend!.blendColor as Float32Array; + gl.blendColor(color[0], color[1], color[2], color[3]); + } else if (isDefined(current?.blendColor)) { + gl.blendColor(0, 0, 0, 0); + } + + // BlendEquation - default: FUNC_ADD for both RGB and Alpha + if ( + isPropertyDefined(blend, 'blendEquationRgb') && + isPropertyDefined(blend, 'blendEquationAlpha') + ) { + gl.blendEquationSeparate( + blend!.blendEquationRgb as number, + blend!.blendEquationAlpha as number + ); + } else if (isDefined(current?.blendEquationRgb) && isDefined(current?.blendEquationAlpha)) { + gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD); + } + + // BlendFunc - default: src=ONE, dst=ZERO for both RGB and Alpha + if ( + isPropertyDefined(blend, 'blendSrcRgb') && + isPropertyDefined(blend, 'blendDstRgb') && + isPropertyDefined(blend, 'blendSrcAlpha') && + isPropertyDefined(blend, 'blendDstAlpha') + ) { + gl.blendFuncSeparate( + blend!.blendSrcRgb as number, + blend!.blendDstRgb as number, + blend!.blendSrcAlpha as number, + blend!.blendDstAlpha as number + ); + } else if ( + isDefined(current?.blendSrcRgb) && + isDefined(current?.blendDstRgb) && + isDefined(current?.blendSrcAlpha) && + isDefined(current?.blendDstAlpha) + ) { + gl.blendFuncSeparate(gl.ONE, gl.ZERO, gl.ONE, gl.ZERO); + } +} + +/** + * Apply depth state + */ +function applyDepthState( + gl: WebGL2RenderingContext, + depth: WebGLDepthState | undefined, + current?: WebGLDepthState +): void { + // DepthFunc - default: LESS + if (isPropertyDefined(depth, 'depthFunc')) { + gl.depthFunc(depth!.depthFunc as number); + } else if (isDefined(current?.depthFunc)) { + gl.depthFunc(gl.LESS); + } + + // DepthWritemask - default: true + if (isPropertyDefined(depth, 'depthWritemask')) { + gl.depthMask(depth!.depthWritemask as boolean); + } else if (isDefined(current?.depthWritemask)) { + gl.depthMask(true); + } + + // DepthRange - default: [0, 1] + if (isPropertyDefined(depth, 'depthRange')) { + const range = depth!.depthRange as Float32Array; + gl.depthRange(range[0], range[1]); + } else if (isDefined(current?.depthRange)) { + gl.depthRange(0, 1); + } +} + +/** + * Apply stencil state + */ +function applyStencilState( + gl: WebGL2RenderingContext, + stencil: WebGLStencilState | undefined, + current?: WebGLStencilState +): void { + // Front face stencil func - default: ALWAYS, ref=0, mask=0xFFFFFFFF + if ( + isPropertyDefined(stencil, 'stencilFunc') && + isPropertyDefined(stencil, 'stencilRef') && + isPropertyDefined(stencil, 'stencilValueMask') + ) { + gl.stencilFuncSeparate( + gl.FRONT, + stencil!.stencilFunc as number, + stencil!.stencilRef as number, + stencil!.stencilValueMask as number + ); + } else if ( + isDefined(current?.stencilFunc) && + isDefined(current?.stencilRef) && + isDefined(current?.stencilValueMask) + ) { + gl.stencilFuncSeparate(gl.FRONT, gl.ALWAYS, 0, 0xffffffff); + } + + // Front face stencil writemask - default: 0xFFFFFFFF + if (isPropertyDefined(stencil, 'stencilWritemask')) { + gl.stencilMaskSeparate(gl.FRONT, stencil!.stencilWritemask as number); + } else if (isDefined(current?.stencilWritemask)) { + gl.stencilMaskSeparate(gl.FRONT, 0xffffffff); + } + + // Front face stencil operations - default: KEEP for all + if ( + isPropertyDefined(stencil, 'stencilFail') && + isPropertyDefined(stencil, 'stencilPassDepthFail') && + isPropertyDefined(stencil, 'stencilPassDepthPass') + ) { + gl.stencilOpSeparate( + gl.FRONT, + stencil!.stencilFail as number, + stencil!.stencilPassDepthFail as number, + stencil!.stencilPassDepthPass as number + ); + } else if ( + isDefined(current?.stencilFail) && + isDefined(current?.stencilPassDepthFail) && + isDefined(current?.stencilPassDepthPass) + ) { + gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.KEEP); + } + + // Back face stencil func - default: ALWAYS, ref=0, mask=0xFFFFFFFF + if ( + isPropertyDefined(stencil, 'stencilBackFunc') && + isPropertyDefined(stencil, 'stencilBackRef') && + isPropertyDefined(stencil, 'stencilBackValueMask') + ) { + gl.stencilFuncSeparate( + gl.BACK, + stencil!.stencilBackFunc as number, + stencil!.stencilBackRef as number, + stencil!.stencilBackValueMask as number + ); + } else if ( + isDefined(current?.stencilBackFunc) && + isDefined(current?.stencilBackRef) && + isDefined(current?.stencilBackValueMask) + ) { + gl.stencilFuncSeparate(gl.BACK, gl.ALWAYS, 0, 0xffffffff); + } + + // Back face stencil writemask - default: 0xFFFFFFFF + if (isPropertyDefined(stencil, 'stencilBackWritemask')) { + gl.stencilMaskSeparate(gl.BACK, stencil!.stencilBackWritemask as number); + } else if (isDefined(current?.stencilBackWritemask)) { + gl.stencilMaskSeparate(gl.BACK, 0xffffffff); + } + + // Back face stencil operations - default: KEEP for all + if ( + isPropertyDefined(stencil, 'stencilBackFail') && + isPropertyDefined(stencil, 'stencilBackPassDepthFail') && + isPropertyDefined(stencil, 'stencilBackPassDepthPass') + ) { + gl.stencilOpSeparate( + gl.BACK, + stencil!.stencilBackFail as number, + stencil!.stencilBackPassDepthFail as number, + stencil!.stencilBackPassDepthPass as number + ); + } else if ( + isDefined(current?.stencilBackFail) && + isDefined(current?.stencilBackPassDepthFail) && + isDefined(current?.stencilBackPassDepthPass) + ) { + gl.stencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.KEEP); + } +} + +/** + * Apply color write mask state + */ +function applyColorState( + gl: WebGL2RenderingContext, + color: WebGLColorState | undefined, + current?: WebGLColorState +): void { + // ColorWritemask - default: [true, true, true, true] + if (isPropertyDefined(color, 'colorWritemask')) { + const mask = color!.colorWritemask as boolean[]; + gl.colorMask(mask[0], mask[1], mask[2], mask[3]); + } else if (isDefined(current?.colorWritemask)) { + gl.colorMask(true, true, true, true); + } +} + +/** + * Apply culling state + */ +function applyCullingState( + gl: WebGL2RenderingContext, + culling: WebGLCullingState | undefined, + current?: WebGLCullingState +): void { + // CullFaceMode - default: BACK + if (isPropertyDefined(culling, 'cullFaceMode')) { + gl.cullFace(culling!.cullFaceMode as number); + } else if (isDefined(current?.cullFaceMode)) { + gl.cullFace(gl.BACK); + } + + // FrontFace - default: CCW (counter-clockwise) + if (isPropertyDefined(culling, 'frontFace')) { + gl.frontFace(culling!.frontFace as number); + } else if (isDefined(current?.frontFace)) { + gl.frontFace(gl.CCW); + } +} + +/** + * Apply line width state + */ +function applyLineState( + gl: WebGL2RenderingContext, + line: WebGLLineState | undefined, + current?: WebGLLineState +): void { + // LineWidth - default: 1 + if (isPropertyDefined(line, 'lineWidth')) { + gl.lineWidth(line!.lineWidth as number); + } else if (isDefined(current?.lineWidth)) { + gl.lineWidth(1); + } +} + +/** + * Apply polygon offset state + */ +function applyPolygonOffsetState( + gl: WebGL2RenderingContext, + polygonOffset: WebGLPolygonOffsetState | undefined, + current?: WebGLPolygonOffsetState +): void { + // PolygonOffset - default: factor=0, units=0 + if ( + isPropertyDefined(polygonOffset, 'polygonOffsetFactor') && + isPropertyDefined(polygonOffset, 'polygonOffsetUnits') + ) { + gl.polygonOffset( + polygonOffset!.polygonOffsetFactor as number, + polygonOffset!.polygonOffsetUnits as number + ); + } else if (isDefined(current?.polygonOffsetFactor) && isDefined(current?.polygonOffsetUnits)) { + gl.polygonOffset(0, 0); + } +} + +/** + * Apply sample coverage state + */ +function applySampleState( + gl: WebGL2RenderingContext, + sample: WebGLSampleState | undefined, + current?: WebGLSampleState +): void { + // SampleCoverage - default: value=1, invert=false + if ( + isPropertyDefined(sample, 'sampleCoverageValue') && + isPropertyDefined(sample, 'sampleCoverageInvert') + ) { + gl.sampleCoverage( + sample!.sampleCoverageValue as number, + sample!.sampleCoverageInvert as boolean + ); + } else if (isDefined(current?.sampleCoverageValue) && isDefined(current?.sampleCoverageInvert)) { + gl.sampleCoverage(1, false); + } +} + +/** + * Apply pixel store parameters + */ +function applyPixelStoreState( + gl: WebGL2RenderingContext, + pixelStore: WebGLPixelStoreState | undefined, + current?: WebGLPixelStoreState +): void { + // PackAlignment - default: 4 + if (isPropertyDefined(pixelStore, 'packAlignment')) { + gl.pixelStorei(gl.PACK_ALIGNMENT, pixelStore!.packAlignment as number); + } else if (isDefined(current?.packAlignment)) { + gl.pixelStorei(gl.PACK_ALIGNMENT, 4); + } + + // UnpackAlignment - default: 4 + if (isPropertyDefined(pixelStore, 'unpackAlignment')) { + gl.pixelStorei(gl.UNPACK_ALIGNMENT, pixelStore!.unpackAlignment as number); + } else if (isDefined(current?.unpackAlignment)) { + gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4); + } + + // UnpackFlipY - default: false + if (isPropertyDefined(pixelStore, 'unpackFlipY')) { + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, pixelStore!.unpackFlipY ? 1 : 0); + } else if (isDefined(current?.unpackFlipY)) { + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0); + } + + // UnpackPremultiplyAlpha - default: false + if (isPropertyDefined(pixelStore, 'unpackPremultiplyAlpha')) { + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, pixelStore!.unpackPremultiplyAlpha ? 1 : 0); + } else if (isDefined(current?.unpackPremultiplyAlpha)) { + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0); + } + + // PackRowLength - default: 0 + if (isPropertyDefined(pixelStore, 'packRowLength')) { + gl.pixelStorei(gl.PACK_ROW_LENGTH, pixelStore!.packRowLength as number); + } else if (isDefined(current?.packRowLength)) { + gl.pixelStorei(gl.PACK_ROW_LENGTH, 0); + } + + // PackSkipPixels - default: 0 + if (isPropertyDefined(pixelStore, 'packSkipPixels')) { + gl.pixelStorei(gl.PACK_SKIP_PIXELS, pixelStore!.packSkipPixels as number); + } else if (isDefined(current?.packSkipPixels)) { + gl.pixelStorei(gl.PACK_SKIP_PIXELS, 0); + } + + // PackSkipRows - default: 0 + if (isPropertyDefined(pixelStore, 'packSkipRows')) { + gl.pixelStorei(gl.PACK_SKIP_ROWS, pixelStore!.packSkipRows as number); + } else if (isDefined(current?.packSkipRows)) { + gl.pixelStorei(gl.PACK_SKIP_ROWS, 0); + } + + // UnpackRowLength - default: 0 + if (isPropertyDefined(pixelStore, 'unpackRowLength')) { + gl.pixelStorei(gl.UNPACK_ROW_LENGTH, pixelStore!.unpackRowLength as number); + } else if (isDefined(current?.unpackRowLength)) { + gl.pixelStorei(gl.UNPACK_ROW_LENGTH, 0); + } + + // UnpackImageHeight - default: 0 + if (isPropertyDefined(pixelStore, 'unpackImageHeight')) { + gl.pixelStorei(gl.UNPACK_IMAGE_HEIGHT, pixelStore!.unpackImageHeight as number); + } else if (isDefined(current?.unpackImageHeight)) { + gl.pixelStorei(gl.UNPACK_IMAGE_HEIGHT, 0); + } + + // UnpackSkipPixels - default: 0 + if (isPropertyDefined(pixelStore, 'unpackSkipPixels')) { + gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, pixelStore!.unpackSkipPixels as number); + } else if (isDefined(current?.unpackSkipPixels)) { + gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, 0); + } + + // UnpackSkipRows - default: 0 + if (isPropertyDefined(pixelStore, 'unpackSkipRows')) { + gl.pixelStorei(gl.UNPACK_SKIP_ROWS, pixelStore!.unpackSkipRows as number); + } else if (isDefined(current?.unpackSkipRows)) { + gl.pixelStorei(gl.UNPACK_SKIP_ROWS, 0); + } + + // UnpackSkipImages - default: 0 + if (isPropertyDefined(pixelStore, 'unpackSkipImages')) { + gl.pixelStorei(gl.UNPACK_SKIP_IMAGES, pixelStore!.unpackSkipImages as number); + } else if (isDefined(current?.unpackSkipImages)) { + gl.pixelStorei(gl.UNPACK_SKIP_IMAGES, 0); + } +} + +/** + * Apply query state + */ +function applyQueryState(gl: WebGL2RenderingContext, queries: WebGLQueryState): void { + // Note: Query state includes active queries, but we can't directly set them + // as beginQuery requires starting a new query operation. + // This is here for completeness but may need special handling. + + if (isDefined(queries.currentAnySamplesPassed) && queries.currentAnySamplesPassed) { + console.warn( + '[WebGLStateApply] Cannot restore active query state for ANY_SAMPLES_PASSED - queries cannot be directly restored' + ); + // gl.beginQuery(gl.ANY_SAMPLES_PASSED, queries.currentAnySamplesPassed); + } + if ( + isDefined(queries.currentAnySamplesPassedConservative) && + queries.currentAnySamplesPassedConservative + ) { + console.warn( + '[WebGLStateApply] Cannot restore active query state for ANY_SAMPLES_PASSED_CONSERVATIVE - queries cannot be directly restored' + ); + // gl.beginQuery(gl.ANY_SAMPLES_PASSED_CONSERVATIVE, queries.currentAnySamplesPassedConservative); + } + if ( + isDefined(queries.currentTransformFeedbackPrimitivesWritten) && + queries.currentTransformFeedbackPrimitivesWritten + ) { + console.warn( + '[WebGLStateApply] Cannot restore active query state for TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN - queries cannot be directly restored' + ); + // gl.beginQuery(gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, queries.currentTransformFeedbackPrimitivesWritten); + } +} diff --git a/deps/cloudxr/helpers/WebGLStateBinding.ts b/deps/cloudxr/helpers/WebGLStateBinding.ts new file mode 100644 index 0000000..fe63d98 --- /dev/null +++ b/deps/cloudxr/helpers/WebGLStateBinding.ts @@ -0,0 +1,286 @@ +import { WebGLStateTracker, WebGLState } from './WebGLState'; +import { apply } from './WebGLStateApply'; + +/** + * BoundWebGLState - A WebGL2 context with automatic state tracking + * + * This is the original WebGL2 context with state-changing functions rebound + * to call the tracker before forwarding to the original implementation. + */ +export class BoundWebGLState { + private gl: WebGL2RenderingContext; + private tracker: WebGLStateTracker; + private originalFunctions: Map; + private savedState?: WebGLState; + private trackingEnabled = true; + + constructor( + gl: WebGL2RenderingContext, + tracker: WebGLStateTracker, + originalFunctions: Map + ) { + this.gl = gl; + this.tracker = tracker; + this.originalFunctions = originalFunctions; + } + + /** + * Get the internal state tracker + */ + private getTracker(): WebGLStateTracker { + return this.tracker; + } + + /** + * Save the current tracked WebGL state + * This clones the state so it can be restored later + */ + save(): void { + this.savedState = this.tracker.getState(); + } + + /** + * Restore the previously saved WebGL state + * This applies the saved state back to the WebGL context + * @throws {Error} If no state has been saved + */ + restore(): void { + if (!this.savedState) { + throw new Error('No state has been saved. Call save() before restore().'); + } + + // Save the current tracking state and enable tracking during restore + // This ensures the tracker stays synchronized with actual GL state + const wasTrackingEnabled = this.trackingEnabled; + this.trackingEnabled = true; + + const currentState = this.tracker.getState(); + apply(this.gl, this.savedState, currentState); + + // Restore the original tracking state + this.trackingEnabled = wasTrackingEnabled; + } + + enableTracking(enable: boolean): void { + this.trackingEnabled = enable; + } + + _enabled(): boolean { + return this.trackingEnabled; + } + + /** + * Revert all tracked functions back to their original implementations + * This removes state tracking from the WebGL context + */ + revert(): void { + const glAny = this.gl as any; + + for (const [name, originalFunction] of this.originalFunctions.entries()) { + glAny[name] = originalFunction; + } + + // Clear the map + this.originalFunctions.clear(); + + // Remove the stored BoundWebGLState from the GL context + delete glAny.__cloudxrBoundState; + } +} + +/** + * Bind a WebGL2 context with automatic state tracking + * + * Rebinds state-changing methods on the WebGL context to automatically track + * state changes before forwarding to the original implementation. + * + * @param gl - The WebGL2RenderingContext to wrap + * @returns A BoundWebGLState instance that provides access to the tracker and revert functionality + * + * @example + * ```typescript + * const canvas = document.getElementById('canvas') as HTMLCanvasElement; + * const gl = canvas.getContext('webgl2')!; + * const binding = bindGL(gl); + * + * // Use gl like a normal WebGL context - it's now tracked + * gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + * gl.bindVertexArray(vao); + * + * // Save the current state + * binding.save(); + * + * // Make some temporary changes + * gl.bindBuffer(gl.ARRAY_BUFFER, tempBuffer); + * gl.enable(gl.BLEND); + * + * // Restore the saved state + * binding.restore(); + * + * // Access tracked state + * const state = binding.getTracker().getState(); + * console.log(state.buffers?.arrayBuffer); // The bound buffer + * + * // When done, revert to stop tracking + * binding.revert(); + * ``` + */ +export function bindGL(gl: WebGL2RenderingContext): BoundWebGLState { + const glAny = gl as any; + + // Check if this GL context is already wrapped - prevent double-wrapping + if (glAny.__cloudxrBoundState) { + console.warn( + 'WebGL context is already wrapped with state tracking. Returning existing BoundWebGLState.' + ); + return glAny.__cloudxrBoundState; + } + + // Create the tracker + const tracker = new WebGLStateTracker(); + + // Store original functions for later reversion + const originalFunctions = new Map(); + const wrappedFunctions = new Map(); + + const state = new BoundWebGLState(gl, tracker, originalFunctions); + + // Store the BoundWebGLState on the GL context to prevent double-wrapping + glAny.__cloudxrBoundState = state; + + // Helper function to bind a method + const bind = (name: string, trackerMethod: Function) => { + // CRITICAL: Store the original BEFORE we replace it, otherwise we'll store the wrapper + const originalMethod = glAny[name]; + if (!originalMethod) { + throw new Error('Original method not found for ' + name); + } + if (originalMethod === wrappedFunctions.get(name)) { + throw new Error('Wrapped function already bound for ' + name); + } + + const original = originalMethod.bind(gl); + originalFunctions.set(name, original); + const wrappedFunction = (...args: any[]) => { + if (state._enabled()) { + trackerMethod.apply(tracker, args); + } + return original(...args); + }; + wrappedFunctions.set(name, wrappedFunction); + + glAny[name] = wrappedFunction; + }; + + // Buffer bindings + bind('bindBuffer', tracker.bindBuffer); + bind('bindBufferBase', tracker.bindBufferBase); + bind('bindBufferRange', tracker.bindBufferRange); + + // Buffer lifecycle tracking (for validation without GPU calls) + const originalCreateBuffer = glAny.createBuffer.bind(gl); + originalFunctions.set('createBuffer', originalCreateBuffer); + glAny.createBuffer = (): WebGLBuffer | null => { + const buffer = originalCreateBuffer(); + if (buffer) { + tracker.createBuffer(buffer); + } + return buffer; + }; + bind('deleteBuffer', tracker.deleteBuffer); + + // VAO and vertex attributes + bind('bindVertexArray', tracker.bindVertexArray); + bind('deleteVertexArray', tracker.deleteVertexArray); + bind('enableVertexAttribArray', tracker.enableVertexAttribArray); + bind('disableVertexAttribArray', tracker.disableVertexAttribArray); + bind('vertexAttribPointer', tracker.vertexAttribPointer); + bind('vertexAttribIPointer', tracker.vertexAttribIPointer); + bind('vertexAttribDivisor', tracker.vertexAttribDivisor); + + // Texture bindings + bind('activeTexture', tracker.activeTexture); + bind('bindTexture', tracker.bindTexture); + + // Program binding + bind('useProgram', tracker.useProgram); + + // Framebuffer bindings + bind('bindFramebuffer', tracker.bindFramebuffer); + bind('framebufferTexture2D', tracker.framebufferTexture2D); + bind('framebufferRenderbuffer', tracker.framebufferRenderbuffer); + bind('framebufferTextureLayer', tracker.framebufferTextureLayer); + bind('drawBuffers', tracker.drawBuffers); + bind('readBuffer', tracker.readBuffer); + + // Renderbuffer binding + bind('bindRenderbuffer', tracker.bindRenderbuffer); + + // Transform feedback + bind('bindTransformFeedback', tracker.bindTransformFeedback); + bind('beginTransformFeedback', tracker.beginTransformFeedback); + bind('endTransformFeedback', tracker.endTransformFeedback); + bind('pauseTransformFeedback', tracker.pauseTransformFeedback); + bind('resumeTransformFeedback', tracker.resumeTransformFeedback); + + // Capabilities (enable/disable) + bind('enable', tracker.enable); + bind('disable', tracker.disable); + + // Viewport and scissor + bind('viewport', tracker.viewport); + bind('scissor', tracker.scissor); + + // Clear values + bind('clearColor', tracker.clearColor); + bind('clearDepth', tracker.clearDepth); + bind('clearStencil', tracker.clearStencil); + + // Blend state + bind('blendColor', tracker.blendColor); + bind('blendEquation', tracker.blendEquation); + bind('blendEquationSeparate', tracker.blendEquationSeparate); + bind('blendFunc', tracker.blendFunc); + bind('blendFuncSeparate', tracker.blendFuncSeparate); + + // Depth state + bind('depthFunc', tracker.depthFunc); + bind('depthMask', tracker.depthMask); + bind('depthRange', tracker.depthRange); + + // Stencil state + bind('stencilFunc', tracker.stencilFunc); + bind('stencilFuncSeparate', tracker.stencilFuncSeparate); + bind('stencilMask', tracker.stencilMask); + bind('stencilMaskSeparate', tracker.stencilMaskSeparate); + bind('stencilOp', tracker.stencilOp); + bind('stencilOpSeparate', tracker.stencilOpSeparate); + + // Color mask + bind('colorMask', tracker.colorMask); + + // Culling and face orientation + bind('cullFace', tracker.cullFace); + bind('frontFace', tracker.frontFace); + + // Line width + bind('lineWidth', tracker.lineWidth); + + // Polygon offset + bind('polygonOffset', tracker.polygonOffset); + + // Sample coverage + bind('sampleCoverage', tracker.sampleCoverage); + + // Pixel store parameters + bind('pixelStorei', tracker.pixelStorei); + + // Sampler binding + bind('bindSampler', tracker.bindSampler); + + // Query operations + bind('beginQuery', tracker.beginQuery); + bind('endQuery', tracker.endQuery); + + return state; +} diff --git a/deps/cloudxr/helpers/WebGlUtils.ts b/deps/cloudxr/helpers/WebGlUtils.ts new file mode 100644 index 0000000..74a9517 --- /dev/null +++ b/deps/cloudxr/helpers/WebGlUtils.ts @@ -0,0 +1,54 @@ +export function getOrCreateCanvas( + id: string, + resolution?: { width: number; height: number } +): HTMLCanvasElement { + let canvas = document.getElementById(id) as HTMLCanvasElement | null; + if (!canvas) { + canvas = document.createElement('canvas') as HTMLCanvasElement; + canvas.id = id; + // canvas.style.display = "none"; + document.body.appendChild(canvas); + } + if (!canvas) { + throw new Error('Failed to create canvas'); + } + if (resolution) { + canvas.width = resolution.width; + canvas.height = resolution.height; + } + return canvas; +} + +export function logOrThrow(tagString: string, gl: WebGL2RenderingContext) { + const err = gl.getError(); + if (err !== gl.NO_ERROR) { + let errorString; + switch (err) { + case gl.INVALID_ENUM: + errorString = 'INVALID_ENUM'; + break; + case gl.INVALID_VALUE: + errorString = 'INVALID_VALUE'; + break; + case gl.INVALID_OPERATION: + errorString = 'INVALID_OPERATION'; + break; + case gl.INVALID_FRAMEBUFFER_OPERATION: + errorString = 'INVALID_FRAMEBUFFER_OPERATION'; + break; + case gl.OUT_OF_MEMORY: + errorString = 'OUT_OF_MEMORY'; + break; + case gl.CONTEXT_LOST_WEBGL: + errorString = 'CONTEXT_LOST_WEBGL'; + break; + default: + errorString = 'UNKNOWN_ERROR'; + break; + } + + throw new Error('WebGL error: ' + tagString + ': ' + errorString + ' (' + err + ')'); + } else { + console.debug('WebGL no-error: ' + tagString); + } +} diff --git a/deps/cloudxr/helpers/overridePressureObserver.ts b/deps/cloudxr/helpers/overridePressureObserver.ts new file mode 100644 index 0000000..93eaeb6 --- /dev/null +++ b/deps/cloudxr/helpers/overridePressureObserver.ts @@ -0,0 +1,35 @@ +/** + * Override PressureObserver to catch errors from unexpected browser implementations. + * + * Some browsers have buggy PressureObserver implementations that throw errors + * when observe() is called. This wrapper catches and logs those errors instead + * of letting them propagate. + * + * This should be called early in your application, before any code attempts + * to use PressureObserver. + */ +export function overridePressureObserver(): void { + if (typeof window === 'undefined' || !(window as any).PressureObserver) { + return; + } + + const OriginalPressureObserver = (window as any).PressureObserver; + + (window as any).PressureObserver = class PressureObserver extends OriginalPressureObserver { + observe(source: any) { + try { + const result = super.observe(source); + if (result && typeof result.catch === 'function') { + return result.catch((e: Error) => { + console.warn('PressureObserver.observe() failed:', e.message); + return undefined; + }); + } + return result; + } catch (e: any) { + console.warn('PressureObserver.observe() failed:', e.message); + return undefined; + } + } + }; +} diff --git a/deps/cloudxr/helpers/utils.ts b/deps/cloudxr/helpers/utils.ts new file mode 100644 index 0000000..83366b4 --- /dev/null +++ b/deps/cloudxr/helpers/utils.ts @@ -0,0 +1,293 @@ +/** + * Parses URL parameters and returns them as an object + * @param location - Optional location object (defaults to window.location) + * @returns Object with URL parameters as key-value pairs + */ +export function getUrlParams(location: Location = window.location): Record { + const params: Record = {}; + const queryString = location.search.substring(1); + + if (queryString) { + const pairs = queryString.split('&'); + for (const pair of pairs) { + const [key, value = ''] = pair.split('='); + params[decodeURIComponent(key)] = decodeURIComponent(value); + } + } + + return params; +} + +/** + * Enables localStorage functionality for form elements + * @param element - The HTML input or select element to enable localStorage for + * @param key - The localStorage key to use for saving/loading the value + */ +export function enableLocalStorage(element: HTMLInputElement | HTMLSelectElement, key: string) { + // Check if localStorage is already enabled for this element and key + const localStorageKey = `__localStorageEnabled_${key}`; + if ((element as any)[localStorageKey]) { + console.warn(`localStorage already enabled for ${key}, skipping`); + return; + } + + // Load saved value from localStorage + try { + // Check if the key exists in localStorage, not just if it has values + if (localStorage.hasOwnProperty(key)) { + const savedValue = localStorage.getItem(key); + element.value = savedValue || ''; + console.info(`Loaded saved ${key} from localStorage:`, savedValue); + } + } catch (error) { + console.warn(`${key}: Failed to load saved value from localStorage:`, error); + } + + // Set up event listener to save value when changed + const changeHandler = () => { + try { + // Always save the value, even if it's empty + localStorage.setItem(key, element.value); + console.info(`Saved ${key} to localStorage:`, JSON.stringify(element.value)); + } catch (error) { + console.warn(`${key}: Failed to save to localStorage:`, error); + } + }; + + element.addEventListener('change', changeHandler); + + // Mark this element as having localStorage enabled for this key + (element as any)[localStorageKey] = true; +} + +/** + * Strips protocol prefixes (http:// or https://) from a URL string + * @param url - The URL string to clean + * @returns The URL without protocol prefix + */ +function stripProtocol(url: string): string { + return url.replace(/^https?:\/\//, ''); +} + +/** + * Connection configuration object containing server connection details + */ +export interface ConnectionConfiguration { + /** Final server IP address (may be modified for HTTPS proxy routing) */ + serverIP: string; + + /** Final port number (fixed to 443 for HTTPS, user-provided for HTTP) */ + port: number; + + /** Whether the connection will use secure protocol (HTTPS/WSS) */ + useSecureConnection: boolean; +} + +/** + * CloudXR configuration interface containing all streaming settings + */ +export interface CloudXRConfig { + /** IP address of the CloudXR streaming server */ + serverIP: string; + + /** Port number for the CloudXR server connection */ + port: number; + + /** Whether to use secure connection (HTTPS/WSS) or insecure (HTTP/WS) */ + useSecureConnection: boolean; + + /** Width of each eye in pixels (must be multiple of 16) */ + perEyeWidth: number; + + /** Height of each eye in pixels (must be multiple of 16) */ + perEyeHeight: number; + + /** Target frame rate for the XR device in frames per second (FPS) */ + deviceFrameRate: number; + + /** Maximum streaming bitrate in Megabits per second (Mbps) */ + maxStreamingBitrateMbps: number; + + /** XR immersive mode: 'ar' for augmented reality, 'vr' for virtual reality */ + immersiveMode: 'ar' | 'vr'; + + /** Application identifier string for the CloudXR session */ + app: string; + + /** Type of server being connected to */ + serverType: string; + + /** Optional proxy URL for HTTPS routing (e.g., 'https://proxy.example.com/'); if empty, uses direct WSS connection */ + proxyUrl: string; + + /** Preferred XR reference space for tracking and positioning */ + referenceSpaceType: 'auto' | 'local-floor' | 'local' | 'viewer' | 'unbounded'; + + /** XR reference space offset along X axis in meters (positive is right) */ + xrOffsetX?: number; + /** XR reference space offset along Y axis in meters (positive is up) */ + xrOffsetY?: number; + /** XR reference space offset along Z axis in meters (positive is backward) */ + xrOffsetZ?: number; +} + +/** + * Determines connection configuration based on protocol and user inputs + * Supports both direct WSS connections and proxy routing for HTTPS + * + * @param serverIP - The user-provided server IP address + * @param port - The user-provided port number + * @param proxyUrl - Optional proxy URL for HTTPS routing (if provided, uses proxy routing; otherwise direct connection) + * @param location - Optional location object (defaults to window.location) + * @returns Object containing server IP, port, and security settings + * @throws {Error} When proxy URL format is invalid (must start with https://) + */ +export function getConnectionConfig( + serverIP: string, + port: number, + proxyUrl: string, + location: Location = window.location +): ConnectionConfiguration { + let finalServerIP = ''; + let finalPort = port; + let finalUseSecureConnection = false; + + // Determine if we should use secure connection based on page protocol + if (location.protocol === 'https:') { + console.info('Running on HTTPS protocol - using secure WebSocket (WSS)'); + finalUseSecureConnection = true; + + // Check if proxy URL is provided for routing + const trimmedProxyUrl = proxyUrl?.trim(); + if (trimmedProxyUrl) { + // Proxy routing mode + console.info('Proxy URL provided - using proxy routing mode'); + + if (!trimmedProxyUrl.startsWith('https://')) { + throw new Error('Proxy URL must start with https://. Received: ' + trimmedProxyUrl); + } + + // Use port 443 for proxy routing (standard HTTPS port) + finalPort = 443; + + // Route through proxy: if specific server IP provided, append it to proxy URL + if (serverIP && serverIP !== 'localhost' && serverIP !== '127.0.0.1') { + // Route to proxy with IP + const cleanServerIP = stripProtocol(serverIP); + // Clean proxy URL - strip protocol and trailing slash + const cleanProxyUrl = stripProtocol(trimmedProxyUrl).replace(/\/$/, ''); + finalServerIP = `${cleanProxyUrl}/${cleanServerIP}`; + console.info(`Using HTTPS proxy with IP: ${finalServerIP}`); + } else { + // Route to proxy without IP + finalServerIP = stripProtocol(trimmedProxyUrl).replace(/\/$/, ''); + console.info(`Using HTTPS proxy without specific IP: ${finalServerIP}`); + } + } else { + // Direct WSS connection mode + console.info('No proxy URL - using direct WSS connection'); + + // Handle server IP input + if (serverIP && serverIP !== 'localhost' && serverIP !== '127.0.0.1') { + finalServerIP = stripProtocol(serverIP); + console.info('Using user-provided server IP:', finalServerIP); + } else { + finalServerIP = new URL(location.href).hostname; + console.info('Using default server IP from window location:', finalServerIP); + } + + // Use user-provided port for direct WSS + if (port && !isNaN(port)) { + finalPort = port; + console.info('Using user-provided port:', finalPort); + } + } + } else { + // HTTP protocol - direct WS connection + console.info('Running on HTTP protocol - using insecure WebSocket (WS)'); + finalUseSecureConnection = false; + + // Handle server IP input + if (serverIP && serverIP !== 'localhost' && serverIP !== '127.0.0.1') { + finalServerIP = stripProtocol(serverIP); + console.info('Using user-provided server IP:', finalServerIP); + } else { + finalServerIP = new URL(location.href).hostname; + console.info('Using default server IP from window location:', finalServerIP); + } + + // Handle port input + if (port && !isNaN(port)) { + finalPort = port; + console.info('Using user-provided port:', finalPort); + } + } + + return { + serverIP: finalServerIP, + port: finalPort, + useSecureConnection: finalUseSecureConnection, + } as ConnectionConfiguration; +} + +/** + * Sets up certificate acceptance link for self-signed certificates in HTTPS mode + * Shows a link to accept certificates when using direct WSS connection (no proxy) + * + * @param serverIpInput - Input element for server IP address + * @param portInput - Input element for port number + * @param proxyUrlInput - Input element for proxy URL + * @param certAcceptanceLink - Container element for the certificate link + * @param certLink - Anchor element for the certificate URL + * @param location - Optional location object (defaults to window.location) + * @returns Cleanup function to remove event listeners + */ +export function setupCertificateAcceptanceLink( + serverIpInput: HTMLInputElement, + portInput: HTMLInputElement, + proxyUrlInput: HTMLInputElement, + certAcceptanceLink: HTMLElement, + certLink: HTMLAnchorElement, + location: Location = window.location +): () => void { + /** + * Updates the certificate acceptance link based on current configuration + * Shows link only when in HTTPS mode without proxy (direct WSS) + */ + const updateCertLink = () => { + const isHttps = location.protocol === 'https:'; + const hasProxy = proxyUrlInput.value.trim().length > 0; + const portValue = parseInt(portInput.value, 10); + const defaultPort = hasProxy ? 443 : 48322; + const port = portValue || defaultPort; + + // Show link only in HTTPS mode without proxy + if (isHttps && !hasProxy) { + let serverIp = serverIpInput.value.trim(); + if (!serverIp) { + serverIp = new URL(location.href).hostname; + } + const url = `https://${serverIp}:${port}/`; + certAcceptanceLink.style.display = 'block'; + certLink.href = url; + certLink.textContent = `Click ${url} to accept cert`; + } else { + certAcceptanceLink.style.display = 'none'; + } + }; + + // Add event listeners to update link when inputs change + serverIpInput.addEventListener('input', updateCertLink); + portInput.addEventListener('input', updateCertLink); + proxyUrlInput.addEventListener('input', updateCertLink); + + // Initial update after localStorage values are restored + setTimeout(updateCertLink, 0); + + // Return cleanup function to remove event listeners + return () => { + serverIpInput.removeEventListener('input', updateCertLink); + portInput.removeEventListener('input', updateCertLink); + proxyUrlInput.removeEventListener('input', updateCertLink); + }; +} diff --git a/deps/cloudxr/isaac/.gitignore b/deps/cloudxr/isaac/.gitignore new file mode 100644 index 0000000..11e2140 --- /dev/null +++ b/deps/cloudxr/isaac/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +build/ +*.pem diff --git a/deps/cloudxr/isaac/LICENSE b/deps/cloudxr/isaac/LICENSE new file mode 100644 index 0000000..5e53852 --- /dev/null +++ b/deps/cloudxr/isaac/LICENSE @@ -0,0 +1,307 @@ +NVIDIA SOFTWARE EVALUATION LICENSE AGREEMENT + +IMPORTANT NOTICE – PLEASE READ AND AGREE BEFORE USING THE SOFTWARE + +This software evaluation license agreement (“Agreement”) is a legal agreement between you, whether an +individual or entity, (“you”) and NVIDIA Corporation and its affiliates (“NVIDIA”) and governs the use of certain +NVIDIA CloudXR software and documentation that NVIDIA delivers to you under this Agreement (“Software”). +NVIDIA and you are each a “party” and collectively the “parties.” +This Agreement can be accepted only by an adult of legal age of majority in the country in which the Software is +used. If you don’t have the required age or authority to accept this Agreement, or if you don’t accept all the +terms and conditions of this Agreement, do not use the Software. + +1. License Grants. + +1.1 License Grant to You. The Software is licensed, not sold. Subject to the terms of this Agreement, +NVIDIA grants you a limited, non-exclusive, revocable, non-transferable, non-sublicensable (except +as expressly granted in this Agreement), license to: +(a) access, install and use copies of the Software, +(b) configure the Software using configuration files provided (if applicable), +(c) modify and create derivative works of any source code NVIDIA delivers to you as part of the +Software (“Derivatives”) (if applicable). + +All the foregoing grants are only for internal test and evaluation purposes and, as applicable, for use (a) in +client systems, or (b) in server systems with NVIDIA GPUs (“Purpose”). + +1.2 License Grant to NVIDIA. Subject to the terms of this Agreement, you grant NVIDIA a non-exclusive, +perpetual, irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, +under your intellectual property rights, to publicly perform, publicly display, reproduce, use, make, +have made, sell, offer for sale, distribute (through multiple tiers of distribution), import, create +derivative works of and otherwise commercialize and exploit at NVIDIA’s discretion any Derivatives +created by or for you. You may, but are not required to, deliver any Derivatives to NVIDIA. + +2. License Restrictions. Your license to use the Software and Derivatives is restricted as stated in this “License +Restrictions” Section. You will cooperate with NVIDIA and, upon NVIDIA’s written request, you will confirm +in writing and provide reasonably requested information to verify your compliance with the terms of this +Agreement. You may not: + +2.1 Use the Software or Derivatives for any purpose other than the Purpose, including but not limited to +in production; + +2.2 Sell, rent, sublicense, transfer, distribute or otherwise make available to others (except Authorized +Users as stated in the “Authorized Users” Section) any portion of the Software or Derivatives, except +as expressly granted in Section 1.1 (“License Grant to You”); + +2.3 Reverse engineer, decompile, or disassemble the Software components provided in binary form, nor +attempt in any other manner to obtain source code of such Software; + +2.4 Modify or create derivative works of the Software, except as expressly granted in Section 1.1 +(“License Grant to You”); + +2.5 Change or remove copyright or other proprietary notices in the Software; + +2.6 Bypass, disable, or circumvent any technical limitation, encryption, security, digital rights +management or authentication mechanism in the Software; + +2.7 Use the Software or Derivatives in any manner that would cause them to become subject to an open +source software license; subject to the terms in Section 7 (“Components Under Other Licenses”); + +2.8 Use the Software or Derivatives for the purpose of developing competing products or technologies +or assist a third party in such activities; + +2.9 Replace any Software components governed by this Agreement with other software that +implements NVIDIA APIs; + +2.10 Use the Software or Derivatives in violation of any applicable law or regulation in the relevant +jurisdictions; or + +2.11 Use the Software in or with any system or application where the use or failure of such system or +application developed or deployed with Software could result in injury, death or catastrophic +damage (“Mission Critical Applications”). NVIDIA will not be liable to you or any third party, in whole +or in part, for any claims or damages arising from uses in Mission Critical Applications. + +2.12 Disclose any evaluation or test results regarding the Software or Derivatives without NVIDIA’s prior +written consent. + +3. Authorized Users. You may allow employees and contractors of your entity or of your subsidiary(ies), and +for educational institutions also enrolled students, to internally access and use the Software as authorized +by this Agreement from your secure network to perform the work authorized by this Agreement on your +behalf. You are responsible for the compliance with the terms of this Agreement by your authorized users. +Any act or omission that if committed by you would constitute a breach of this Agreement will be deemed to +constitute a breach of this Agreement if committed by your authorized users. + +4. Pre-Release. Software versions identified as alpha, beta, preview, early access or otherwise as pre-release +may not be fully functional, may contain errors or design flaws, and may have reduced or different security, +privacy, availability and reliability standards relative to NVIDIA commercial offerings. You use pre-release +Software at your own risk. NVIDIA did not design or test the Software for use in production or business +critical systems. NVIDIA may choose not to make available a commercial version of pre-release Software. +NVIDIA may also choose to abandon development and terminate the availability of pre-release Software at +any time without liability. + +5. Your Privacy: Collection and Use of Information. + +5.1 Privacy Policy. Please review the NVIDIA Privacy Policy, located at https://www.nvidia.com/enus/about-nvidia/privacy-policy, which explains NVIDIA’s policy for collecting and using data, as well +as visit the NVIDIA Privacy Center, located at https://www.nvidia.com/en-us/privacy-center, to +manage your consent and privacy preferences. + +5.2 Collection Purposes. You also acknowledge that the Software collects data for the following +purposes: (a) properly configure and optimize products for use with Software; and (b) improve +NVIDIA products and services. Information collected by the Software includes: (i) application +configuration; (ii) browser version; (iii) and session metadata (i.e. performance and usage +statistics). Additionally, NVIDIA may collect certain personal information, such as your name +and email address or those of your authorized users, and other information necessary to +authenticate and enable you or your authorized users’ access to the Software. Where appropriate +you will disclose to, and obtain any necessary consent from, your authorized users to allow NVIDIA +to collect such information. + +5.3 Third Party Privacy Practices. The Software may contain links to third party websites and services. +NVIDIA encourages you to review the privacy statements on those sites and services that you choose +to visit to understand how they may collect, use and share your data. NVIDIA is not responsible for +the privacy statements or practices of third-party sites or services. + +6. Updates. NVIDIA may at any time and at its option, change, discontinue, or deprecate any part, or all, of the +Software, or change or remove features or functionality, or make available patches, workarounds or other +updates to the Software. Unless the updates are provided with their separate governing terms, they are +deemed part of the Software licensed to you under this Agreement, and your continued use of the Software +is deemed acceptance of such changes. + +7. Components Under Other Licenses. The Software may include or be distributed with components provided +with separate legal notices or terms that accompany the components, such as open source software licenses +and other license terms (“Other Licenses”). The components are subject to the applicable Other Licenses, +including any proprietary notices, disclaimers, requirements and extended use rights; except that this +Agreement will prevail regarding the use of third-party open source software, unless a third-party open +source software license requires its license terms to prevail. Open source software license means any +software, data or documentation subject to any license identified as an open source license by the Open +Source Initiative (http://opensource.org), Free Software Foundation (http://www.fsf.org) or other similar +open source organization or listed by the Software Package Data Exchange (SPDX) Workgroup under the +Linux Foundation (http://www.spdx.org). + +8. Ownership. + +8.1 NVIDIA Ownership. The Software, including all intellectual property rights, is and will remain the sole +and exclusive property of NVIDIA or its licensors. Except as expressly granted in this Agreement, +(a) NVIDIA reserves all rights, interests and remedies in connection with the Software, and (b) no +other license or right is granted to you by implication, estoppel or otherwise. + +8.2 Your Ownership. Subject to the rights of NVIDIA and its suppliers in the Software, which continue to +be licensed as stated in this Agreement, even when incorporated in your products, and the extent +permitted by applicable law, as between you and NVIDIA, you hold all rights, title and interest in and +to your services, applications and Derivatives you develop as permitted in this Agreement including +their respective intellectual property rights. + +9. Feedback. You may, but are not obligated to, provide suggestions, requests, fixes, modifications, +enhancements or other feedback regarding your use of the Software (“Feedback”). Feedback, even if +designated as confidential by you, will not create any confidentiality obligation for NVIDIA or its affiliates. If +you provide Feedback, you hereby grant NVIDIA, its affiliates and its designees a nonexclusive, perpetual, +irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, under your +intellectual property rights, to publicly perform, publicly display, reproduce, use, make, have made, sell, +offer for sale, distribute (through multiple tiers of distribution), import, create derivative works of and +otherwise commercialize and exploit the Feedback at NVIDIA’s discretion. + +10. Confidentiality. You may use confidential information only to exercise your rights and perform your +obligations under this Agreement. You will not disclose, nor authorize others to disclose NVIDIA Confidential +Information to any third party, except as expressly authorized in this Agreement and as necessary for the +Purpose, without obtaining NVIDIA’s prior written approval. Each recipient of confidential information must +be subject to a written agreement that includes confidentiality obligations consistent with these terms and +must have a need to know for the Purpose. You will protect the NVIDIA Confidential Information with at +least the same degree of care that you use to protect your own similar confidential and proprietary +information, but no less than a reasonable degree of care. Confidential information includes, but is not +limited to, the Software, including its features and functionality, Derivatives, and any results of +benchmarking or other competitive analysis or regression or performance data relating to the Software. +No Publicity. You may not issue any public statements about this Agreement, disclose the Software or +Derivatives, or any information or results related to your use of the Software, without prior written approval +of NVIDIA. + +11. Term and Termination. + +11.1 Term. This Agreement has a duration of twelve (12) months starting from the date of initial +download (even if you download the same version or updates of the Software later and it is +accompanied by this Agreement or another Agreement), unless terminated earlier in accordance +with this Agreement. + +11.2 Termination for Convenience. Either party may terminate this Agreement at any time with thirty (30) +days’ advance written notice to the other party. + +11.3 Termination for Cause. If you commence or participate in any legal proceeding against NVIDIA with +respect to the Software, this Agreement will terminate immediately without notice. Either party may +terminate this Agreement upon notice for cause if: +(a) the other party fails to cure a material breach of this Agreement within ten (10) days of the +non-breaching party’s notice of the breach; or +(b) the other party breaches its confidentiality obligations or license rights under this +Agreement, which termination will be effective immediately upon written notice. + +11.4 Effect of Termination. Upon any expiration or termination of this Agreement, you will promptly +(a) stop using and return, delete or destroy NVIDIA confidential information and all Software +received under this Agreement, and (b) delete or destroy Derivatives created under this Agreement, +unless an authorized NVIDIA representative provides prior written approval that you may keep a +copy of the Derivatives solely for archival purposes. Upon written request, you will certify in writing +that you have complied with your obligations under this “Effect of Termination” Section. + +11.5 Survival. The “License Grant to NVIDIA”, “Updates”, “Components Under Other Licenses”, +“Ownership”, “Feedback”, “Confidentiality”, “No Publicity”, “Effect of Termination”, “Survival”, +“Disclaimer of Warranties”, “Limitation of Liability”, “Indemnity” and all “General” Sections of this +Agreement will survive any expiration or termination of this Agreement. + +12. Disclaimer of Warranties. THE SOFTWARE IS PROVIDED BY NVIDIA AS-IS AND WITH ALL FAULTS. TO THE +MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, NVIDIA DISCLAIMS ALL WARRANTIES AND +REPRESENTATIONS OF ANY KIND, WHETHER EXPRESS, IMPLIED OR STATUTORY, RELATING TO OR ARISING +UNDER THIS AGREEMENT, INCLUDING, WITHOUT LIMITATION, THE WARRANTIES OF TITLE, +NONINFRINGEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, USAGE OF TRADE AND +COURSE OF DEALING. NVIDIA DOES NOT WARRANT OR ASSUME RESPONSIBILITY FOR THE ACCURACY OR +COMPLETENESS OF ANY THIRD-PARTY INFORMATION, TEXT, GRAPHICS, LINKS CONTAINED IN THE +SOFTWARE. WITHOUT LIMITING THE FOREGOING, NVIDIA DOES NOT WARRANT THAT THE SOFTWARE WILL +MEET YOUR REQUIREMENTS, ANY DEFECTS OR ERRORS WILL BE CORRECTED, ANY CERTAIN CONTENT WILL +BE AVAILABLE; OR THAT THE SOFTWARE IS FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS. NO +INFORMATION OR ADVICE GIVEN BY NVIDIA WILL IN ANY WAY INCREASE THE SCOPE OF ANY WARRANTY +EXPRESSLY PROVIDED IN THIS AGREEMENT. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE +APPROPRIATENESS OF USING THE SOFTWARE OR DERIVATIVES AND ASSUME ANY RISKS ASSOCIATED WITH +YOUR USE OF THE SOFTWARE OR DERIVATIVES. + +13. Limitations of Liability. + +13.1 EXCLUSIONS. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL +NVIDIA BE LIABLE FOR ANY (A) INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL OR CONSEQUENTIAL +DAMAGES, OR (B) DAMAGES FOR THE (I) COST OF PROCURING SUBSTITUTE GOODS OR (II) LOSS OF +PROFITS, REVENUES, USE, DATA OR GOODWILL ARISING OUT OF OR RELATED TO THIS AGREEMENT, +WHETHER BASED ON BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, OR +OTHERWISE, AND EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND +EVEN IF A PARTY’S REMEDIES FAIL THEIR ESSENTIAL PURPOSE. + +13.2 DAMAGES CAP. ADDITIONALLY, TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, +NVIDIA’S TOTAL CUMULATIVE AGGREGATE LIABILITY FOR ANY AND ALL LIABILITIES, OBLIGATIONS OR +CLAIMS ARISING OUT OF OR RELATED TO THIS AGREEMENT WILL NOT EXCEED ONE HUNDRED U.S. +DOLLARS (US$100). + +14. Indemnity. You will defend, indemnify and hold harmless NVIDIA and its affiliates, and their respective +employees, contractors, agents, officers and directors, from and against any and all third party claims, +damages, obligations, losses, liabilities, costs or debt, fines, restitutions and expenses (including but not +limited to attorney’s fees and costs incident to establishing the right of indemnification) arising out of use of +the Software and Derivatives outside of the scope of this Agreement or in breach of the terms of this +Agreement. + +15. General. + +15.1 Governing Law and Jurisdiction. This Agreement will be governed in all respects by the laws of the +United States and the laws of the State of Delaware, without regard to conflict of laws principles or +the United Nations Convention on Contracts for the International Sale of Goods. The state and +federal courts residing in Santa Clara County, California will have exclusive jurisdiction over any +dispute or claim arising out of or related to this Agreement, and the parties irrevocably consent to +personal jurisdiction and venue in those courts; except that either party may apply for injunctive +remedies or an equivalent type of urgent legal relief in any jurisdiction. + +15.2 Independent Contractors. The parties are independent contractors, and this Agreement does not +create a joint venture, partnership, agency or other form of business association between the +parties. Neither party will have the power to bind the other party or incur any obligation on its +behalf without the other party’s prior written consent. Nothing in this Agreement prevents either +party from participating in similar arrangements with third parties. + +15.3 No Assignment. NVIDIA may assign, delegate or transfer its rights or obligations under this +Agreement by any means or operation of law. You may not, without NVIDIA’s prior written consent, +assign, delegate or transfer any of your rights or obligations under this Agreement by any means or +operation of law, and any attempt to do so is null and void. + +15.4 No Waiver. No failure or delay by a party to enforce any term or obligation of this Agreement will +operate as a waiver by that party, or prevent the enforcement of such term or obligation later. + +15.5 Trade Compliance. You agree to comply with all applicable export, import, trade and economic +sanctions laws and regulations, as amended, including without limitation U.S. Export Administration +Regulations and Office of Foreign Assets Control regulations. You confirm (a) your understanding +that export or reexport of certain NVIDIA products or technologies may require a license or other +approval from appropriate authorities and (b) that you will not export or reexport any products or +technology, directly or indirectly, without first obtaining any required license or other approval from +appropriate authorities, (i) to any countries that are subject to any U.S. or local export restrictions +(currently including, but not necessarily limited to, Belarus, Cuba, Iran, North Korea, Russia, Syria, +the Region of Crimea, Donetsk People’s Republic Region and Luhansk People’s Republic Region); (ii) +to any end-user who you know or have reason to know will utilize them in the design, development +or production of nuclear, chemical or biological weapons, missiles, rocket systems, unmanned air +vehicles capable of a maximum range of at least 300 kilometers, regardless of payload, or intended +for military end-use, or any weapons of mass destruction; (iii) to any end-user who has been +prohibited from participating in the U.S. or local export transactions by any governing authority; or +(iv) to any known military or military-intelligence end-user or for any known military or military- +intelligence end-use in accordance with U.S. trade compliance laws and regulations. + +15.6 Government Rights. The Software, documentation and technology (“Protected Items”) are +“Commercial products” as this term is defined at 48 C.F.R. 2.101, consisting of “commercial +computer software” and “commercial computer software documentation” as such terms are used +in, respectively, 48 C.F.R. 12.212 and 48 C.F.R. 227.7202 & 252.227-7014(a)(1). Before any Protected +Items are supplied to the U.S. Government, you will (i) inform the U.S. Government in writing that +the Protected Items are and must be treated as commercial computer software and commercial +computer software documentation developed at private expense; (ii) inform the U.S. Government +that the Protected Items are provided subject to the terms of the Agreement; and (iii) mark the +Protected Items as commercial computer software and commercial computer software +documentation developed at private expense. In no event will you permit the U.S. Government to +acquire rights in Protected Items beyond those specified in 48 C.F.R. 52.227-19(b)(1)-(2) or 252.227- +7013(c) except as expressly approved by NVIDIA in writing. + +15.7 Notices. Please direct your legal notices or other correspondence to legalnotices@nvidia.com with a +copy mailed to NVIDIA Corporation, 2788 San Tomas Expressway, Santa Clara, California 95051, +United States of America, Attention: Legal Department. If NVIDIA needs to contact you about the +Software, you consent to receive the notices by email and agree that such notices will satisfy any +legal communication requirements. + +15.8 Severability. If a court of competent jurisdiction rules that a provision of this Agreement is +unenforceable, that provision will be deemed modified to the extent necessary to make it +enforceable and the remainder of this Agreement will continue in full force and effect. + +15.9 Amendment. Any amendment to this Agreement must be in writing and signed by authorized +representatives of both parties. + +15.10 Entire Agreement. Regarding the subject matter of this Agreement, the parties agree that (a) this +Agreement constitutes the entire and exclusive agreement between the parties and supersedes all +prior and contemporaneous communications and (b) any additional or different terms or conditions, +whether contained in purchase orders, order acknowledgments, invoices or otherwise, will not be +binding and are null and void. + +(v. February 25, 2025) + +NVIDIA Confidential \ No newline at end of file diff --git a/deps/cloudxr/isaac/README.md b/deps/cloudxr/isaac/README.md new file mode 100644 index 0000000..f7b5e3a --- /dev/null +++ b/deps/cloudxr/isaac/README.md @@ -0,0 +1,5 @@ +# Run Isaac Lab Teleoperation + +This is the Isaac Lab Teleop client built on top of CloudXR.js. + +See the CloudXR.js SDK documentation > Show Cases > Isaac Lab Teleop for detailed guide. \ No newline at end of file diff --git a/deps/cloudxr/isaac/favicon.ico b/deps/cloudxr/isaac/favicon.ico new file mode 100644 index 0000000..a1de915 Binary files /dev/null and b/deps/cloudxr/isaac/favicon.ico differ diff --git a/deps/cloudxr/isaac/package-lock.json b/deps/cloudxr/isaac/package-lock.json new file mode 100644 index 0000000..2477e74 --- /dev/null +++ b/deps/cloudxr/isaac/package-lock.json @@ -0,0 +1,7089 @@ +{ + "name": "cloudxr-isaac-lab-teleop", + "version": "6.0.0-beta", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cloudxr-isaac-lab-teleop", + "version": "6.0.0-beta", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@nvidia/cloudxr": "file:../nvidia-cloudxr-6.0.0-beta.tgz", + "@react-three/drei": "^10.6.1", + "@react-three/fiber": "^9.3.0", + "@react-three/uikit": "^1.0.0", + "@react-three/uikit-default": "^1.0.0", + "@react-three/xr": "^6.6.22", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "three": "^0.172.0" + }, + "devDependencies": { + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@types/three": "^0.172.0", + "copy-webpack-plugin": "^13.0.0", + "css-loader": "^6.8.1", + "html-webpack-plugin": "^5.6.3", + "rimraf": "^5.0.5", + "style-loader": "^3.3.3", + "ts-loader": "^9.5.1", + "typescript": "^5.8.2", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.1" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", + "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", + "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", + "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", + "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "deprecated": "v0.2.x is no longer supported. Unless you are still using FontAwesome 5, please update to v3.1.1 or greater.", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@iwer/devui": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@iwer/devui/-/devui-1.1.2.tgz", + "integrity": "sha512-ggF1lXSX14BTYP0QzB4xaurySr2PC+3+rtK/dpCR++giWquzFv2mBw3LW/PaCtdl5mqkZMrQ2GSwfUNg9ZoO+w==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "6.6.0", + "@fortawesome/free-solid-svg-icons": "6.6.0", + "@fortawesome/react-fontawesome": "0.2.2", + "@pmndrs/handle": "^6.6.17", + "@pmndrs/pointer-events": "^6.6.17", + "react": ">=18.3.1", + "react-dom": ">=18.3.1", + "styled-components": "^6.1.13", + "three": "^0.165.0" + }, + "peerDependencies": { + "iwer": "^2.0.1" + } + }, + "node_modules/@iwer/devui/node_modules/three": { + "version": "0.165.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.165.0.tgz", + "integrity": "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==", + "license": "MIT" + }, + "node_modules/@iwer/sem": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@iwer/sem/-/sem-0.2.5.tgz", + "integrity": "sha512-vMCfpu/7Qqc+hkBiGD9pxjeObgrhXOrL0KX94CA3yzJaU0dq0y49HXZT6fC+6X/jOmjaM3hjyE1m2h7ZmLzzyA==", + "license": "MIT", + "dependencies": { + "three": "^0.165.0", + "ts-proto": "^2.6.0" + }, + "peerDependencies": { + "iwer": "^2.0.0" + } + }, + "node_modules/@iwer/sem/node_modules/three": { + "version": "0.165.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.165.0.tgz", + "integrity": "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==", + "license": "MIT" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.1.tgz", + "integrity": "sha512-YrEi/ZPmgc+GfdO0esBF04qv8boK9Dg9WpRQw/+vM8Qt3nnVIJWIa8HwZ/LXVZ0DB11XUROM8El/7yYTJX+WtA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.1.tgz", + "integrity": "sha512-ooEPvSW/HQDivPDPZMibHGKZf/QS4WRir1czGZmXmp3MsQqLECZEpN0JobrD8iV9BzsuwdIv+PxtWX9WpPLsIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.1", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.1.tgz", + "integrity": "sha512-3YaKhP8gXEKN+2O49GLNfNb5l2gbnCFHyAaybbA2JkkbQP3dpdef7WcUaHAulg/c5Dg4VncHsA3NWAUSZMR5KQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.1", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1", + "@jsonjoy.com/fs-print": "4.57.1", + "@jsonjoy.com/fs-snapshot": "4.57.1", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.1.tgz", + "integrity": "sha512-XHkFKQ5GSH3uxm8c3ZYXVrexGdscpWKIcMWKFQpMpMJc8gA3AwOMBJXJlgpdJqmrhPyQXxaY9nbkNeYpacC0Og==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.1.tgz", + "integrity": "sha512-pqGHyWWzNck4jRfaGV39hkqpY5QjRUQ/nRbNT7FYbBa0xf4bDG+TE1Gt2KWZrSkrkZZDE3qZUjYMbjwSliX6pg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.57.1", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.1.tgz", + "integrity": "sha512-vp+7ZzIB8v43G+GLXTS4oDUSQmhAsRz532QmmWBbdYA20s465JvwhkSFvX9cVTqRRAQg+vZ7zWDaIEh0lFe2gw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.57.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.1.tgz", + "integrity": "sha512-Ynct7ZJmfk6qoXDOKfpovNA36ITUx8rChLmRQtW08J73VOiuNsU8PB6d/Xs7fxJC2ohWR3a5AqyjmLojfrw5yw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.57.1", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.1.tgz", + "integrity": "sha512-/oG8xBNFMbDXTq9J7vepSA1kerS5vpgd3p5QZSPd+nX59uwodGJftI51gDYyHRpP57P3WCQf7LHtBYPqwUg2Bg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.57.1", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mediapipe/tasks-vision": { + "version": "0.10.17", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", + "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==", + "license": "Apache-2.0" + }, + "node_modules/@monogrid/gainmap-js": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz", + "integrity": "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==", + "license": "MIT", + "dependencies": { + "promise-worker-transferable": "^1.0.4" + }, + "peerDependencies": { + "three": ">= 0.159.0" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nvidia/cloudxr": { + "version": "6.0.0-beta", + "resolved": "file:../nvidia-cloudxr-6.0.0-beta.tgz", + "integrity": "sha512-3hhRzWWmNrTFMD7QxLOkZRYEJrWYH0q4hYG+j4hc1ZyhDsD0vmEZNNoJG9wp2/PsTfjlGm9JLtcCKCiZkkqaIw==", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "gl-matrix": "^3.4.3", + "long": "^5.3.2" + } + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", + "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", + "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", + "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", + "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-rsa": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", + "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", + "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pfx": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", + "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", + "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", + "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pmndrs/handle": { + "version": "6.6.29", + "resolved": "https://registry.npmjs.org/@pmndrs/handle/-/handle-6.6.29.tgz", + "integrity": "sha512-zfvakgxva2P5nB6HeOuSMGyFFFP6B5cRO61DeHKSm4z7WRW2FT8baaxW5izsYqymzc4Xutc9fpeIxbFL/GbmAw==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/pointer-events": "~6.6.29", + "zustand": "^4.5.2" + } + }, + "node_modules/@pmndrs/handle/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@pmndrs/msdfonts": { + "version": "1.0.64", + "resolved": "https://registry.npmjs.org/@pmndrs/msdfonts/-/msdfonts-1.0.64.tgz", + "integrity": "sha512-fyDNvCIGUTUYxDOzQnLOAGEuDQbyjx1JUlEYrjHB8KLDWzVl0j2SoFiLN4AwahEh4pXiA7XdJBNneQKnfMa/Bw==", + "license": "SEE LICENSE IN LICENSE" + }, + "node_modules/@pmndrs/pointer-events": { + "version": "6.6.29", + "resolved": "https://registry.npmjs.org/@pmndrs/pointer-events/-/pointer-events-6.6.29.tgz", + "integrity": "sha512-o4YD6VfJgDYjFgde/YyAw2X5KY454tdmOXrHGOvKTWJBHzkL90B5vH4rqmexwRVvaDfT3YLvVh/Dm5cBbgZXMg==", + "license": "SEE LICENSE IN LICENSE" + }, + "node_modules/@pmndrs/uikit": { + "version": "1.0.64", + "resolved": "https://registry.npmjs.org/@pmndrs/uikit/-/uikit-1.0.64.tgz", + "integrity": "sha512-FQgF8sf46U6fmHnqOn32S04lnWiXVxQO6MatL7FgoLKJ74YIBgEHgwKHRwltgLrN/rcTu7LgJm7LgvXvMccEGA==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/msdfonts": "^1.0.64", + "@pmndrs/uikit-pub-sub": "^1.0.64", + "@preact/signals-core": "^1.5.1", + "@zappar/msdf-generator": "^1.2.4", + "yoga-layout": "^3.2.1" + }, + "peerDependencies": { + "three": ">=0.162" + } + }, + "node_modules/@pmndrs/uikit-default": { + "version": "1.0.64", + "resolved": "https://registry.npmjs.org/@pmndrs/uikit-default/-/uikit-default-1.0.64.tgz", + "integrity": "sha512-247ak5lysNU9NGGuxOvdOas65aa78eGQTRWktmF9ymFLyqHdti0mlby5oqtFV/gIVjF0ZHNvdwinnUhm9pX8RA==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/uikit": "^1.0.64", + "@pmndrs/uikit-lucide": "^1.0.64" + } + }, + "node_modules/@pmndrs/uikit-lucide": { + "version": "1.0.64", + "resolved": "https://registry.npmjs.org/@pmndrs/uikit-lucide/-/uikit-lucide-1.0.64.tgz", + "integrity": "sha512-WU9CqboaBJHLvAk3Qt6V+7oMSk2lafY/ZwdcjuwSt3U+ZExHMnM24APeBdwi6w0e0sER2I/qiSVDs26JPOg7ew==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/uikit": "^1.0.64" + } + }, + "node_modules/@pmndrs/uikit-pub-sub": { + "version": "1.0.64", + "resolved": "https://registry.npmjs.org/@pmndrs/uikit-pub-sub/-/uikit-pub-sub-1.0.64.tgz", + "integrity": "sha512-+hlc5aWC3wQlA/zihwh+OGSm3aY9O+L/TEuNLwXw6R8syHMv7obqeY6s/hcjzD92k6DRQZLhVzVy+yqqVKAbRA==", + "license": "MIT", + "dependencies": { + "@preact/signals-core": "^1.8.0" + } + }, + "node_modules/@pmndrs/xr": { + "version": "6.6.29", + "resolved": "https://registry.npmjs.org/@pmndrs/xr/-/xr-6.6.29.tgz", + "integrity": "sha512-+17cGaV6tlYM6UJznKiPR1qw39NQDAvYchb2TRg8MnxUc9hOXKe/3a62DlLIe3uGfK9dnd7DlZlegeR/jIDEWg==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@iwer/devui": "^1.1.1", + "@iwer/sem": "~0.2.5", + "@pmndrs/pointer-events": "~6.6.29", + "iwer": "^2.1.0", + "meshline": "^3.3.1", + "zustand": "^4.5.2" + }, + "peerDependencies": { + "three": "*" + } + }, + "node_modules/@pmndrs/xr/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@preact/signals-core": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.0.tgz", + "integrity": "sha512-AowtCcCU/33lFlh1zRFf/u+12rfrhtNakj7UpaGEsmMwUKpKWMVvcktOGcwBBNiB4lWrZWc01LhiyyzVklJyaQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/@react-three/drei": { + "version": "10.7.7", + "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz", + "integrity": "sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mediapipe/tasks-vision": "0.10.17", + "@monogrid/gainmap-js": "^3.0.6", + "@use-gesture/react": "^10.3.1", + "camera-controls": "^3.1.0", + "cross-env": "^7.0.3", + "detect-gpu": "^5.0.56", + "glsl-noise": "^0.0.0", + "hls.js": "^1.5.17", + "maath": "^0.10.8", + "meshline": "^3.3.1", + "stats-gl": "^2.2.8", + "stats.js": "^0.17.0", + "suspend-react": "^0.1.3", + "three-mesh-bvh": "^0.8.3", + "three-stdlib": "^2.35.6", + "troika-three-text": "^0.52.4", + "tunnel-rat": "^0.1.2", + "use-sync-external-store": "^1.4.0", + "utility-types": "^3.11.0", + "zustand": "^5.0.1" + }, + "peerDependencies": { + "@react-three/fiber": "^9.0.0", + "react": "^19", + "react-dom": "^19", + "three": ">=0.159" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz", + "integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8", + "@types/webxr": "*", + "base64-js": "^1.5.1", + "buffer": "^6.0.3", + "its-fine": "^2.0.0", + "react-use-measure": "^2.1.7", + "scheduler": "^0.27.0", + "suspend-react": "^0.1.3", + "use-sync-external-store": "^1.4.0", + "zustand": "^5.0.3" + }, + "peerDependencies": { + "expo": ">=43.0", + "expo-asset": ">=8.4", + "expo-file-system": ">=11.0", + "expo-gl": ">=11.0", + "react": ">=19 <19.3", + "react-dom": ">=19 <19.3", + "react-native": ">=0.78", + "three": ">=0.156" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-file-system": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@react-three/uikit": { + "version": "1.0.64", + "resolved": "https://registry.npmjs.org/@react-three/uikit/-/uikit-1.0.64.tgz", + "integrity": "sha512-//wUWePJHHHWgDokWs2WUs8yUqOJ1Dy/g1OufGHw/BeTujtoNG0hmuISjdeDXZOQPfNjpMgrbemfmvryTeCfLg==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/pointer-events": "^6.6.29", + "@pmndrs/uikit": "^1.0.64", + "@preact/signals-core": "^1.5.1", + "suspend-react": "^0.1.3", + "zustand": "^5.0.6" + }, + "peerDependencies": { + "@react-three/fiber": ">=8", + "react": ">=18" + } + }, + "node_modules/@react-three/uikit-default": { + "version": "1.0.64", + "resolved": "https://registry.npmjs.org/@react-three/uikit-default/-/uikit-default-1.0.64.tgz", + "integrity": "sha512-OoHSHevT0L8v6LCiNdpxsSMTRAYXa00H67BD+jmaMMrB/ehFGvXtfEqAze5HRoIMwW2mppg3phy5T1GNtuVvWw==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/uikit-default": "^1.0.64", + "@react-three/uikit": "^1.0.64" + } + }, + "node_modules/@react-three/xr": { + "version": "6.6.29", + "resolved": "https://registry.npmjs.org/@react-three/xr/-/xr-6.6.29.tgz", + "integrity": "sha512-qHegmgqTuao+8WC3MylTqg1HZWCw0b4hRH+rF4Em0kRaWU8gYCGpSYnq8fDrG6QlzfEUTTz5b235BwjUvi6oWw==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/pointer-events": "~6.6.29", + "@pmndrs/xr": "~6.6.29", + "suspend-react": "^0.1.3", + "tunnel-rat": "^0.1.2", + "zustand": "^4.5.2" + }, + "peerDependencies": { + "@react-three/fiber": ">=8", + "react": ">=18", + "react-dom": ">=18", + "three": "*" + } + }, + "node_modules/@react-three/xr/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/draco3d": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz", + "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==", + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/react-reconciler": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", + "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "license": "MIT" + }, + "node_modules/@types/stylis": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.7.tgz", + "integrity": "sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==", + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.172.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.172.0.tgz", + "integrity": "sha512-LrUtP3FEG26Zg5WiF0nbg8VoXiKokBLTcqM2iLvM9vzcfEiYmmBAPGdBgV0OYx9fvWlY3R/3ERTZcD9X5sc0NA==", + "license": "MIT", + "dependencies": { + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": "*", + "@webgpu/types": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~0.18.1" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@use-gesture/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", + "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==", + "license": "MIT" + }, + "node_modules/@use-gesture/react": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz", + "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==", + "license": "MIT", + "dependencies": { + "@use-gesture/core": "10.3.1" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webgpu/types": { + "version": "0.1.69", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", + "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@zappar/msdf-generator": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@zappar/msdf-generator/-/msdf-generator-1.2.4.tgz", + "integrity": "sha512-6S/MCk0Ky0ipewZJw4xFEzH/2aYfWmPXEkTdBtNyDDfkbicrNwgJgtxZ4SnTDyNe9XHMqDA4sL9srRsgDLRMqA==", + "license": "MIT", + "dependencies": { + "comlink": "^4.4.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/asn1js": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", + "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", + "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camera-controls": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.0.tgz", + "integrity": "sha512-w5oULNpijgTRH0ARFJJ0R5ct1nUM3R3WP7/b8A6j9uTGpRfnsypc/RBMPQV8JQDPayUe37p/TZZY1PcUr4czOQ==", + "license": "MIT", + "engines": { + "node": ">=20.11.0", + "npm": ">=10.8.2" + }, + "peerDependencies": { + "three": ">=0.126.1" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001781", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0", + "peer": true + }, + "node_modules/case-anything": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz", + "integrity": "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/comlink": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz", + "integrity": "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==", + "license": "Apache-2.0" + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", + "integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-parent": "^6.0.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2", + "tinyglobby": "^0.2.12" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-gpu": { + "version": "5.0.70", + "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz", + "integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==", + "license": "MIT", + "dependencies": { + "webgl-constants": "^1.1.1" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dprint-node": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.8.tgz", + "integrity": "sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==", + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3" + } + }, + "node_modules/draco3d": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz", + "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==", + "license": "Apache-2.0" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.325", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.325.tgz", + "integrity": "sha512-PwfIw7WQSt3xX7yOf5OE/unLzsK9CaN2f/FvV3WjPR1Knoc1T9vePRVV4W1EM301JzzysK51K7FNKcusCr0zYA==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/glsl-noise": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz", + "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==", + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hls.js": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.15.tgz", + "integrity": "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==", + "license": "Apache-2.0" + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.6", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz", + "integrity": "sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-network-error": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", + "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/its-fine": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz", + "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==", + "license": "MIT", + "dependencies": { + "@types/react-reconciler": "^0.28.9" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/iwer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/iwer/-/iwer-2.1.1.tgz", + "integrity": "sha512-3VuQhekh/3BMRlaS7FFjcTNjKOwURAgL7mu0HndU72mFNyRyHRpGfhXGZ1iJkjfq/vEw0v1b7fx8k1knGSQ5gQ==", + "license": "MIT", + "dependencies": { + "gl-matrix": "^3.4.3", + "webxr-layers-polyfill": "^1.1.0" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", + "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/maath": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz", + "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==", + "license": "MIT", + "peerDependencies": { + "@types/three": ">=0.134.0", + "three": ">=0.134.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.1.tgz", + "integrity": "sha512-WvzrWPwMQT+PtbX2Et64R4qXKK0fj/8pO85MrUCzymX3twwCiJCdvntW3HdhG1teLJcHDDLIKx5+c3HckWYZtQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.1", + "@jsonjoy.com/fs-fsa": "4.57.1", + "@jsonjoy.com/fs-node": "4.57.1", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-to-fsa": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1", + "@jsonjoy.com/fs-print": "4.57.1", + "@jsonjoy.com/fs-snapshot": "4.57.1", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/meshline": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz", + "integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==", + "license": "MIT", + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", + "license": "ISC" + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/promise-worker-transferable": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz", + "integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==", + "license": "Apache-2.0", + "dependencies": { + "is-promise": "^2.1.0", + "lie": "^3.0.2" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-use-measure": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", + "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/spdy-transport/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/spdy/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/spdy/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stats-gl": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz", + "integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==", + "license": "MIT", + "dependencies": { + "@types/three": "*", + "three": "^0.170.0" + }, + "peerDependencies": { + "@types/three": "*", + "three": "*" + } + }, + "node_modules/stats-gl/node_modules/three": { + "version": "0.170.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz", + "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==", + "license": "MIT" + }, + "node_modules/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/styled-components": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.3.12.tgz", + "integrity": "sha512-hFR6xsVkVYbsdcUlzPYFvFfoc6o2KlV0VvgRIQwSYMtdThM7SCxnjX9efh/cWce2kTq16I/Kl3xM98xiLptsXA==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.4.0", + "@emotion/unitless": "0.10.0", + "@types/stylis": "4.2.7", + "css-to-react-native": "3.2.0", + "csstype": "3.2.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.6", + "tslib": "2.8.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0" + } + }, + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/thingies": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz", + "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/three": { + "version": "0.172.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.172.0.tgz", + "integrity": "sha512-6HMgMlzU97MsV7D/tY8Va38b83kz8YJX+BefKjspMNAv0Vx6dxMogHOrnRl/sbMIs3BPUKijPqDqJ/+UwJbIow==", + "license": "MIT" + }, + "node_modules/three-mesh-bvh": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.8.3.tgz", + "integrity": "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==", + "license": "MIT", + "peerDependencies": { + "three": ">= 0.159.0" + } + }, + "node_modules/three-stdlib": { + "version": "2.36.1", + "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz", + "integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==", + "license": "MIT", + "dependencies": { + "@types/draco3d": "^1.4.0", + "@types/offscreencanvas": "^2019.6.4", + "@types/webxr": "^0.5.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "potpack": "^1.0.1" + }, + "peerDependencies": { + "three": ">=0.128.0" + } + }, + "node_modules/three-stdlib/node_modules/fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", + "license": "MIT" + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/troika-three-text": { + "version": "0.52.4", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz", + "integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==", + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.2", + "troika-three-utils": "^0.52.4", + "troika-worker-utils": "^0.52.0", + "webgl-sdf-generator": "1.1.1" + }, + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-three-utils": { + "version": "0.52.4", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz", + "integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==", + "license": "MIT", + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-worker-utils": { + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz", + "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==", + "license": "MIT" + }, + "node_modules/ts-loader": { + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", + "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ts-poet": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.12.0.tgz", + "integrity": "sha512-xo+iRNMWqyvXpFTaOAvLPA5QAWO6TZrSUs5s4Odaya3epqofBu/fMLHEWl8jPmjhA0s9sgj9sNvF1BmaQlmQkA==", + "license": "Apache-2.0", + "dependencies": { + "dprint-node": "^1.0.8" + } + }, + "node_modules/ts-proto": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-2.11.6.tgz", + "integrity": "sha512-2rPkH5W/KeXOyVUC6o06RdRabVK8zSDmQpnRz4XbRiYMHRdI12KqDjAdGW7ebxzzMNE5cw/j+ptA0WMVqZILrQ==", + "license": "ISC", + "dependencies": { + "@bufbuild/protobuf": "^2.10.2", + "case-anything": "^2.1.13", + "ts-poet": "^6.12.0", + "ts-proto-descriptors": "2.1.0" + }, + "bin": { + "protoc-gen-ts_proto": "protoc-gen-ts_proto" + } + }, + "node_modules/ts-proto-descriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-2.1.0.tgz", + "integrity": "sha512-S5EZYEQ6L9KLFfjSRpZWDIXDV/W7tAj8uW7pLsihIxyr62EAVSiKuVPwE8iWnr849Bqa53enex1jhDUcpgquzA==", + "license": "ISC", + "dependencies": { + "@bufbuild/protobuf": "^2.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel-rat": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz", + "integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==", + "license": "MIT", + "dependencies": { + "zustand": "^4.3.2" + } + }, + "node_modules/tunnel-rat/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webgl-constants": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", + "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" + }, + "node_modules/webgl-sdf-generator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", + "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==", + "license": "MIT" + }, + "node_modules/webpack": { + "version": "5.105.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", + "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", + "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.25", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.8.1", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.22.1", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^5.5.0", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webxr-layers-polyfill": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/webxr-layers-polyfill/-/webxr-layers-polyfill-1.1.0.tgz", + "integrity": "sha512-GqWE6IFlut8a1Lnh9t1RPnOXud1rZ7wLPvWp7mqTDOYtgorXqlNMhEnI9EqjU33grBx0v3jm0Oc13opkAdmgMQ==", + "license": "Apache-2.0", + "dependencies": { + "gl-matrix": "^3.4.3" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, + "node_modules/zustand": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", + "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/deps/cloudxr/isaac/package.json b/deps/cloudxr/isaac/package.json new file mode 100644 index 0000000..3d2ab51 --- /dev/null +++ b/deps/cloudxr/isaac/package.json @@ -0,0 +1,47 @@ +{ + "name": "cloudxr-isaac-lab-teleop", + "version": "6.0.0-beta", + "private": true, + "description": "CloudXR.js Isaac Lab Teleop application", + "author": "NVIDIA Corporation", + "license": "SEE LICENSE IN LICENSE", + "keywords": [ + "react", + "three.js", + "webxr", + "cloudxr", + "vr" + ], + "scripts": { + "build": "webpack --config ./webpack.prod.js", + "dev": "webpack --config ./webpack.dev.js", + "dev-server": "webpack serve --config ./webpack.dev.js --no-open", + "dev-server:https": "HTTPS=true webpack serve --config ./webpack.dev.js --no-open", + "clean": "rimraf dist" + }, + "dependencies": { + "@nvidia/cloudxr": "file:../nvidia-cloudxr-6.0.0-beta.tgz", + "@react-three/drei": "^10.6.1", + "@react-three/fiber": "^9.3.0", + "@react-three/uikit": "^1.0.0", + "@react-three/uikit-default": "^1.0.0", + "@react-three/xr": "^6.6.22", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "three": "^0.172.0" + }, + "devDependencies": { + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@types/three": "^0.172.0", + "copy-webpack-plugin": "^13.0.0", + "css-loader": "^6.8.1", + "html-webpack-plugin": "^5.6.3", + "rimraf": "^5.0.5", + "style-loader": "^3.3.3", + "ts-loader": "^9.5.1", + "typescript": "^5.8.2", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.1" + } +} diff --git a/deps/cloudxr/isaac/public/HEROICONS_LICENSE b/deps/cloudxr/isaac/public/HEROICONS_LICENSE new file mode 100644 index 0000000..d6a8229 --- /dev/null +++ b/deps/cloudxr/isaac/public/HEROICONS_LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Tailwind Labs, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/deps/cloudxr/isaac/public/arrow-left-start-on-rectangle.svg b/deps/cloudxr/isaac/public/arrow-left-start-on-rectangle.svg new file mode 100644 index 0000000..85d9580 --- /dev/null +++ b/deps/cloudxr/isaac/public/arrow-left-start-on-rectangle.svg @@ -0,0 +1,4 @@ + + diff --git a/deps/cloudxr/isaac/public/arrow-uturn-left.svg b/deps/cloudxr/isaac/public/arrow-uturn-left.svg new file mode 100644 index 0000000..22c8189 --- /dev/null +++ b/deps/cloudxr/isaac/public/arrow-uturn-left.svg @@ -0,0 +1,4 @@ + + diff --git a/deps/cloudxr/isaac/public/assets/hdri/potsdamer_platz_1k.hdr b/deps/cloudxr/isaac/public/assets/hdri/potsdamer_platz_1k.hdr new file mode 100644 index 0000000..e3121c1 Binary files /dev/null and b/deps/cloudxr/isaac/public/assets/hdri/potsdamer_platz_1k.hdr differ diff --git a/deps/cloudxr/isaac/public/controller-icons.svg b/deps/cloudxr/isaac/public/controller-icons.svg new file mode 100644 index 0000000..1d87b2a --- /dev/null +++ b/deps/cloudxr/isaac/public/controller-icons.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/generic-hand/left.glb b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/generic-hand/left.glb new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/generic-hand/profile.json b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/generic-hand/profile.json new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/generic-hand/right.glb b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/generic-hand/right.glb new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/generic-trigger/left.glb b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/generic-trigger/left.glb new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/generic-trigger/profile.json b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/generic-trigger/profile.json new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/generic-trigger/right.glb b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/generic-trigger/right.glb new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/meta-quest-touch-plus/left.glb b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/meta-quest-touch-plus/left.glb new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/meta-quest-touch-plus/profile.json b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/meta-quest-touch-plus/profile.json new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/meta-quest-touch-plus/right.glb b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/meta-quest-touch-plus/right.glb new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/oculus-touch-v2/left.glb b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/oculus-touch-v2/left.glb new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/oculus-touch-v2/profile.json b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/oculus-touch-v2/profile.json new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/oculus-touch-v2/right.glb b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/oculus-touch-v2/right.glb new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/oculus-touch-v3/left.glb b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/oculus-touch-v3/left.glb new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/oculus-touch-v3/profile.json b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/oculus-touch-v3/profile.json new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/oculus-touch-v3/right.glb b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/oculus-touch-v3/right.glb new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/pico-4u/left.glb b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/pico-4u/left.glb new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/pico-4u/profile.json b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/pico-4u/profile.json new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/pico-4u/right.glb b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/pico-4u/right.glb new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/profilesList.json b/deps/cloudxr/isaac/public/npm/@webxr-input-profiles/assets@1.0.19/dist/profiles/profilesList.json new file mode 100644 index 0000000..e69de29 diff --git a/deps/cloudxr/isaac/public/play-circle.svg b/deps/cloudxr/isaac/public/play-circle.svg new file mode 100644 index 0000000..37cb3ce --- /dev/null +++ b/deps/cloudxr/isaac/public/play-circle.svg @@ -0,0 +1,4 @@ + + diff --git a/deps/cloudxr/isaac/public/stop-circle.svg b/deps/cloudxr/isaac/public/stop-circle.svg new file mode 100644 index 0000000..9e3dcdc --- /dev/null +++ b/deps/cloudxr/isaac/public/stop-circle.svg @@ -0,0 +1,4 @@ + + diff --git a/deps/cloudxr/isaac/src/App.tsx b/deps/cloudxr/isaac/src/App.tsx new file mode 100644 index 0000000..4f58fd9 --- /dev/null +++ b/deps/cloudxr/isaac/src/App.tsx @@ -0,0 +1,558 @@ +/** + * App.tsx - Main CloudXR React Application + * + * This is the root component of the CloudXR React example application. It sets up: + * - WebXR session management and XR store configuration + * - CloudXR server configuration (IP, port, stream settings) + * - UI state management (connection status, session state) + * - Integration between CloudXR rendering component and UI components + * - Entry point for AR/VR experiences with CloudXR streaming + * + * The app integrates with the HTML interface which provides a "CONNECT" button + * to enter AR mode and displays the CloudXR UI with controls for teleop actions + * and disconnect when in XR mode. + */ + +import { checkCapabilities } from '@helpers/BrowserCapabilities'; +import { loadIWERIfNeeded } from '@helpers/LoadIWER'; +import { overridePressureObserver } from '@helpers/overridePressureObserver'; +import * as CloudXR from '@nvidia/cloudxr'; +import { Environment } from '@react-three/drei'; +import { Canvas } from '@react-three/fiber'; +import { setPreferredColorScheme } from '@react-three/uikit'; +import { XR, createXRStore, noEvents, PointerEvents, XROrigin, useXR } from '@react-three/xr'; +import { useState, useMemo, useEffect, useRef } from 'react'; + +import { CloudXR2DUI } from './CloudXR2DUI'; +import CloudXRComponent from './CloudXRComponent'; +import CloudXR3DUI from './CloudXRUI'; + +// Override PressureObserver early to catch errors from buggy browser implementations +overridePressureObserver(); + +const store = createXRStore({ + foveation: 0, + emulate: false, // Disable IWER emulation from react in favor of custom iwer loading function + // Configure WebXR input profiles to use local assets + // Use relative path from current page location + baseAssetPath: `${new URL('.', window.location).href}npm/@webxr-input-profiles/assets@${process.env.WEBXR_ASSETS_VERSION}/dist/profiles/`, + hand: { + model: false, // Disable hand models but keep pointer functionality + }, + controller: { + model: false, // Disable controller models but keep pointer functionality + }, +}); + +setPreferredColorScheme('dark'); + +const START_TELEOP_COMMAND = { + type: 'teleop_command', + message: { + command: 'start teleop', + }, +} as const; + +// Environment component like controller-test +function NonAREnvironment() { + // Use local HDR file instead of preset so client doesn't need to download it from CDN + return ( + + ); +} + +function App() { + const COUNTDOWN_MAX_SECONDS = 9; + const COUNTDOWN_STORAGE_KEY = 'cxr.react.countdownSeconds'; + // 2D UI management + const [cloudXR2DUI, setCloudXR2DUI] = useState(null); + // IWER loading state + const [iwerLoaded, setIwerLoaded] = useState(false); + // Capability state management + const [capabilitiesValid, setCapabilitiesValid] = useState(false); + const capabilitiesCheckedRef = useRef(false); + // Connection state management + const [isConnected, setIsConnected] = useState(false); + // Session status management + const [sessionStatus, setSessionStatus] = useState('Disconnected'); + // Error message management + const [errorMessage, setErrorMessage] = useState(''); + // CloudXR session reference + const [cloudXRSession, setCloudXRSession] = useState(null); + // XR mode state for UI visibility + const [isXRMode, setIsXRMode] = useState(false); + // Server address being used for connection + const [serverAddress, setServerAddress] = useState(''); + // Teleop countdown and state + const [isCountingDown, setIsCountingDown] = useState(false); + const [countdownRemaining, setCountdownRemaining] = useState(0); + const [isTeleopRunning, setIsTeleopRunning] = useState(false); + const countdownTimerRef = useRef(null); + const [countdownDuration, setCountdownDuration] = useState(() => { + try { + const saved = localStorage.getItem(COUNTDOWN_STORAGE_KEY); + if (saved != null) { + const value = parseInt(saved, 10); + if (!isNaN(value)) { + return Math.min(COUNTDOWN_MAX_SECONDS, Math.max(0, value)); + } + } + } catch (_) {} + return 3; + }); + + // Persist countdown duration on change + useEffect(() => { + try { + localStorage.setItem(COUNTDOWN_STORAGE_KEY, String(countdownDuration)); + } catch (_) {} + }, [countdownDuration]); + + // Load IWER first (must happen before anything else) + // Note: React Three Fiber's emulation is disabled (emulate: false) to avoid conflicts + useEffect(() => { + const loadIWER = async () => { + const { supportsImmersive, iwerLoaded: wasIwerLoaded } = await loadIWERIfNeeded(); + if (!supportsImmersive) { + setErrorMessage('Immersive mode not supported'); + setIwerLoaded(false); + setCapabilitiesValid(false); + capabilitiesCheckedRef.current = false; // Reset check flag on failure + return; + } + // IWER loaded successfully, now we can proceed with capability checks + setIwerLoaded(true); + // Store whether IWER was loaded for status message display later + if (wasIwerLoaded) { + sessionStorage.setItem('iwerWasLoaded', 'true'); + } + }; + + loadIWER(); + }, []); + + // Update button state when IWER fails and UI becomes ready + useEffect(() => { + if (cloudXR2DUI && !iwerLoaded && !capabilitiesValid) { + cloudXR2DUI.setStartButtonState(true, 'CONNECT (immersive mode not supported)'); + } + }, [cloudXR2DUI, iwerLoaded, capabilitiesValid]); + + // Check capabilities once CloudXR2DUI is ready and IWER is loaded + useEffect(() => { + const checkCapabilitiesOnce = async () => { + if (!cloudXR2DUI || !iwerLoaded) { + return; + } + + // Guard: only check capabilities once + if (capabilitiesCheckedRef.current) { + return; + } + capabilitiesCheckedRef.current = true; + + // Disable button and show checking status + cloudXR2DUI.setStartButtonState(true, 'CONNECT (checking capabilities)'); + + let result: { success: boolean; failures: string[]; warnings: string[] } = { + success: false, + failures: [], + warnings: [], + }; + try { + result = await checkCapabilities(); + } catch (error) { + cloudXR2DUI.showStatus(`Capability check error: ${error}`, 'error'); + setCapabilitiesValid(false); + cloudXR2DUI.setStartButtonState(true, 'CONNECT (capability check failed)'); + capabilitiesCheckedRef.current = false; // Reset on error for potential retry + return; + } + if (!result.success) { + cloudXR2DUI.showStatus( + 'Browser does not meet required capabilities:\n' + result.failures.join('\n'), + 'error' + ); + setCapabilitiesValid(false); + cloudXR2DUI.setStartButtonState(true, 'CONNECT (capability check failed)'); + capabilitiesCheckedRef.current = false; // Reset on failure for potential retry + return; + } + + // Show final status message with IWER info if applicable + const iwerWasLoaded = sessionStorage.getItem('iwerWasLoaded') === 'true'; + if (result.warnings.length > 0) { + cloudXR2DUI.showStatus('Performance notice:\n' + result.warnings.join('\n'), 'info'); + } else if (iwerWasLoaded) { + // Include IWER status in the final success message + cloudXR2DUI.showStatus( + 'CloudXR.js SDK is supported. Ready to connect!\nUsing IWER (Immersive Web Emulator Runtime) - Emulating Meta Quest 3.', + 'info' + ); + } else { + cloudXR2DUI.showStatus('CloudXR.js SDK is supported. Ready to connect!', 'success'); + } + + setCapabilitiesValid(true); + cloudXR2DUI.setStartButtonState(false, 'CONNECT'); + }; + + checkCapabilitiesOnce(); + }, [cloudXR2DUI, iwerLoaded]); + + // Track config changes to trigger re-renders when form values change + const [configVersion, setConfigVersion] = useState(0); + + // Initialize CloudXR2DUI + useEffect(() => { + // Create and initialize the 2D UI manager + const ui = new CloudXR2DUI(() => { + // Callback when configuration changes + setConfigVersion(v => v + 1); + }); + ui.initialize(); + ui.setupConnectButtonHandler( + async () => { + // Start XR session + if (ui.getConfiguration().immersiveMode === 'ar') { + await store.enterAR(); + } else if (ui.getConfiguration().immersiveMode === 'vr') { + await store.enterVR(); + } else { + setErrorMessage('Unrecognized immersive mode'); + } + }, + (error: Error) => { + setErrorMessage(`Failed to start XR session: ${error}`); + } + ); + + setCloudXR2DUI(ui); + + // Cleanup function + return () => { + if (ui) { + ui.cleanup(); + } + }; + }, []); + + // Update HTML error message display when error state changes + useEffect(() => { + if (cloudXR2DUI) { + if (errorMessage) { + cloudXR2DUI.showError(errorMessage); + } else { + cloudXR2DUI.hideError(); + } + } + }, [errorMessage, cloudXR2DUI]); + + // Listen for XR session state changes to update button and UI visibility + useEffect(() => { + const handleXRStateChange = () => { + const xrState = store.getState(); + + if (xrState.mode === 'immersive-ar' || xrState.mode === 'immersive-vr') { + // XR session is active + setIsXRMode(true); + if (cloudXR2DUI) { + cloudXR2DUI.setStartButtonState(true, 'CONNECT (XR session active)'); + } + } else { + // XR session ended + setIsXRMode(false); + if (cloudXR2DUI) { + cloudXR2DUI.setStartButtonState(false, 'CONNECT'); + } + + if (xrState.error) { + setErrorMessage(`XR session error: ${xrState.error}`); + } + } + }; + + // Subscribe to XR state changes + const unsubscribe = store.subscribe(handleXRStateChange); + + // Cleanup + return () => { + unsubscribe(); + setIsXRMode(false); + }; + }, [cloudXR2DUI]); + + // CloudXR status change handler + const handleStatusChange = (connected: boolean, status: string) => { + setIsConnected(connected); + setSessionStatus(status); + }; + + // UI Event Handlers + const handleStartTeleop = () => { + console.log('Start Teleop pressed'); + + if (!cloudXRSession) { + console.error('CloudXR session not available'); + return; + } + + if (isCountingDown || isTeleopRunning) { + return; + } + + // Begin countdown before starting teleop (immediately if 0) + if (countdownDuration <= 0) { + setIsCountingDown(false); + setCountdownRemaining(0); + + try { + cloudXRSession.sendServerMessage(START_TELEOP_COMMAND); + console.log('Start teleop command sent'); + setIsTeleopRunning(true); + } catch (error) { + console.error('Failed to send teleop command:', error); + setIsTeleopRunning(false); + } + return; + } + + setIsCountingDown(true); + setCountdownRemaining(countdownDuration); + + countdownTimerRef.current = window.setInterval(() => { + setCountdownRemaining(prev => { + if (prev <= 1) { + // Countdown finished + if (countdownTimerRef.current !== null) { + clearInterval(countdownTimerRef.current); + countdownTimerRef.current = null; + } + setIsCountingDown(false); + + // Send start teleop command + + try { + cloudXRSession.sendServerMessage(START_TELEOP_COMMAND); + console.log('Start teleop command sent'); + setIsTeleopRunning(true); + } catch (error) { + console.error('Failed to send teleop command:', error); + setIsTeleopRunning(false); + } + + return 0; + } + return prev - 1; + }); + }, 1000); + }; + + const handleStopTeleop = () => { + console.log('Stop Teleop pressed'); + + // If countdown is active, cancel it and reset state + if (isCountingDown) { + if (countdownTimerRef.current !== null) { + clearInterval(countdownTimerRef.current); + countdownTimerRef.current = null; + } + setIsCountingDown(false); + setCountdownRemaining(0); + return; + } + + if (!cloudXRSession) { + console.error('CloudXR session not available'); + return; + } + + // Send stop teleop command + const teleopCommand = { + type: 'teleop_command', + message: { + command: 'stop teleop', + }, + }; + + try { + cloudXRSession.sendServerMessage(teleopCommand); + console.log('Stop teleop command sent'); + setIsTeleopRunning(false); + } catch (error) { + console.error('Failed to send teleop command:', error); + } + }; + + const handleResetTeleop = () => { + console.log('Reset Teleop pressed'); + + // Cancel any active countdown + if (countdownTimerRef.current !== null) { + clearInterval(countdownTimerRef.current); + countdownTimerRef.current = null; + } + setIsCountingDown(false); + setCountdownRemaining(0); + + if (!cloudXRSession) { + console.error('CloudXR session not available'); + return; + } + + // Send stop teleop command first + const stopCommand = { + type: 'teleop_command', + message: { + command: 'stop teleop', + }, + }; + + // Send reset teleop command + const resetCommand = { + type: 'teleop_command', + message: { + command: 'reset teleop', + }, + }; + + try { + cloudXRSession.sendServerMessage(stopCommand); + console.log('Stop teleop command sent'); + cloudXRSession.sendServerMessage(resetCommand); + console.log('Reset teleop command sent'); + setIsTeleopRunning(false); + } catch (error) { + console.error('Failed to send teleop commands:', error); + } + }; + + const handleDisconnect = () => { + console.log('Disconnect pressed'); + + // Cleanup countdown state on disconnect + if (countdownTimerRef.current !== null) { + clearInterval(countdownTimerRef.current); + countdownTimerRef.current = null; + } + setIsCountingDown(false); + setCountdownRemaining(0); + setIsTeleopRunning(false); + + const xrState = store.getState(); + const session = xrState.session; + if (session) { + session.end().catch((err: unknown) => { + setErrorMessage( + `Failed to end XR session: ${err instanceof Error ? err.message : String(err)}` + ); + }); + } + }; + + // Countdown configuration handlers (0-5 seconds) + const handleIncreaseCountdown = () => { + if (isCountingDown) return; + setCountdownDuration(prev => Math.min(COUNTDOWN_MAX_SECONDS, prev + 1)); + }; + + const handleDecreaseCountdown = () => { + if (isCountingDown) return; + setCountdownDuration(prev => Math.max(0, prev - 1)); + }; + + // Memo config based on configVersion (manual dependency tracker incremented on config changes) + // eslint-disable-next-line react-hooks/exhaustive-deps + const config = useMemo( + () => (cloudXR2DUI ? cloudXR2DUI.getConfiguration() : null), + [cloudXR2DUI, configVersion] + ); + + // Sync XR mode state to body class for CSS styling + useEffect(() => { + if (isXRMode) { + document.body.classList.add('xr-mode'); + } else { + document.body.classList.remove('xr-mode'); + } + + return () => { + document.body.classList.remove('xr-mode'); + }; + }, [isXRMode]); + + return ( + <> + { + e.preventDefault(); + }} + > + + + + + {cloudXR2DUI && config && ( + <> + { + if (cloudXR2DUI) { + cloudXR2DUI.showError(error); + } + }} + onSessionReady={setCloudXRSession} + onServerAddress={setServerAddress} + /> + + + )} + + + + ); +} + +export default App; diff --git a/deps/cloudxr/isaac/src/CloudXR2DUI.tsx b/deps/cloudxr/isaac/src/CloudXR2DUI.tsx new file mode 100644 index 0000000..d6b2848 --- /dev/null +++ b/deps/cloudxr/isaac/src/CloudXR2DUI.tsx @@ -0,0 +1,438 @@ +/** + * CloudXR2DUI.tsx - CloudXR 2D User Interface Management + * + * This class handles all the HTML form interactions, localStorage persistence, + * and form validation for the CloudXR React example. It follows the same pattern + * as the simple example's CloudXRWebUI class, providing a clean separation + * between UI management and React component logic. + * + * Features: + * - Form field management and localStorage persistence + * - Proxy configuration based on protocol + * - Form validation and default value handling + * - Event listener management + * - Error handling and logging + */ + +import { CloudXRConfig, enableLocalStorage, setupCertificateAcceptanceLink } from '@helpers/utils'; + +/** + * 2D UI Management for CloudXR React Example + * Handles the main user interface for CloudXR streaming, including form management, + * localStorage persistence, and user interaction controls. + */ +export class CloudXR2DUI { + /** Button to initiate XR streaming session */ + private startButton!: HTMLButtonElement; + /** Input field for the CloudXR server IP address */ + private serverIpInput!: HTMLInputElement; + /** Input field for the CloudXR server port number */ + private portInput!: HTMLInputElement; + /** Input field for proxy URL configuration */ + private proxyUrlInput!: HTMLInputElement; + /** Dropdown to select between AR and VR immersive modes */ + private immersiveSelect!: HTMLSelectElement; + /** Dropdown to select device frame rate (FPS) */ + private deviceFrameRateSelect!: HTMLSelectElement; + /** Dropdown to select max streaming bitrate (Mbps) */ + private maxStreamingBitrateMbpsSelect!: HTMLSelectElement; + /** Input field for per-eye width configuration */ + private perEyeWidthInput!: HTMLInputElement; + /** Input field for per-eye height configuration */ + private perEyeHeightInput!: HTMLInputElement; + /** Dropdown to select server backend type */ + private serverTypeSelect!: HTMLSelectElement; + /** Dropdown to select application type */ + private appSelect!: HTMLSelectElement; + /** Dropdown to select reference space for XR tracking */ + private referenceSpaceSelect!: HTMLSelectElement; + /** Input for XR reference space X offset (cm) */ + private xrOffsetXInput!: HTMLInputElement; + /** Input for XR reference space Y offset (cm) */ + private xrOffsetYInput!: HTMLInputElement; + /** Input for XR reference space Z offset (cm) */ + private xrOffsetZInput!: HTMLInputElement; + /** Text element displaying proxy configuration help */ + private proxyDefaultText!: HTMLElement; + /** Error message box element */ + private errorMessageBox!: HTMLElement; + /** Error message text element */ + private errorMessageText!: HTMLElement; + /** Certificate acceptance link container */ + private certAcceptanceLink!: HTMLElement; + /** Certificate acceptance link anchor */ + private certLink!: HTMLAnchorElement; + /** Flag to track if the 2D UI has been initialized */ + private initialized: boolean = false; + + /** Current form configuration state */ + private currentConfiguration: CloudXRConfig; + /** Callback function for configuration changes */ + private onConfigurationChange: ((config: CloudXRConfig) => void) | null = null; + /** Connect button click handler for cleanup */ + private handleConnectClick: ((event: Event) => void) | null = null; + /** Array to store all event listeners for proper cleanup */ + private eventListeners: Array<{ + element: HTMLElement; + event: string; + handler: EventListener; + }> = []; + /** Cleanup function for certificate acceptance link */ + private certLinkCleanup: (() => void) | null = null; + + /** + * Creates a new CloudXR2DUI instance + * @param onConfigurationChange - Callback function called when configuration changes + */ + constructor(onConfigurationChange?: (config: CloudXRConfig) => void) { + this.onConfigurationChange = onConfigurationChange || null; + this.currentConfiguration = this.getDefaultConfiguration(); + } + + /** + * Initializes the CloudXR2DUI with all necessary components and event handlers + */ + public initialize(): void { + if (this.initialized) { + return; + } + + try { + this.initializeElements(); + this.setupLocalStorage(); + this.setupProxyConfiguration(); + this.setupEventListeners(); + this.updateConfiguration(); + this.setStartButtonState(false, 'CONNECT'); + this.initialized = true; + } catch (error) { + // Continue with default values if initialization fails + this.showError(`Failed to initialize CloudXR2DUI: ${error}`); + } + } + + /** + * Initializes all DOM element references by their IDs + * Throws an error if any required element is not found + */ + private initializeElements(): void { + this.startButton = this.getElement('startButton'); + this.serverIpInput = this.getElement('serverIpInput'); + this.portInput = this.getElement('portInput'); + this.proxyUrlInput = this.getElement('proxyUrl'); + this.immersiveSelect = this.getElement('immersive'); + this.deviceFrameRateSelect = this.getElement('deviceFrameRate'); + this.maxStreamingBitrateMbpsSelect = + this.getElement('maxStreamingBitrateMbps'); + this.perEyeWidthInput = this.getElement('perEyeWidth'); + this.perEyeHeightInput = this.getElement('perEyeHeight'); + this.serverTypeSelect = this.getElement('serverType'); + this.appSelect = this.getElement('app'); + this.referenceSpaceSelect = this.getElement('referenceSpace'); + this.xrOffsetXInput = this.getElement('xrOffsetX'); + this.xrOffsetYInput = this.getElement('xrOffsetY'); + this.xrOffsetZInput = this.getElement('xrOffsetZ'); + this.proxyDefaultText = this.getElement('proxyDefaultText'); + this.errorMessageBox = this.getElement('errorMessageBox'); + this.errorMessageText = this.getElement('errorMessageText'); + this.certAcceptanceLink = this.getElement('certAcceptanceLink'); + this.certLink = this.getElement('certLink'); + } + + /** + * Gets a DOM element by ID with type safety + * @param id - The element ID to find + * @returns The found element with the specified type + * @throws Error if element is not found + */ + private getElement(id: string): T { + const element = document.getElementById(id) as T; + if (!element) { + throw new Error(`Element with id '${id}' not found`); + } + return element; + } + + /** + * Gets the default configuration values + * @returns Default configuration object + */ + private getDefaultConfiguration(): CloudXRConfig { + const useSecure = typeof window !== 'undefined' ? window.location.protocol === 'https:' : false; + // Default port: HTTP → 49100, HTTPS without proxy → 48322, HTTPS with proxy → 443 + const defaultPort = useSecure ? 48322 : 49100; + return { + serverIP: '127.0.0.1', + port: defaultPort, + useSecureConnection: useSecure, + perEyeWidth: 2048, + perEyeHeight: 1792, + deviceFrameRate: 90, + maxStreamingBitrateMbps: 150, + immersiveMode: 'ar', + app: 'generic', + serverType: 'manual', + proxyUrl: '', + referenceSpaceType: 'auto', + }; + } + + /** + * Enables localStorage persistence for form inputs + * Automatically saves and restores user preferences + */ + private setupLocalStorage(): void { + enableLocalStorage(this.serverTypeSelect, 'serverType'); + enableLocalStorage(this.serverIpInput, 'serverIp'); + enableLocalStorage(this.portInput, 'port'); + enableLocalStorage(this.perEyeWidthInput, 'perEyeWidth'); + enableLocalStorage(this.perEyeHeightInput, 'perEyeHeight'); + enableLocalStorage(this.proxyUrlInput, 'proxyUrl'); + enableLocalStorage(this.deviceFrameRateSelect, 'deviceFrameRate'); + enableLocalStorage(this.maxStreamingBitrateMbpsSelect, 'maxStreamingBitrateMbps'); + enableLocalStorage(this.immersiveSelect, 'immersiveMode'); + enableLocalStorage(this.appSelect, 'app'); + enableLocalStorage(this.referenceSpaceSelect, 'referenceSpace'); + enableLocalStorage(this.xrOffsetXInput, 'xrOffsetX'); + enableLocalStorage(this.xrOffsetYInput, 'xrOffsetY'); + enableLocalStorage(this.xrOffsetZInput, 'xrOffsetZ'); + } + + /** + * Configures proxy settings based on the current protocol (HTTP/HTTPS) + * Sets appropriate placeholders and help text for port and proxy URL inputs + */ + private setupProxyConfiguration(): void { + // Update port placeholder based on protocol + if (window.location.protocol === 'https:') { + this.portInput.placeholder = 'Port (default: 48322, or 443 if proxy URL set)'; + } else { + this.portInput.placeholder = 'Port (default: 49100)'; + } + + // Set default text and placeholder based on protocol + if (window.location.protocol === 'https:') { + this.proxyDefaultText.textContent = + 'Optional: Leave empty for direct WSS connection, or provide URL for proxy routing (e.g., https://proxy.example.com/)'; + this.proxyUrlInput.placeholder = ''; + } else { + this.proxyDefaultText.textContent = 'Not needed for HTTP - uses direct WS connection'; + this.proxyUrlInput.placeholder = ''; + } + } + + /** + * Sets up event listeners for form input changes + * Handles both input and change events for better compatibility + */ + private setupEventListeners(): void { + // Update configuration when form inputs change + const updateConfig = () => this.updateConfiguration(); + + // Helper function to add listeners and store them for cleanup + const addListener = (element: HTMLElement, event: string, handler: EventListener) => { + element.addEventListener(event, handler); + this.eventListeners.push({ element, event, handler }); + }; + + // Add event listeners for all form fields + addListener(this.serverTypeSelect, 'change', updateConfig); + addListener(this.serverIpInput, 'input', updateConfig); + addListener(this.serverIpInput, 'change', updateConfig); + addListener(this.portInput, 'input', updateConfig); + addListener(this.portInput, 'change', updateConfig); + addListener(this.perEyeWidthInput, 'input', updateConfig); + addListener(this.perEyeWidthInput, 'change', updateConfig); + addListener(this.perEyeHeightInput, 'input', updateConfig); + addListener(this.perEyeHeightInput, 'change', updateConfig); + addListener(this.deviceFrameRateSelect, 'change', updateConfig); + addListener(this.maxStreamingBitrateMbpsSelect, 'change', updateConfig); + addListener(this.immersiveSelect, 'change', updateConfig); + addListener(this.appSelect, 'change', updateConfig); + addListener(this.referenceSpaceSelect, 'change', updateConfig); + addListener(this.xrOffsetXInput, 'input', updateConfig); + addListener(this.xrOffsetXInput, 'change', updateConfig); + addListener(this.xrOffsetYInput, 'input', updateConfig); + addListener(this.xrOffsetYInput, 'change', updateConfig); + addListener(this.xrOffsetZInput, 'input', updateConfig); + addListener(this.xrOffsetZInput, 'change', updateConfig); + addListener(this.proxyUrlInput, 'input', updateConfig); + addListener(this.proxyUrlInput, 'change', updateConfig); + + // Set up certificate acceptance link and store cleanup function + this.certLinkCleanup = setupCertificateAcceptanceLink( + this.serverIpInput, + this.portInput, + this.proxyUrlInput, + this.certAcceptanceLink, + this.certLink + ); + } + + /** + * Updates the current configuration from form values + * Calls the configuration change callback if provided + */ + private updateConfiguration(): void { + const useSecure = this.getDefaultConfiguration().useSecureConnection; + const portValue = parseInt(this.portInput.value); + const hasProxy = this.proxyUrlInput.value.trim().length > 0; + + // Smart default port based on connection type and proxy usage + let defaultPort = 49100; // HTTP default + if (useSecure) { + defaultPort = hasProxy ? 443 : 48322; // HTTPS with proxy → 443, HTTPS without → 48322 + } + + const newConfiguration: CloudXRConfig = { + serverIP: this.serverIpInput.value || this.getDefaultConfiguration().serverIP, + port: portValue || defaultPort, + useSecureConnection: useSecure, + perEyeWidth: + parseInt(this.perEyeWidthInput.value) || this.getDefaultConfiguration().perEyeWidth, + perEyeHeight: + parseInt(this.perEyeHeightInput.value) || this.getDefaultConfiguration().perEyeHeight, + deviceFrameRate: + parseInt(this.deviceFrameRateSelect.value) || + this.getDefaultConfiguration().deviceFrameRate, + maxStreamingBitrateMbps: + parseInt(this.maxStreamingBitrateMbpsSelect.value) || + this.getDefaultConfiguration().maxStreamingBitrateMbps, + immersiveMode: + (this.immersiveSelect.value as 'ar' | 'vr') || this.getDefaultConfiguration().immersiveMode, + app: this.appSelect.value || this.getDefaultConfiguration().app, + serverType: this.serverTypeSelect.value || this.getDefaultConfiguration().serverType, + proxyUrl: this.proxyUrlInput.value || this.getDefaultConfiguration().proxyUrl, + referenceSpaceType: + (this.referenceSpaceSelect.value as 'auto' | 'local-floor' | 'local' | 'viewer') || + this.getDefaultConfiguration().referenceSpaceType, + // Convert cm from UI into meters for config (respect 0; if invalid, use 0) + xrOffsetX: (() => { + const v = parseFloat(this.xrOffsetXInput.value); + return Number.isFinite(v) ? v / 100 : 0; + })(), + xrOffsetY: (() => { + const v = parseFloat(this.xrOffsetYInput.value); + return Number.isFinite(v) ? v / 100 : 0; + })(), + xrOffsetZ: (() => { + const v = parseFloat(this.xrOffsetZInput.value); + return Number.isFinite(v) ? v / 100 : 0; + })(), + }; + + this.currentConfiguration = newConfiguration; + + // Call the configuration change callback if provided + if (this.onConfigurationChange) { + this.onConfigurationChange(newConfiguration); + } + } + + /** + * Gets the current configuration + * @returns Current configuration object + */ + public getConfiguration(): CloudXRConfig { + return { ...this.currentConfiguration }; + } + + /** + * Sets the start button state + * @param disabled - Whether the button should be disabled + * @param text - Text to display on the button + */ + public setStartButtonState(disabled: boolean, text: string): void { + if (this.startButton) { + this.startButton.disabled = disabled; + this.startButton.innerHTML = text; + } + } + + /** + * Sets up the connect button click handler + * @param onConnect - Function to call when connect button is clicked + * @param onError - Function to call when an error occurs + */ + public setupConnectButtonHandler( + onConnect: () => Promise, + onError: (error: Error) => void + ): void { + if (this.startButton) { + // Remove any existing listener + if (this.handleConnectClick) { + this.startButton.removeEventListener('click', this.handleConnectClick); + } + + // Create new handler + this.handleConnectClick = async () => { + // Disable button during XR session + this.setStartButtonState(true, 'CONNECT (starting XR session...)'); + + try { + await onConnect(); + } catch (error) { + this.setStartButtonState(false, 'CONNECT'); + onError(error as Error); + } + }; + + // Add the new listener + this.startButton.addEventListener('click', this.handleConnectClick); + } + } + + /** + * Shows a status message in the UI with a specific type + * @param message - Message to display + * @param type - Message type: 'success', 'error', or 'info' + */ + public showStatus(message: string, type: 'success' | 'error' | 'info'): void { + if (this.errorMessageText && this.errorMessageBox) { + this.errorMessageText.textContent = message; + this.errorMessageBox.className = `error-message-box show ${type}`; + } + console[type === 'error' ? 'error' : 'info'](message); + } + + /** + * Shows an error message in the UI + * @param message - Error message to display + */ + public showError(message: string): void { + this.showStatus(message, 'error'); + } + + /** + * Hides the error message + */ + public hideError(): void { + if (this.errorMessageBox) { + this.errorMessageBox.classList.remove('show'); + } + } + + /** + * Cleans up event listeners and resources + * Should be called when the component unmounts + */ + public cleanup(): void { + // Remove all stored event listeners + this.eventListeners.forEach(({ element, event, handler }) => { + element.removeEventListener(event, handler); + }); + this.eventListeners = []; + + // Remove CONNECT button listener + if (this.startButton && this.handleConnectClick) { + this.startButton.removeEventListener('click', this.handleConnectClick); + this.handleConnectClick = null; + } + + // Clean up certificate acceptance link listeners + if (this.certLinkCleanup) { + this.certLinkCleanup(); + this.certLinkCleanup = null; + } + } +} diff --git a/deps/cloudxr/isaac/src/CloudXRComponent.tsx b/deps/cloudxr/isaac/src/CloudXRComponent.tsx new file mode 100644 index 0000000..dafa35a --- /dev/null +++ b/deps/cloudxr/isaac/src/CloudXRComponent.tsx @@ -0,0 +1,289 @@ +/** + * CloudXRComponent.tsx - CloudXR WebXR Integration Component + * + * This component handles the core CloudXR streaming functionality and WebXR integration. + * It manages: + * - CloudXR session lifecycle (creation, connection, disconnection, cleanup) + * - WebXR session event handling (sessionstart, sessionend) + * - WebGL state management and render target preservation + * - Frame-by-frame rendering loop with pose tracking and stream rendering + * - Server configuration and connection parameters + * - Status reporting back to parent components + * + * The component accepts configuration via props and communicates status changes + * and disconnect requests through callback props. It integrates with Three.js + * and React Three Fiber for WebXR rendering while preserving WebGL state + * for CloudXR's custom rendering pipeline. + */ + +import { getConnectionConfig, ConnectionConfiguration, CloudXRConfig } from '@helpers/utils'; +import { bindGL } from '@helpers/WebGLStateBinding'; +import * as CloudXR from '@nvidia/cloudxr'; +import { useThree, useFrame } from '@react-three/fiber'; +import { useXR } from '@react-three/xr'; +import { useRef, useEffect } from 'react'; +import type { WebGLRenderer } from 'three'; + +interface CloudXRComponentProps { + config: CloudXRConfig; + onStatusChange?: (isConnected: boolean, status: string) => void; + onError?: (error: string) => void; + onSessionReady?: (session: CloudXR.Session | null) => void; + onServerAddress?: (address: string) => void; +} + +// React component that integrates CloudXR with Three.js/WebXR +// This component handles the CloudXR session lifecycle and render loop +export default function CloudXRComponent({ + config, + onStatusChange, + onError, + onSessionReady, + onServerAddress, +}: CloudXRComponentProps) { + const threeRenderer: WebGLRenderer = useThree().gl; + const { session } = useXR(); + // React reference to the CloudXR session that persists across re-renders. + const cxrSessionRef = useRef(null); + + // Disable Three.js so it doesn't clear the framebuffer after CloudXR renders. + threeRenderer.autoClear = false; + + // Access Three.js WebXRManager and WebGL context. + const gl: WebGL2RenderingContext = threeRenderer.getContext() as WebGL2RenderingContext; + + const trackedGL = bindGL(gl); + + // Set up event listeners in useEffect to add them only once + useEffect(() => { + const webXRManager = threeRenderer.xr; + + if (webXRManager) { + const handleSessionStart = async () => { + // Explicitly request the desired reference space from the XRSession to avoid + // inheriting a default 'local-floor' space that could stack with UI offsets. + let referenceSpace: XRReferenceSpace | null = null; + try { + const xrSession: XRSession | null = (webXRManager as any).getSession + ? (webXRManager as any).getSession() + : null; + if (xrSession) { + if (config.referenceSpaceType === 'auto') { + const fallbacks: XRReferenceSpaceType[] = [ + 'local-floor', + 'local', + 'viewer', + 'unbounded', + ]; + for (const t of fallbacks) { + try { + referenceSpace = await xrSession.requestReferenceSpace(t); + if (referenceSpace) break; + } catch (_) {} + } + } else { + try { + referenceSpace = await xrSession.requestReferenceSpace( + config.referenceSpaceType as XRReferenceSpaceType + ); + } catch (error) { + console.error( + `Failed to request reference space '${config.referenceSpaceType}':`, + error + ); + } + } + } + } catch (error) { + console.error('Failed to request XR reference space:', error); + referenceSpace = null; + } + + if (!referenceSpace) { + // As a last resort, fall back to WebXRManager's current reference space + referenceSpace = webXRManager.getReferenceSpace(); + } + + if (referenceSpace) { + // Ensure that the session is not already created. + if (cxrSessionRef.current) { + console.error('CloudXR session already exists'); + return; + } + + const glBinding = webXRManager.getBinding(); + if (!glBinding) { + console.warn('No WebGL binding found'); + } + + // Apply proxy configuration logic + let connectionConfig: ConnectionConfiguration; + try { + connectionConfig = getConnectionConfig(config.serverIP, config.port, config.proxyUrl); + onServerAddress?.(connectionConfig.serverIP); + } catch (error) { + onStatusChange?.(false, 'Configuration Error'); + onError?.(`Proxy configuration failed: ${error}`); + return; + } + + // Apply XR offset if provided in config (meters) + const offsetX = config.xrOffsetX || 0; + const offsetY = config.xrOffsetY || 0; + const offsetZ = config.xrOffsetZ || 0; + if (offsetX !== 0 || offsetY !== 0 || offsetZ !== 0) { + const offsetTransform = new XRRigidTransform( + { x: offsetX, y: offsetY, z: offsetZ }, + { x: 0, y: 0, z: 0, w: 1 } + ); + referenceSpace = referenceSpace.getOffsetReferenceSpace(offsetTransform); + } + + // Fill in CloudXR session options. + const cloudXROptions: CloudXR.SessionOptions = { + serverAddress: connectionConfig.serverIP, + serverPort: connectionConfig.port, + useSecureConnection: connectionConfig.useSecureConnection, + perEyeWidth: config.perEyeWidth, + perEyeHeight: config.perEyeHeight, + gl: gl, + referenceSpace: referenceSpace, + deviceFrameRate: config.deviceFrameRate, + maxStreamingBitrateKbps: config.maxStreamingBitrateMbps * 1000, // Convert Mbps to Kbps + glBinding: glBinding, + telemetry: { + enabled: true, + appInfo: { + version: '6.0.0-beta', + product: 'CloudXR.js React Example', + }, + }, + }; + + // Store the render target and key GL bindings to restore after CloudXR rendering + const cloudXRDelegates: CloudXR.SessionDelegates = { + onWebGLStateChangeBegin: () => { + // Save the current render target before CloudXR changes state + trackedGL.save(); + }, + onWebGLStateChangeEnd: () => { + // Restore the tracked GL state to the state before CloudXR rendering. + trackedGL.restore(); + }, + onStreamStarted: () => { + console.debug('CloudXR stream started'); + onStatusChange?.(true, 'Connected'); + }, + onStreamStopped: (error?: Error) => { + if (error) { + onStatusChange?.(false, 'Error'); + onError?.(`CloudXR session stopped with error: ${error.message}`); + } else { + console.debug('CloudXR session stopped'); + onStatusChange?.(false, 'Disconnected'); + } + // Clear the session reference + cxrSessionRef.current = null; + onSessionReady?.(null); + }, + }; + + // Create the CloudXR session. + let cxrSession: CloudXR.Session; + try { + cxrSession = CloudXR.createSession(cloudXROptions, cloudXRDelegates); + } catch (error) { + onStatusChange?.(false, 'Session Creation Failed'); + onError?.(`Failed to create CloudXR session: ${error}`); + return; + } + + // Store the session in the ref so it persists across re-renders + cxrSessionRef.current = cxrSession; + + // Notify parent that session is ready + onSessionReady?.(cxrSession); + + // Start session (synchronous call that initiates connection) + try { + cxrSession.connect(); + console.log('CloudXR session connect initiated'); + // Note: The session will transition to Connected state via the onStreamStarted callback + // Use cxrSession.state to check if streaming has actually started + } catch (error) { + onStatusChange?.(false, 'Connection Failed'); + // Report error via callback + onError?.('Failed to connect CloudXR session'); + // Clean up the failed session + cxrSessionRef.current = null; + } + } + }; + + const handleSessionEnd = () => { + if (cxrSessionRef.current) { + cxrSessionRef.current.disconnect(); + cxrSessionRef.current = null; + onSessionReady?.(null); + } + }; + + // Add start+end session event listeners to the WebXRManager. + webXRManager.addEventListener('sessionstart', handleSessionStart); + webXRManager.addEventListener('sessionend', handleSessionEnd); + + // Cleanup function to remove listeners + return () => { + webXRManager.removeEventListener('sessionstart', handleSessionStart); + webXRManager.removeEventListener('sessionend', handleSessionEnd); + }; + } + }, [threeRenderer, config]); // Re-register handlers when renderer or config changes + + // Custom render loop - runs every frame + useFrame((state, delta) => { + const webXRManager = threeRenderer.xr; + + if (webXRManager.isPresenting && session) { + // Access the current WebXR XRFrame + const xrFrame = state.gl.xr.getFrame(); + if (xrFrame) { + // Get THREE WebXRManager from the the useFrame state. + const webXRManager = state.gl.xr; + + if (!cxrSessionRef || !cxrSessionRef.current) { + console.debug('Skipping frame, no session yet'); + // Clear the framebuffer as we've set autoClear to false. + threeRenderer.clear(); + return; + } + + // Get session from reference. + const cxrSession: CloudXR.Session = cxrSessionRef.current; + + // If the CloudXR session is not connected, skip the frame. + if (cxrSession.state !== CloudXR.SessionState.Connected) { + console.debug('Skipping frame, session not connected, state:', cxrSession.state); + // Clear the framebuffer as we've set autoClear to false. + threeRenderer.clear(); + return; + } + + // Get timestamp from useFrame state and convert to milliseconds. + const timestamp: DOMHighResTimeStamp = state.clock.elapsedTime * 1000; + + // Send the tracking state (including viewer pose and hand/controller data) to the server, this will trigger server-side rendering for frame. + cxrSession.sendTrackingStateToServer(timestamp, xrFrame); + + // Get the WebXR layer from THREE WebXRManager. + let layer: XRWebGLLayer = webXRManager.getBaseLayer() as XRWebGLLayer; + + // Render the current streamed CloudXR frame (not the frame that was just sent to the server). + cxrSession.render(timestamp, xrFrame, layer); + } + } + }, -1000); + + return null; +} + diff --git a/deps/cloudxr/isaac/src/CloudXRUI.tsx b/deps/cloudxr/isaac/src/CloudXRUI.tsx new file mode 100644 index 0000000..c753072 --- /dev/null +++ b/deps/cloudxr/isaac/src/CloudXRUI.tsx @@ -0,0 +1,218 @@ +/** + * CloudXRUI.tsx - CloudXR User Interface Component + * + * This component renders the in-VR user interface for the CloudXR application using + * React Three UIKit. It provides: + * - CloudXR branding and title display + * - Server connection information and status display + * - Interactive control buttons (Start Teleop, Reset Teleop, Disconnect) + * - Responsive button layout with hover effects + * - Integration with parent component event handlers + * - Configurable position and rotation in world space for flexible UI placement + * + * The UI is positioned in 3D space and designed for VR/AR interaction with + * visual feedback and clear button labeling. All interactions are passed + * back to the parent component through callback props. + */ + +import { Container, Text, Image } from '@react-three/uikit'; +import { Button } from '@react-three/uikit-default'; +import React from 'react'; + +interface CloudXRUIProps { + onStartTeleop?: () => void; + onDisconnect?: () => void; + onResetTeleop?: () => void; + serverAddress?: string; + sessionStatus?: string; + playLabel?: string; + playDisabled?: boolean; + countdownSeconds?: number; + onCountdownIncrease?: () => void; + onCountdownDecrease?: () => void; + countdownDisabled?: boolean; + position?: [number, number, number]; + rotation?: [number, number, number]; +} + +export default function CloudXR3DUI({ + onStartTeleop, + onDisconnect, + onResetTeleop, + serverAddress = '127.0.0.1', + sessionStatus = 'Disconnected', + playLabel = 'Play', + playDisabled = false, + countdownSeconds, + onCountdownIncrease, + onCountdownDecrease, + countdownDisabled = false, + position = [1.8, 1.75, -1.3], + rotation = [0, -0.3, 0], +}: CloudXRUIProps) { + return ( + + + + {/* Title */} + + Controls + + + {/* Server Info */} + + Server address: {serverAddress} + + + Session status: {sessionStatus} + + + {/* Countdown Config Row */} + + + Countdown + + + + + {countdownSeconds}s + + + + + + {/* Button Grid */} + + {/* Start/reset row*/} + + + + + + + {/* Bottom Row */} + + + + + + + + ); +} diff --git a/deps/cloudxr/isaac/src/index.html b/deps/cloudxr/isaac/src/index.html new file mode 100644 index 0000000..7e943cf --- /dev/null +++ b/deps/cloudxr/isaac/src/index.html @@ -0,0 +1,576 @@ + + + + + + + + + NVIDIA CloudXR.js Isaac Lab Teleop application + + + + + +
+
+
+

NVIDIA CloudXR.js Isaac Lab Teleop Client

+
+ +
+ + +
+

Debug Settings

+ +
+ + + +
+ +
+
+ +
+ + + +
+ Select the target device frame rate for the XR session +
+
+ +
+ + + +
+ Select the maximum streaming bitrate (in Megabits per second) for the XR session +
+
+ +
+ + + + + +
+ Configure the per-eye resolution. Width and height must be multiples of 16. +
+
+ +
+ + +
+ Select the preferred reference space for XR tracking. "Auto" uses fallback logic (local-floor → local → viewer). Other options will attempt to use the specified space only. +
+ + + + + + + +
+ Configure the XR reference space offset in centimeters. These values will be applied to the reference space transform to adjust the origin position of the XR experience. +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/deps/cloudxr/isaac/src/index.tsx b/deps/cloudxr/isaac/src/index.tsx new file mode 100644 index 0000000..48169f4 --- /dev/null +++ b/deps/cloudxr/isaac/src/index.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; + +import App from './App'; + +// Start the React app immediately in the 3d-ui container +function startApp() { + const reactContainer = document.getElementById('3d-ui'); + + if (reactContainer) { + const root = ReactDOM.createRoot(reactContainer); + root.render( + + + + ); + } else { + console.error('3d-ui container not found'); + } +} + +// Initialize the app when DOM is ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', startApp); +} else { + startApp(); +} diff --git a/deps/cloudxr/isaac/tsconfig.json b/deps/cloudxr/isaac/tsconfig.json new file mode 100644 index 0000000..486f69a --- /dev/null +++ b/deps/cloudxr/isaac/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "CommonJS", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": false, + "outDir": "./dist", + "incremental": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noFallthroughCasesInSwitch": true, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@helpers/*": ["../helpers/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/deps/cloudxr/isaac/webpack-plugins/DownloadAssetsPlugin.js b/deps/cloudxr/isaac/webpack-plugins/DownloadAssetsPlugin.js new file mode 100644 index 0000000..9433fcf --- /dev/null +++ b/deps/cloudxr/isaac/webpack-plugins/DownloadAssetsPlugin.js @@ -0,0 +1,109 @@ +const fs = require('fs'); +const path = require('path'); +const https = require('https'); + +class DownloadAssetsPlugin { + constructor(assets) { + this.assets = assets; + this.hasRun = false; + this.createdDirs = new Set(); + } + + safeUnlink(filePath) { + try { + fs.unlinkSync(filePath); + } catch (err) { + // Ignore cleanup errors + } + } + + apply(compiler) { + compiler.hooks.beforeCompile.tapAsync('DownloadAssetsPlugin', (params, callback) => { + // Only run once per webpack process + if (this.hasRun) { + callback(); + return; + } + + console.log('📦 Checking and downloading required assets...'); + + const downloadPromises = this.assets.map(asset => this.downloadFile(asset)); + + Promise.allSettled(downloadPromises) + .then((results) => { + const failed = results.filter(r => r.status === 'rejected'); + if (failed.length > 0) { + console.warn(`⚠️ ${failed.length} asset(s) failed to download, continuing anyway...`); + } + console.log('✅ Asset check complete!'); + this.hasRun = true; + callback(); + }); + }); + } + + downloadFile({ url, output }) { + return new Promise((resolve, reject) => { + // Ensure directory exists (only once per unique path) + if (!this.createdDirs.has(output)) { + fs.mkdirSync(output, { recursive: true }); + this.createdDirs.add(output); + } + + const filename = path.basename(url); + const filePath = path.join(output, filename); + + // Skip if file already exists + if (fs.existsSync(filePath)) { + resolve(); + return; + } + + console.log(` Downloading ${filename}...`); + + const file = fs.createWriteStream(filePath); + + const downloadFromUrl = (downloadUrl) => { + https.get(downloadUrl, (response) => { + // Handle redirects + if (response.statusCode === 302 || response.statusCode === 301) { + file.close(); + this.safeUnlink(filePath); + downloadFromUrl(response.headers.location); + return; + } + + if (response.statusCode !== 200) { + file.close(); + this.safeUnlink(filePath); + reject(new Error(`Failed to download ${filename}: HTTP ${response.statusCode}`)); + return; + } + + response.pipe(file); + + file.on('finish', () => { + file.close(); + console.log(` ✓ Downloaded ${filename}`); + resolve(); + }); + + file.on('error', (err) => { + this.safeUnlink(filePath); + reject(err); + }); + }).on('error', (err) => { + if (fs.existsSync(filePath)) { + this.safeUnlink(filePath); + } + reject(err); + }); + }; + + downloadFromUrl(url); + }); + } +} + +module.exports = DownloadAssetsPlugin; + diff --git a/deps/cloudxr/isaac/webpack.common.js b/deps/cloudxr/isaac/webpack.common.js new file mode 100644 index 0000000..4304484 --- /dev/null +++ b/deps/cloudxr/isaac/webpack.common.js @@ -0,0 +1,168 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const DownloadAssetsPlugin = require('./webpack-plugins/DownloadAssetsPlugin'); + +const WEBXR_ASSETS_VERSION = '1.0.19'; + +module.exports = { + entry: './src/index.tsx', + + // Module rules define how different file types are processed + module: { + rules: [ + { + test: /\.tsx?$/, + use: { + loader: 'ts-loader', + options: { + // Only transpile, don't type-check (faster builds) + transpileOnly: true, + }, + }, + exclude: /node_modules/, + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'], + }, + ], + }, + + // Resolve configuration for module resolution + resolve: { + extensions: ['.tsx', '.ts', '.js'], + alias: { + // @helpers can be used instead of relative paths to the helpers directory + '@helpers': path.resolve(__dirname, '../helpers') + } + }, + + // Output configuration for bundled files + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, './build'), + }, + + // Webpack plugins that extend webpack's functionality + plugins: [ + // Generates HTML file and automatically injects bundled JavaScript + new HtmlWebpackPlugin({ + template: './src/index.html', + favicon: './favicon.ico' + }), + + // Inject environment variables + new webpack.DefinePlugin({ + 'process.env.WEBXR_ASSETS_VERSION': JSON.stringify(WEBXR_ASSETS_VERSION), + }), + + // Download external assets during build + new DownloadAssetsPlugin([ + // HDRI environment map + { + url: 'https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/potsdamer_platz_1k.hdr', + output: path.resolve(__dirname, 'public/assets/hdri') + }, + // WebXR controller profiles + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/profilesList.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles`) + }, + // Generic hand profile + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-hand/profile.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-hand`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-hand/left.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-hand`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-hand/right.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-hand`) + }, + // Generic trigger profile + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-trigger/profile.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-trigger`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-trigger/left.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-trigger`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-trigger/right.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-trigger`) + }, + // Oculus Touch v2 + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v2/profile.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v2`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v2/left.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v2`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v2/right.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v2`) + }, + // Oculus Touch v3 + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v3/profile.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v3`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v3/left.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v3`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v3/right.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v3`) + }, + // Meta Quest Touch Plus + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/meta-quest-touch-plus/profile.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/meta-quest-touch-plus`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/meta-quest-touch-plus/left.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/meta-quest-touch-plus`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/meta-quest-touch-plus/right.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/meta-quest-touch-plus`) + }, + // Pico 4 Ultra + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/pico-4u/profile.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/pico-4u`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/pico-4u/left.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/pico-4u`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/pico-4u/right.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/pico-4u`) + }, + ]), + + // Copies static assets from public directory to build output + new CopyWebpackPlugin({ + patterns: [ + { + from: 'public', + to: '.', + globOptions: { + // Don't copy index.html since HtmlWebpackPlugin handles it + ignore: ['**/index.html'], + }, + }, + { from: './favicon.ico', to: 'favicon.ico' }, + ], + }), + ], +}; diff --git a/deps/cloudxr/isaac/webpack.dev.js b/deps/cloudxr/isaac/webpack.dev.js new file mode 100644 index 0000000..f14ac89 --- /dev/null +++ b/deps/cloudxr/isaac/webpack.dev.js @@ -0,0 +1,46 @@ +const { merge } = require('webpack-merge') +const common = require('./webpack.common.js') +const path = require('path') + +// Check if HTTPS mode is enabled via environment variable +const useHttps = process.env.HTTPS === 'true' + +module.exports = merge(common, { + mode: 'development', + devtool: 'eval-source-map', + watchOptions: { + ignored: /node_modules/, + }, + devServer: { + allowedHosts: 'all', + hot: false, + liveReload: false, + open: false, + // Enable HTTPS with self-signed certificate when HTTPS=true + ...(useHttps && { server: 'https' }), + static: [ + { + directory: path.join(__dirname, './build'), + watch: false, + }, + { + directory: path.join(__dirname, './public'), + publicPath: '/', + watch: false, + }, + ], + watchFiles: [], + client: { + progress: true, + overlay: { + errors: true, + warnings: false, + }, + }, + devMiddleware: { + writeToDisk: true, + }, + compress: true, + port: 8080, + }, +}) diff --git a/deps/cloudxr/isaac/webpack.prod.js b/deps/cloudxr/isaac/webpack.prod.js new file mode 100644 index 0000000..4f7d055 --- /dev/null +++ b/deps/cloudxr/isaac/webpack.prod.js @@ -0,0 +1,6 @@ +const { merge } = require('webpack-merge'); +const common = require('./webpack.common.js'); + +module.exports = merge(common, { + mode: 'production' +}); diff --git a/deps/cloudxr/nvidia-cloudxr-6.0.0-beta.tgz b/deps/cloudxr/nvidia-cloudxr-6.0.0-beta.tgz new file mode 100644 index 0000000..503f2c0 Binary files /dev/null and b/deps/cloudxr/nvidia-cloudxr-6.0.0-beta.tgz differ diff --git a/deps/cloudxr/react/LICENSE b/deps/cloudxr/react/LICENSE new file mode 100644 index 0000000..5e53852 --- /dev/null +++ b/deps/cloudxr/react/LICENSE @@ -0,0 +1,307 @@ +NVIDIA SOFTWARE EVALUATION LICENSE AGREEMENT + +IMPORTANT NOTICE – PLEASE READ AND AGREE BEFORE USING THE SOFTWARE + +This software evaluation license agreement (“Agreement”) is a legal agreement between you, whether an +individual or entity, (“you”) and NVIDIA Corporation and its affiliates (“NVIDIA”) and governs the use of certain +NVIDIA CloudXR software and documentation that NVIDIA delivers to you under this Agreement (“Software”). +NVIDIA and you are each a “party” and collectively the “parties.” +This Agreement can be accepted only by an adult of legal age of majority in the country in which the Software is +used. If you don’t have the required age or authority to accept this Agreement, or if you don’t accept all the +terms and conditions of this Agreement, do not use the Software. + +1. License Grants. + +1.1 License Grant to You. The Software is licensed, not sold. Subject to the terms of this Agreement, +NVIDIA grants you a limited, non-exclusive, revocable, non-transferable, non-sublicensable (except +as expressly granted in this Agreement), license to: +(a) access, install and use copies of the Software, +(b) configure the Software using configuration files provided (if applicable), +(c) modify and create derivative works of any source code NVIDIA delivers to you as part of the +Software (“Derivatives”) (if applicable). + +All the foregoing grants are only for internal test and evaluation purposes and, as applicable, for use (a) in +client systems, or (b) in server systems with NVIDIA GPUs (“Purpose”). + +1.2 License Grant to NVIDIA. Subject to the terms of this Agreement, you grant NVIDIA a non-exclusive, +perpetual, irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, +under your intellectual property rights, to publicly perform, publicly display, reproduce, use, make, +have made, sell, offer for sale, distribute (through multiple tiers of distribution), import, create +derivative works of and otherwise commercialize and exploit at NVIDIA’s discretion any Derivatives +created by or for you. You may, but are not required to, deliver any Derivatives to NVIDIA. + +2. License Restrictions. Your license to use the Software and Derivatives is restricted as stated in this “License +Restrictions” Section. You will cooperate with NVIDIA and, upon NVIDIA’s written request, you will confirm +in writing and provide reasonably requested information to verify your compliance with the terms of this +Agreement. You may not: + +2.1 Use the Software or Derivatives for any purpose other than the Purpose, including but not limited to +in production; + +2.2 Sell, rent, sublicense, transfer, distribute or otherwise make available to others (except Authorized +Users as stated in the “Authorized Users” Section) any portion of the Software or Derivatives, except +as expressly granted in Section 1.1 (“License Grant to You”); + +2.3 Reverse engineer, decompile, or disassemble the Software components provided in binary form, nor +attempt in any other manner to obtain source code of such Software; + +2.4 Modify or create derivative works of the Software, except as expressly granted in Section 1.1 +(“License Grant to You”); + +2.5 Change or remove copyright or other proprietary notices in the Software; + +2.6 Bypass, disable, or circumvent any technical limitation, encryption, security, digital rights +management or authentication mechanism in the Software; + +2.7 Use the Software or Derivatives in any manner that would cause them to become subject to an open +source software license; subject to the terms in Section 7 (“Components Under Other Licenses”); + +2.8 Use the Software or Derivatives for the purpose of developing competing products or technologies +or assist a third party in such activities; + +2.9 Replace any Software components governed by this Agreement with other software that +implements NVIDIA APIs; + +2.10 Use the Software or Derivatives in violation of any applicable law or regulation in the relevant +jurisdictions; or + +2.11 Use the Software in or with any system or application where the use or failure of such system or +application developed or deployed with Software could result in injury, death or catastrophic +damage (“Mission Critical Applications”). NVIDIA will not be liable to you or any third party, in whole +or in part, for any claims or damages arising from uses in Mission Critical Applications. + +2.12 Disclose any evaluation or test results regarding the Software or Derivatives without NVIDIA’s prior +written consent. + +3. Authorized Users. You may allow employees and contractors of your entity or of your subsidiary(ies), and +for educational institutions also enrolled students, to internally access and use the Software as authorized +by this Agreement from your secure network to perform the work authorized by this Agreement on your +behalf. You are responsible for the compliance with the terms of this Agreement by your authorized users. +Any act or omission that if committed by you would constitute a breach of this Agreement will be deemed to +constitute a breach of this Agreement if committed by your authorized users. + +4. Pre-Release. Software versions identified as alpha, beta, preview, early access or otherwise as pre-release +may not be fully functional, may contain errors or design flaws, and may have reduced or different security, +privacy, availability and reliability standards relative to NVIDIA commercial offerings. You use pre-release +Software at your own risk. NVIDIA did not design or test the Software for use in production or business +critical systems. NVIDIA may choose not to make available a commercial version of pre-release Software. +NVIDIA may also choose to abandon development and terminate the availability of pre-release Software at +any time without liability. + +5. Your Privacy: Collection and Use of Information. + +5.1 Privacy Policy. Please review the NVIDIA Privacy Policy, located at https://www.nvidia.com/enus/about-nvidia/privacy-policy, which explains NVIDIA’s policy for collecting and using data, as well +as visit the NVIDIA Privacy Center, located at https://www.nvidia.com/en-us/privacy-center, to +manage your consent and privacy preferences. + +5.2 Collection Purposes. You also acknowledge that the Software collects data for the following +purposes: (a) properly configure and optimize products for use with Software; and (b) improve +NVIDIA products and services. Information collected by the Software includes: (i) application +configuration; (ii) browser version; (iii) and session metadata (i.e. performance and usage +statistics). Additionally, NVIDIA may collect certain personal information, such as your name +and email address or those of your authorized users, and other information necessary to +authenticate and enable you or your authorized users’ access to the Software. Where appropriate +you will disclose to, and obtain any necessary consent from, your authorized users to allow NVIDIA +to collect such information. + +5.3 Third Party Privacy Practices. The Software may contain links to third party websites and services. +NVIDIA encourages you to review the privacy statements on those sites and services that you choose +to visit to understand how they may collect, use and share your data. NVIDIA is not responsible for +the privacy statements or practices of third-party sites or services. + +6. Updates. NVIDIA may at any time and at its option, change, discontinue, or deprecate any part, or all, of the +Software, or change or remove features or functionality, or make available patches, workarounds or other +updates to the Software. Unless the updates are provided with their separate governing terms, they are +deemed part of the Software licensed to you under this Agreement, and your continued use of the Software +is deemed acceptance of such changes. + +7. Components Under Other Licenses. The Software may include or be distributed with components provided +with separate legal notices or terms that accompany the components, such as open source software licenses +and other license terms (“Other Licenses”). The components are subject to the applicable Other Licenses, +including any proprietary notices, disclaimers, requirements and extended use rights; except that this +Agreement will prevail regarding the use of third-party open source software, unless a third-party open +source software license requires its license terms to prevail. Open source software license means any +software, data or documentation subject to any license identified as an open source license by the Open +Source Initiative (http://opensource.org), Free Software Foundation (http://www.fsf.org) or other similar +open source organization or listed by the Software Package Data Exchange (SPDX) Workgroup under the +Linux Foundation (http://www.spdx.org). + +8. Ownership. + +8.1 NVIDIA Ownership. The Software, including all intellectual property rights, is and will remain the sole +and exclusive property of NVIDIA or its licensors. Except as expressly granted in this Agreement, +(a) NVIDIA reserves all rights, interests and remedies in connection with the Software, and (b) no +other license or right is granted to you by implication, estoppel or otherwise. + +8.2 Your Ownership. Subject to the rights of NVIDIA and its suppliers in the Software, which continue to +be licensed as stated in this Agreement, even when incorporated in your products, and the extent +permitted by applicable law, as between you and NVIDIA, you hold all rights, title and interest in and +to your services, applications and Derivatives you develop as permitted in this Agreement including +their respective intellectual property rights. + +9. Feedback. You may, but are not obligated to, provide suggestions, requests, fixes, modifications, +enhancements or other feedback regarding your use of the Software (“Feedback”). Feedback, even if +designated as confidential by you, will not create any confidentiality obligation for NVIDIA or its affiliates. If +you provide Feedback, you hereby grant NVIDIA, its affiliates and its designees a nonexclusive, perpetual, +irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, under your +intellectual property rights, to publicly perform, publicly display, reproduce, use, make, have made, sell, +offer for sale, distribute (through multiple tiers of distribution), import, create derivative works of and +otherwise commercialize and exploit the Feedback at NVIDIA’s discretion. + +10. Confidentiality. You may use confidential information only to exercise your rights and perform your +obligations under this Agreement. You will not disclose, nor authorize others to disclose NVIDIA Confidential +Information to any third party, except as expressly authorized in this Agreement and as necessary for the +Purpose, without obtaining NVIDIA’s prior written approval. Each recipient of confidential information must +be subject to a written agreement that includes confidentiality obligations consistent with these terms and +must have a need to know for the Purpose. You will protect the NVIDIA Confidential Information with at +least the same degree of care that you use to protect your own similar confidential and proprietary +information, but no less than a reasonable degree of care. Confidential information includes, but is not +limited to, the Software, including its features and functionality, Derivatives, and any results of +benchmarking or other competitive analysis or regression or performance data relating to the Software. +No Publicity. You may not issue any public statements about this Agreement, disclose the Software or +Derivatives, or any information or results related to your use of the Software, without prior written approval +of NVIDIA. + +11. Term and Termination. + +11.1 Term. This Agreement has a duration of twelve (12) months starting from the date of initial +download (even if you download the same version or updates of the Software later and it is +accompanied by this Agreement or another Agreement), unless terminated earlier in accordance +with this Agreement. + +11.2 Termination for Convenience. Either party may terminate this Agreement at any time with thirty (30) +days’ advance written notice to the other party. + +11.3 Termination for Cause. If you commence or participate in any legal proceeding against NVIDIA with +respect to the Software, this Agreement will terminate immediately without notice. Either party may +terminate this Agreement upon notice for cause if: +(a) the other party fails to cure a material breach of this Agreement within ten (10) days of the +non-breaching party’s notice of the breach; or +(b) the other party breaches its confidentiality obligations or license rights under this +Agreement, which termination will be effective immediately upon written notice. + +11.4 Effect of Termination. Upon any expiration or termination of this Agreement, you will promptly +(a) stop using and return, delete or destroy NVIDIA confidential information and all Software +received under this Agreement, and (b) delete or destroy Derivatives created under this Agreement, +unless an authorized NVIDIA representative provides prior written approval that you may keep a +copy of the Derivatives solely for archival purposes. Upon written request, you will certify in writing +that you have complied with your obligations under this “Effect of Termination” Section. + +11.5 Survival. The “License Grant to NVIDIA”, “Updates”, “Components Under Other Licenses”, +“Ownership”, “Feedback”, “Confidentiality”, “No Publicity”, “Effect of Termination”, “Survival”, +“Disclaimer of Warranties”, “Limitation of Liability”, “Indemnity” and all “General” Sections of this +Agreement will survive any expiration or termination of this Agreement. + +12. Disclaimer of Warranties. THE SOFTWARE IS PROVIDED BY NVIDIA AS-IS AND WITH ALL FAULTS. TO THE +MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, NVIDIA DISCLAIMS ALL WARRANTIES AND +REPRESENTATIONS OF ANY KIND, WHETHER EXPRESS, IMPLIED OR STATUTORY, RELATING TO OR ARISING +UNDER THIS AGREEMENT, INCLUDING, WITHOUT LIMITATION, THE WARRANTIES OF TITLE, +NONINFRINGEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, USAGE OF TRADE AND +COURSE OF DEALING. NVIDIA DOES NOT WARRANT OR ASSUME RESPONSIBILITY FOR THE ACCURACY OR +COMPLETENESS OF ANY THIRD-PARTY INFORMATION, TEXT, GRAPHICS, LINKS CONTAINED IN THE +SOFTWARE. WITHOUT LIMITING THE FOREGOING, NVIDIA DOES NOT WARRANT THAT THE SOFTWARE WILL +MEET YOUR REQUIREMENTS, ANY DEFECTS OR ERRORS WILL BE CORRECTED, ANY CERTAIN CONTENT WILL +BE AVAILABLE; OR THAT THE SOFTWARE IS FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS. NO +INFORMATION OR ADVICE GIVEN BY NVIDIA WILL IN ANY WAY INCREASE THE SCOPE OF ANY WARRANTY +EXPRESSLY PROVIDED IN THIS AGREEMENT. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE +APPROPRIATENESS OF USING THE SOFTWARE OR DERIVATIVES AND ASSUME ANY RISKS ASSOCIATED WITH +YOUR USE OF THE SOFTWARE OR DERIVATIVES. + +13. Limitations of Liability. + +13.1 EXCLUSIONS. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL +NVIDIA BE LIABLE FOR ANY (A) INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL OR CONSEQUENTIAL +DAMAGES, OR (B) DAMAGES FOR THE (I) COST OF PROCURING SUBSTITUTE GOODS OR (II) LOSS OF +PROFITS, REVENUES, USE, DATA OR GOODWILL ARISING OUT OF OR RELATED TO THIS AGREEMENT, +WHETHER BASED ON BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, OR +OTHERWISE, AND EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND +EVEN IF A PARTY’S REMEDIES FAIL THEIR ESSENTIAL PURPOSE. + +13.2 DAMAGES CAP. ADDITIONALLY, TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, +NVIDIA’S TOTAL CUMULATIVE AGGREGATE LIABILITY FOR ANY AND ALL LIABILITIES, OBLIGATIONS OR +CLAIMS ARISING OUT OF OR RELATED TO THIS AGREEMENT WILL NOT EXCEED ONE HUNDRED U.S. +DOLLARS (US$100). + +14. Indemnity. You will defend, indemnify and hold harmless NVIDIA and its affiliates, and their respective +employees, contractors, agents, officers and directors, from and against any and all third party claims, +damages, obligations, losses, liabilities, costs or debt, fines, restitutions and expenses (including but not +limited to attorney’s fees and costs incident to establishing the right of indemnification) arising out of use of +the Software and Derivatives outside of the scope of this Agreement or in breach of the terms of this +Agreement. + +15. General. + +15.1 Governing Law and Jurisdiction. This Agreement will be governed in all respects by the laws of the +United States and the laws of the State of Delaware, without regard to conflict of laws principles or +the United Nations Convention on Contracts for the International Sale of Goods. The state and +federal courts residing in Santa Clara County, California will have exclusive jurisdiction over any +dispute or claim arising out of or related to this Agreement, and the parties irrevocably consent to +personal jurisdiction and venue in those courts; except that either party may apply for injunctive +remedies or an equivalent type of urgent legal relief in any jurisdiction. + +15.2 Independent Contractors. The parties are independent contractors, and this Agreement does not +create a joint venture, partnership, agency or other form of business association between the +parties. Neither party will have the power to bind the other party or incur any obligation on its +behalf without the other party’s prior written consent. Nothing in this Agreement prevents either +party from participating in similar arrangements with third parties. + +15.3 No Assignment. NVIDIA may assign, delegate or transfer its rights or obligations under this +Agreement by any means or operation of law. You may not, without NVIDIA’s prior written consent, +assign, delegate or transfer any of your rights or obligations under this Agreement by any means or +operation of law, and any attempt to do so is null and void. + +15.4 No Waiver. No failure or delay by a party to enforce any term or obligation of this Agreement will +operate as a waiver by that party, or prevent the enforcement of such term or obligation later. + +15.5 Trade Compliance. You agree to comply with all applicable export, import, trade and economic +sanctions laws and regulations, as amended, including without limitation U.S. Export Administration +Regulations and Office of Foreign Assets Control regulations. You confirm (a) your understanding +that export or reexport of certain NVIDIA products or technologies may require a license or other +approval from appropriate authorities and (b) that you will not export or reexport any products or +technology, directly or indirectly, without first obtaining any required license or other approval from +appropriate authorities, (i) to any countries that are subject to any U.S. or local export restrictions +(currently including, but not necessarily limited to, Belarus, Cuba, Iran, North Korea, Russia, Syria, +the Region of Crimea, Donetsk People’s Republic Region and Luhansk People’s Republic Region); (ii) +to any end-user who you know or have reason to know will utilize them in the design, development +or production of nuclear, chemical or biological weapons, missiles, rocket systems, unmanned air +vehicles capable of a maximum range of at least 300 kilometers, regardless of payload, or intended +for military end-use, or any weapons of mass destruction; (iii) to any end-user who has been +prohibited from participating in the U.S. or local export transactions by any governing authority; or +(iv) to any known military or military-intelligence end-user or for any known military or military- +intelligence end-use in accordance with U.S. trade compliance laws and regulations. + +15.6 Government Rights. The Software, documentation and technology (“Protected Items”) are +“Commercial products” as this term is defined at 48 C.F.R. 2.101, consisting of “commercial +computer software” and “commercial computer software documentation” as such terms are used +in, respectively, 48 C.F.R. 12.212 and 48 C.F.R. 227.7202 & 252.227-7014(a)(1). Before any Protected +Items are supplied to the U.S. Government, you will (i) inform the U.S. Government in writing that +the Protected Items are and must be treated as commercial computer software and commercial +computer software documentation developed at private expense; (ii) inform the U.S. Government +that the Protected Items are provided subject to the terms of the Agreement; and (iii) mark the +Protected Items as commercial computer software and commercial computer software +documentation developed at private expense. In no event will you permit the U.S. Government to +acquire rights in Protected Items beyond those specified in 48 C.F.R. 52.227-19(b)(1)-(2) or 252.227- +7013(c) except as expressly approved by NVIDIA in writing. + +15.7 Notices. Please direct your legal notices or other correspondence to legalnotices@nvidia.com with a +copy mailed to NVIDIA Corporation, 2788 San Tomas Expressway, Santa Clara, California 95051, +United States of America, Attention: Legal Department. If NVIDIA needs to contact you about the +Software, you consent to receive the notices by email and agree that such notices will satisfy any +legal communication requirements. + +15.8 Severability. If a court of competent jurisdiction rules that a provision of this Agreement is +unenforceable, that provision will be deemed modified to the extent necessary to make it +enforceable and the remainder of this Agreement will continue in full force and effect. + +15.9 Amendment. Any amendment to this Agreement must be in writing and signed by authorized +representatives of both parties. + +15.10 Entire Agreement. Regarding the subject matter of this Agreement, the parties agree that (a) this +Agreement constitutes the entire and exclusive agreement between the parties and supersedes all +prior and contemporaneous communications and (b) any additional or different terms or conditions, +whether contained in purchase orders, order acknowledgments, invoices or otherwise, will not be +binding and are null and void. + +(v. February 25, 2025) + +NVIDIA Confidential \ No newline at end of file diff --git a/deps/cloudxr/react/README.md b/deps/cloudxr/react/README.md new file mode 100644 index 0000000..2e81485 --- /dev/null +++ b/deps/cloudxr/react/README.md @@ -0,0 +1,207 @@ +# React Three Fiber Example + +This is a comprehensive CloudXR.js React Three Fiber example application that demonstrates how to integrate CloudXR streaming with modern React development patterns. This example showcases the power of combining CloudXR.js with React Three Fiber, React Three XR, and React Three UIKit to create immersive XR experiences with rich 3D user interfaces. + +> NOTE: This example is not meant to be used for production. + +## Overview + +This example showcases the integration of CloudXR.js with the React Three Fiber ecosystem, providing: + +- **React Three Fiber Integration**: Seamless integration with Three.js through React components +- **React Three XR**: WebXR session management with React hooks and state management +- **React Three UIKit**: Rich 3D user interface components for VR/AR experiences +- **CloudXR Streaming**: Real-time streaming of XR content from a CloudXR server +- **Modern React Patterns**: Hooks, context, and component-based architecture +- **Dual UI System**: 2D HTML interface for configuration and 3D VR interface for interaction + +## Quick Start + +### Prerequisites +- Node.js (v20 or higher) +- A CloudXR server running and accessible +- A WebXR-compatible device (VR headset, AR device) + +### Installation + +1. **Navigate to the example folder** + ```bash + cd react + ``` + +2. **Install Dependencies** + ```bash + # For this early access release, please run the following to install SDK from the given tarball. This step will not be needed when SDK is publicly accessible. + npm install ../nvidia-cloudxr-6.0.0-beta.tgz + + npm install + ``` + +3. **Build the Application** + ```bash + npm run build + ``` + +4. **Start Development Server** + ```bash + npm run dev-server + ``` + +5. **Open in Browser** + - Navigate to `http://localhost:8080` (or the port shown in terminal) + - For desktop browsers, IWER (Immersive Web Emulator Runtime) will automatically load to emulate a Meta Quest 3 headset + +### Basic Usage + +1. **Configure Connection** + - Enter your CloudXR server IP address + - Set the port (default: 49100) + - Select AR or VR immersive mode + +2. **Adjust Settings** (Optional) + - Configure per-eye resolution (perEyeWidth and perEyeHeight, must be multiples of 16) + - Set target frame rate and bitrate + - Adjust XR reference space + +3. **Start Streaming** + - Click "CONNECT" to initiate the XR session + - Grant XR permissions when prompted + +> NOTE: In order to connect to an actual server and start streaming, you need: +> +> - A CloudXR server running and accessible +> - A WebXR-compatible device (VR/AR headset) or desktop browser (IWER loads automatically for emulation) + + +## Technical Architecture + +### Core Components + +#### `App.tsx` +Main React application component managing: +- XR store configuration and session state +- CloudXR component integration +- 2D UI management and event handling +- Error handling and capability checking +- React Three Fiber Canvas setup + +#### `CloudXRComponent.tsx` +Handles the core CloudXR streaming functionality: +- CloudXR session lifecycle management +- WebXR session event handling +- WebGL state management and render target preservation +- Frame-by-frame rendering loop with pose tracking +- Integration with Three.js WebXRManager + +#### `CloudXR2DUI.tsx` +Manages the 2D HTML interface: +- Form field management and localStorage persistence +- Proxy configuration based on protocol +- Event listener management and cleanup +- Error handling and user feedback +- Configuration validation and updates + +#### `CloudXRUI.tsx` (3D UI) +Renders the in-VR user interface: +- React Three UIKit components for 3D UI +- Interactive control buttons with hover effects +- Server information and status display +- Event handler integration + +## Development + +### Project Structure + +```bash +react/ +├── src/ +│ ├── App.tsx # Main React application +│ ├── CloudXRComponent.tsx # CloudXR streaming component +│ ├── CloudXR2DUI.tsx # 2D UI management class +│ ├── CloudXRUI.tsx # 3D VR UI component +│ ├── index.tsx # React app entry point +│ └── index.html # HTML template +├── public/ +│ ├── play-circle.svg # Play button icon (Heroicons) +│ ├── stop-circle.svg # Stop button icon (Heroicons) +│ ├── arrow-uturn-left.svg # Reset button icon (Heroicons) +│ └── arrow-left-start-on-rectangle.svg # Disconnect button icon (Heroicons) +├── package.json # Dependencies and scripts +├── webpack.common.js # Webpack configuration +├── webpack.dev.js # Development webpack config +├── webpack.prod.js # Production webpack config +└── tsconfig.json # TypeScript configuration +``` + +## React Three Fiber Integration + +### XR Store Configuration +The application uses React Three XR's store for XR session management: + +```javascript +const store = createXRStore({ + foveation: 0, + emulate: { syntheticEnvironment: false }, +}); +``` + +### Canvas Setup +React Three Fiber Canvas with WebXR integration: + +```typescript + + + + + + +``` + +### Custom Render Loop +The CloudXR component uses `useFrame` for custom rendering: + +```typescript +useFrame((state, delta) => { + if (webXRManager.isPresenting && session) { + // CloudXR rendering logic + cxrSession.sendTrackingStateToServer(timestamp, xrFrame); + cxrSession.render(timestamp, xrFrame, layer); + } +}, -1000); +``` + +## 3D User Interface + +### React Three UIKit Components +The 3D UI uses React Three UIKit for modern VR/AR interfaces: + +- **Container**: Layout and positioning components +- **Text**: 3D text rendering with custom fonts +- **Button**: Interactive buttons with hover effects +- **Image**: Texture-based image display +- **Root**: Main UI container with pixel-perfect rendering + +### UI Positioning +3D UI elements are positioned in world space: + +```typescript + + + {/* UI components */} + + +``` + +### WebGL State Tracking + +This example uses WebGL state tracking to prevent rendering conflicts between React Three Fiber and CloudXR. Both libraries render to the same WebGL context, but CloudXR's rendering operations modify WebGL state (framebuffers, textures, buffers, VAOs, shaders, blend modes, etc.) which can interfere with React Three Fiber's expectations. The example wraps the WebGL context with `bindGL()` from `@helpers/WebGLStateBinding`, then uses CloudXR's `onWebGLStateChangeBegin` and `onWebGLStateChangeEnd` callbacks to automatically save and restore state around CloudXR's rendering. This ensures React Three Fiber always finds the WebGL context in the expected state after each CloudXR render operation. + +See `examples/helpers/WebGLStateBinding.ts`, `WebGLState.ts`, and `WebGLStateApply.ts` for implementation details. Comprehensive tests are available in `tests/unit/WebGLState.test.ts` and `tests/playwright/WebGLTests/src/WebGLStateBindingTests.ts`. + +## License + +See the [LICENSE](LICENSE) file for details. + +### Third-Party Assets + +Icons used in the immersive UI are from [Heroicons](https://heroicons.com/) by Tailwind Labs, licensed under the MIT License. See [HEROICONS_LICENSE](public/HEROICONS_LICENSE) for details. diff --git a/deps/cloudxr/react/favicon.ico b/deps/cloudxr/react/favicon.ico new file mode 100644 index 0000000..a1de915 Binary files /dev/null and b/deps/cloudxr/react/favicon.ico differ diff --git a/deps/cloudxr/react/package.json b/deps/cloudxr/react/package.json new file mode 100644 index 0000000..72b6220 --- /dev/null +++ b/deps/cloudxr/react/package.json @@ -0,0 +1,47 @@ +{ + "name": "cloudxr-react-example", + "version": "6.0.0-beta", + "private": true, + "description": "React Three Fiber WebXR example for CloudXR", + "author": "NVIDIA Corporation", + "license": "SEE LICENSE IN LICENSE", + "keywords": [ + "react", + "three.js", + "webxr", + "cloudxr", + "vr" + ], + "scripts": { + "build": "webpack --config ./webpack.prod.js", + "dev": "webpack --config ./webpack.dev.js", + "dev-server": "webpack serve --config ./webpack.dev.js --no-open", + "dev-server:https": "HTTPS=true webpack serve --config ./webpack.dev.js --no-open", + "clean": "rimraf dist" + }, + "dependencies": { + "@nvidia/cloudxr": "dev", + "@react-three/drei": "^10.6.1", + "@react-three/fiber": "^9.3.0", + "@react-three/uikit": "^1.0.0", + "@react-three/uikit-default": "^1.0.0", + "@react-three/xr": "^6.6.22", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "three": "^0.172.0" + }, + "devDependencies": { + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@types/three": "^0.172.0", + "css-loader": "^6.8.1", + "rimraf": "^5.0.5", + "ts-loader": "^9.5.1", + "typescript": "^5.8.2", + "copy-webpack-plugin": "^13.0.0", + "html-webpack-plugin": "^5.6.3", + "style-loader": "^3.3.3", + "webpack-dev-server": "^5.2.1", + "webpack-cli": "^6.0.1" + } +} \ No newline at end of file diff --git a/deps/cloudxr/react/public/HEROICONS_LICENSE b/deps/cloudxr/react/public/HEROICONS_LICENSE new file mode 100644 index 0000000..d6a8229 --- /dev/null +++ b/deps/cloudxr/react/public/HEROICONS_LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Tailwind Labs, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/deps/cloudxr/react/public/arrow-left-start-on-rectangle.svg b/deps/cloudxr/react/public/arrow-left-start-on-rectangle.svg new file mode 100644 index 0000000..9fbe1ee --- /dev/null +++ b/deps/cloudxr/react/public/arrow-left-start-on-rectangle.svg @@ -0,0 +1,3 @@ + diff --git a/deps/cloudxr/react/public/arrow-uturn-left.svg b/deps/cloudxr/react/public/arrow-uturn-left.svg new file mode 100644 index 0000000..bfb8063 --- /dev/null +++ b/deps/cloudxr/react/public/arrow-uturn-left.svg @@ -0,0 +1,3 @@ + diff --git a/deps/cloudxr/react/public/controller-icons.svg b/deps/cloudxr/react/public/controller-icons.svg new file mode 100644 index 0000000..1d87b2a --- /dev/null +++ b/deps/cloudxr/react/public/controller-icons.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/deps/cloudxr/react/public/play-circle.svg b/deps/cloudxr/react/public/play-circle.svg new file mode 100644 index 0000000..d6a90c5 --- /dev/null +++ b/deps/cloudxr/react/public/play-circle.svg @@ -0,0 +1,3 @@ + diff --git a/deps/cloudxr/react/public/stop-circle.svg b/deps/cloudxr/react/public/stop-circle.svg new file mode 100644 index 0000000..a2d87ea --- /dev/null +++ b/deps/cloudxr/react/public/stop-circle.svg @@ -0,0 +1,3 @@ + diff --git a/deps/cloudxr/react/src/App.tsx b/deps/cloudxr/react/src/App.tsx new file mode 100644 index 0000000..698919b --- /dev/null +++ b/deps/cloudxr/react/src/App.tsx @@ -0,0 +1,555 @@ +/** + * App.tsx - Main CloudXR React Application + * + * This is the root component of the CloudXR React example application. It sets up: + * - WebXR session management and XR store configuration + * - CloudXR server configuration (IP, port, stream settings) + * - UI state management (connection status, session state) + * - Integration between CloudXR rendering component and UI components + * - Entry point for AR/VR experiences with CloudXR streaming + * + * The app integrates with the HTML interface which provides a "CONNECT" button + * to enter AR mode and displays the CloudXR UI with controls for teleop actions + * and disconnect when in XR mode. + */ + +import { checkCapabilities } from '@helpers/BrowserCapabilities'; +import { loadIWERIfNeeded } from '@helpers/LoadIWER'; +import { overridePressureObserver } from '@helpers/overridePressureObserver'; +import * as CloudXR from '@nvidia/cloudxr'; +import { Environment } from '@react-three/drei'; +import { Canvas } from '@react-three/fiber'; +import { setPreferredColorScheme } from '@react-three/uikit'; +import { XR, createXRStore, noEvents, PointerEvents, XROrigin, useXR } from '@react-three/xr'; +import { useState, useMemo, useEffect, useRef } from 'react'; + +import { CloudXR2DUI } from './CloudXR2DUI'; +import CloudXRComponent from './CloudXRComponent'; +import CloudXR3DUI from './CloudXRUI'; + +// Override PressureObserver early to catch errors from buggy browser implementations +overridePressureObserver(); + +const store = createXRStore({ + foveation: 0, + emulate: false, // Disable IWER emulation from react in favor of custom iwer loading function + // Configure WebXR input profiles to use local assets + // Use relative path from current page location + baseAssetPath: `${new URL('.', window.location).href}npm/@webxr-input-profiles/assets@${process.env.WEBXR_ASSETS_VERSION}/dist/profiles/`, + hand: { + model: false, // Disable hand models but keep pointer functionality + }, +}); + +setPreferredColorScheme('dark'); + +const START_TELEOP_COMMAND = { + type: 'teleop_command', + message: { + command: 'start teleop', + }, +} as const; + +// Environment component like controller-test +function NonAREnvironment() { + // Use local HDR file instead of preset so client doesn't need to download it from CDN + return ( + + ); +} + +function App() { + const COUNTDOWN_MAX_SECONDS = 9; + const COUNTDOWN_STORAGE_KEY = 'cxr.react.countdownSeconds'; + // 2D UI management + const [cloudXR2DUI, setCloudXR2DUI] = useState(null); + // IWER loading state + const [iwerLoaded, setIwerLoaded] = useState(false); + // Capability state management + const [capabilitiesValid, setCapabilitiesValid] = useState(false); + const capabilitiesCheckedRef = useRef(false); + // Connection state management + const [isConnected, setIsConnected] = useState(false); + // Session status management + const [sessionStatus, setSessionStatus] = useState('Disconnected'); + // Error message management + const [errorMessage, setErrorMessage] = useState(''); + // CloudXR session reference + const [cloudXRSession, setCloudXRSession] = useState(null); + // XR mode state for UI visibility + const [isXRMode, setIsXRMode] = useState(false); + // Server address being used for connection + const [serverAddress, setServerAddress] = useState(''); + // Teleop countdown and state + const [isCountingDown, setIsCountingDown] = useState(false); + const [countdownRemaining, setCountdownRemaining] = useState(0); + const [isTeleopRunning, setIsTeleopRunning] = useState(false); + const countdownTimerRef = useRef(null); + const [countdownDuration, setCountdownDuration] = useState(() => { + try { + const saved = localStorage.getItem(COUNTDOWN_STORAGE_KEY); + if (saved != null) { + const value = parseInt(saved, 10); + if (!isNaN(value)) { + return Math.min(COUNTDOWN_MAX_SECONDS, Math.max(0, value)); + } + } + } catch (_) {} + return 3; + }); + + // Persist countdown duration on change + useEffect(() => { + try { + localStorage.setItem(COUNTDOWN_STORAGE_KEY, String(countdownDuration)); + } catch (_) {} + }, [countdownDuration]); + + // Load IWER first (must happen before anything else) + // Note: React Three Fiber's emulation is disabled (emulate: false) to avoid conflicts + useEffect(() => { + const loadIWER = async () => { + const { supportsImmersive, iwerLoaded: wasIwerLoaded } = await loadIWERIfNeeded(); + if (!supportsImmersive) { + setErrorMessage('Immersive mode not supported'); + setIwerLoaded(false); + setCapabilitiesValid(false); + capabilitiesCheckedRef.current = false; // Reset check flag on failure + return; + } + // IWER loaded successfully, now we can proceed with capability checks + setIwerLoaded(true); + // Store whether IWER was loaded for status message display later + if (wasIwerLoaded) { + sessionStorage.setItem('iwerWasLoaded', 'true'); + } + }; + + loadIWER(); + }, []); + + // Update button state when IWER fails and UI becomes ready + useEffect(() => { + if (cloudXR2DUI && !iwerLoaded && !capabilitiesValid) { + cloudXR2DUI.setStartButtonState(true, 'CONNECT (immersive mode not supported)'); + } + }, [cloudXR2DUI, iwerLoaded, capabilitiesValid]); + + // Check capabilities once CloudXR2DUI is ready and IWER is loaded + useEffect(() => { + const checkCapabilitiesOnce = async () => { + if (!cloudXR2DUI || !iwerLoaded) { + return; + } + + // Guard: only check capabilities once + if (capabilitiesCheckedRef.current) { + return; + } + capabilitiesCheckedRef.current = true; + + // Disable button and show checking status + cloudXR2DUI.setStartButtonState(true, 'CONNECT (checking capabilities)'); + + let result: { success: boolean; failures: string[]; warnings: string[] } = { + success: false, + failures: [], + warnings: [], + }; + try { + result = await checkCapabilities(); + } catch (error) { + cloudXR2DUI.showStatus(`Capability check error: ${error}`, 'error'); + setCapabilitiesValid(false); + cloudXR2DUI.setStartButtonState(true, 'CONNECT (capability check failed)'); + capabilitiesCheckedRef.current = false; // Reset on error for potential retry + return; + } + if (!result.success) { + cloudXR2DUI.showStatus( + 'Browser does not meet required capabilities:\n' + result.failures.join('\n'), + 'error' + ); + setCapabilitiesValid(false); + cloudXR2DUI.setStartButtonState(true, 'CONNECT (capability check failed)'); + capabilitiesCheckedRef.current = false; // Reset on failure for potential retry + return; + } + + // Show final status message with IWER info if applicable + const iwerWasLoaded = sessionStorage.getItem('iwerWasLoaded') === 'true'; + if (result.warnings.length > 0) { + cloudXR2DUI.showStatus('Performance notice:\n' + result.warnings.join('\n'), 'info'); + } else if (iwerWasLoaded) { + // Include IWER status in the final success message + cloudXR2DUI.showStatus( + 'CloudXR.js SDK is supported. Ready to connect!\nUsing IWER (Immersive Web Emulator Runtime) - Emulating Meta Quest 3.', + 'info' + ); + } else { + cloudXR2DUI.showStatus('CloudXR.js SDK is supported. Ready to connect!', 'success'); + } + + setCapabilitiesValid(true); + cloudXR2DUI.setStartButtonState(false, 'CONNECT'); + }; + + checkCapabilitiesOnce(); + }, [cloudXR2DUI, iwerLoaded]); + + // Track config changes to trigger re-renders when form values change + const [configVersion, setConfigVersion] = useState(0); + + // Initialize CloudXR2DUI + useEffect(() => { + // Create and initialize the 2D UI manager + const ui = new CloudXR2DUI(() => { + // Callback when configuration changes + setConfigVersion(v => v + 1); + }); + ui.initialize(); + ui.setupConnectButtonHandler( + async () => { + // Start XR session + if (ui.getConfiguration().immersiveMode === 'ar') { + await store.enterAR(); + } else if (ui.getConfiguration().immersiveMode === 'vr') { + await store.enterVR(); + } else { + setErrorMessage('Unrecognized immersive mode'); + } + }, + (error: Error) => { + setErrorMessage(`Failed to start XR session: ${error}`); + } + ); + + setCloudXR2DUI(ui); + + // Cleanup function + return () => { + if (ui) { + ui.cleanup(); + } + }; + }, []); + + // Update HTML error message display when error state changes + useEffect(() => { + if (cloudXR2DUI) { + if (errorMessage) { + cloudXR2DUI.showError(errorMessage); + } else { + cloudXR2DUI.hideError(); + } + } + }, [errorMessage, cloudXR2DUI]); + + // Listen for XR session state changes to update button and UI visibility + useEffect(() => { + const handleXRStateChange = () => { + const xrState = store.getState(); + + if (xrState.mode === 'immersive-ar' || xrState.mode === 'immersive-vr') { + // XR session is active + setIsXRMode(true); + if (cloudXR2DUI) { + cloudXR2DUI.setStartButtonState(true, 'CONNECT (XR session active)'); + } + } else { + // XR session ended + setIsXRMode(false); + if (cloudXR2DUI) { + cloudXR2DUI.setStartButtonState(false, 'CONNECT'); + } + + if (xrState.error) { + setErrorMessage(`XR session error: ${xrState.error}`); + } + } + }; + + // Subscribe to XR state changes + const unsubscribe = store.subscribe(handleXRStateChange); + + // Cleanup + return () => { + unsubscribe(); + setIsXRMode(false); + }; + }, [cloudXR2DUI]); + + // CloudXR status change handler + const handleStatusChange = (connected: boolean, status: string) => { + setIsConnected(connected); + setSessionStatus(status); + }; + + // UI Event Handlers + const handleStartTeleop = () => { + console.log('Start Teleop pressed'); + + if (!cloudXRSession) { + console.error('CloudXR session not available'); + return; + } + + if (isCountingDown || isTeleopRunning) { + return; + } + + // Begin countdown before starting teleop (immediately if 0) + if (countdownDuration <= 0) { + setIsCountingDown(false); + setCountdownRemaining(0); + + try { + cloudXRSession.sendServerMessage(START_TELEOP_COMMAND); + console.log('Start teleop command sent'); + setIsTeleopRunning(true); + } catch (error) { + console.error('Failed to send teleop command:', error); + setIsTeleopRunning(false); + } + return; + } + + setIsCountingDown(true); + setCountdownRemaining(countdownDuration); + + countdownTimerRef.current = window.setInterval(() => { + setCountdownRemaining(prev => { + if (prev <= 1) { + // Countdown finished + if (countdownTimerRef.current !== null) { + clearInterval(countdownTimerRef.current); + countdownTimerRef.current = null; + } + setIsCountingDown(false); + + // Send start teleop command + + try { + cloudXRSession.sendServerMessage(START_TELEOP_COMMAND); + console.log('Start teleop command sent'); + setIsTeleopRunning(true); + } catch (error) { + console.error('Failed to send teleop command:', error); + setIsTeleopRunning(false); + } + + return 0; + } + return prev - 1; + }); + }, 1000); + }; + + const handleStopTeleop = () => { + console.log('Stop Teleop pressed'); + + // If countdown is active, cancel it and reset state + if (isCountingDown) { + if (countdownTimerRef.current !== null) { + clearInterval(countdownTimerRef.current); + countdownTimerRef.current = null; + } + setIsCountingDown(false); + setCountdownRemaining(0); + return; + } + + if (!cloudXRSession) { + console.error('CloudXR session not available'); + return; + } + + // Send stop teleop command + const teleopCommand = { + type: 'teleop_command', + message: { + command: 'stop teleop', + }, + }; + + try { + cloudXRSession.sendServerMessage(teleopCommand); + console.log('Stop teleop command sent'); + setIsTeleopRunning(false); + } catch (error) { + console.error('Failed to send teleop command:', error); + } + }; + + const handleResetTeleop = () => { + console.log('Reset Teleop pressed'); + + // Cancel any active countdown + if (countdownTimerRef.current !== null) { + clearInterval(countdownTimerRef.current); + countdownTimerRef.current = null; + } + setIsCountingDown(false); + setCountdownRemaining(0); + + if (!cloudXRSession) { + console.error('CloudXR session not available'); + return; + } + + // Send stop teleop command first + const stopCommand = { + type: 'teleop_command', + message: { + command: 'stop teleop', + }, + }; + + // Send reset teleop command + const resetCommand = { + type: 'teleop_command', + message: { + command: 'reset teleop', + }, + }; + + try { + cloudXRSession.sendServerMessage(stopCommand); + console.log('Stop teleop command sent'); + cloudXRSession.sendServerMessage(resetCommand); + console.log('Reset teleop command sent'); + setIsTeleopRunning(false); + } catch (error) { + console.error('Failed to send teleop commands:', error); + } + }; + + const handleDisconnect = () => { + console.log('Disconnect pressed'); + + // Cleanup countdown state on disconnect + if (countdownTimerRef.current !== null) { + clearInterval(countdownTimerRef.current); + countdownTimerRef.current = null; + } + setIsCountingDown(false); + setCountdownRemaining(0); + setIsTeleopRunning(false); + + const xrState = store.getState(); + const session = xrState.session; + if (session) { + session.end().catch((err: unknown) => { + setErrorMessage( + `Failed to end XR session: ${err instanceof Error ? err.message : String(err)}` + ); + }); + } + }; + + // Countdown configuration handlers (0-5 seconds) + const handleIncreaseCountdown = () => { + if (isCountingDown) return; + setCountdownDuration(prev => Math.min(COUNTDOWN_MAX_SECONDS, prev + 1)); + }; + + const handleDecreaseCountdown = () => { + if (isCountingDown) return; + setCountdownDuration(prev => Math.max(0, prev - 1)); + }; + + // Memo config based on configVersion (manual dependency tracker incremented on config changes) + // eslint-disable-next-line react-hooks/exhaustive-deps + const config = useMemo( + () => (cloudXR2DUI ? cloudXR2DUI.getConfiguration() : null), + [cloudXR2DUI, configVersion] + ); + + // Sync XR mode state to body class for CSS styling + useEffect(() => { + if (isXRMode) { + document.body.classList.add('xr-mode'); + } else { + document.body.classList.remove('xr-mode'); + } + + return () => { + document.body.classList.remove('xr-mode'); + }; + }, [isXRMode]); + + return ( + <> + { + e.preventDefault(); + }} + > + + + + + {cloudXR2DUI && config && ( + <> + { + if (cloudXR2DUI) { + cloudXR2DUI.showError(error); + } + }} + onSessionReady={setCloudXRSession} + onServerAddress={setServerAddress} + /> + + + )} + + + + ); +} + +export default App; diff --git a/deps/cloudxr/react/src/CloudXR2DUI.tsx b/deps/cloudxr/react/src/CloudXR2DUI.tsx new file mode 100644 index 0000000..d6b2848 --- /dev/null +++ b/deps/cloudxr/react/src/CloudXR2DUI.tsx @@ -0,0 +1,438 @@ +/** + * CloudXR2DUI.tsx - CloudXR 2D User Interface Management + * + * This class handles all the HTML form interactions, localStorage persistence, + * and form validation for the CloudXR React example. It follows the same pattern + * as the simple example's CloudXRWebUI class, providing a clean separation + * between UI management and React component logic. + * + * Features: + * - Form field management and localStorage persistence + * - Proxy configuration based on protocol + * - Form validation and default value handling + * - Event listener management + * - Error handling and logging + */ + +import { CloudXRConfig, enableLocalStorage, setupCertificateAcceptanceLink } from '@helpers/utils'; + +/** + * 2D UI Management for CloudXR React Example + * Handles the main user interface for CloudXR streaming, including form management, + * localStorage persistence, and user interaction controls. + */ +export class CloudXR2DUI { + /** Button to initiate XR streaming session */ + private startButton!: HTMLButtonElement; + /** Input field for the CloudXR server IP address */ + private serverIpInput!: HTMLInputElement; + /** Input field for the CloudXR server port number */ + private portInput!: HTMLInputElement; + /** Input field for proxy URL configuration */ + private proxyUrlInput!: HTMLInputElement; + /** Dropdown to select between AR and VR immersive modes */ + private immersiveSelect!: HTMLSelectElement; + /** Dropdown to select device frame rate (FPS) */ + private deviceFrameRateSelect!: HTMLSelectElement; + /** Dropdown to select max streaming bitrate (Mbps) */ + private maxStreamingBitrateMbpsSelect!: HTMLSelectElement; + /** Input field for per-eye width configuration */ + private perEyeWidthInput!: HTMLInputElement; + /** Input field for per-eye height configuration */ + private perEyeHeightInput!: HTMLInputElement; + /** Dropdown to select server backend type */ + private serverTypeSelect!: HTMLSelectElement; + /** Dropdown to select application type */ + private appSelect!: HTMLSelectElement; + /** Dropdown to select reference space for XR tracking */ + private referenceSpaceSelect!: HTMLSelectElement; + /** Input for XR reference space X offset (cm) */ + private xrOffsetXInput!: HTMLInputElement; + /** Input for XR reference space Y offset (cm) */ + private xrOffsetYInput!: HTMLInputElement; + /** Input for XR reference space Z offset (cm) */ + private xrOffsetZInput!: HTMLInputElement; + /** Text element displaying proxy configuration help */ + private proxyDefaultText!: HTMLElement; + /** Error message box element */ + private errorMessageBox!: HTMLElement; + /** Error message text element */ + private errorMessageText!: HTMLElement; + /** Certificate acceptance link container */ + private certAcceptanceLink!: HTMLElement; + /** Certificate acceptance link anchor */ + private certLink!: HTMLAnchorElement; + /** Flag to track if the 2D UI has been initialized */ + private initialized: boolean = false; + + /** Current form configuration state */ + private currentConfiguration: CloudXRConfig; + /** Callback function for configuration changes */ + private onConfigurationChange: ((config: CloudXRConfig) => void) | null = null; + /** Connect button click handler for cleanup */ + private handleConnectClick: ((event: Event) => void) | null = null; + /** Array to store all event listeners for proper cleanup */ + private eventListeners: Array<{ + element: HTMLElement; + event: string; + handler: EventListener; + }> = []; + /** Cleanup function for certificate acceptance link */ + private certLinkCleanup: (() => void) | null = null; + + /** + * Creates a new CloudXR2DUI instance + * @param onConfigurationChange - Callback function called when configuration changes + */ + constructor(onConfigurationChange?: (config: CloudXRConfig) => void) { + this.onConfigurationChange = onConfigurationChange || null; + this.currentConfiguration = this.getDefaultConfiguration(); + } + + /** + * Initializes the CloudXR2DUI with all necessary components and event handlers + */ + public initialize(): void { + if (this.initialized) { + return; + } + + try { + this.initializeElements(); + this.setupLocalStorage(); + this.setupProxyConfiguration(); + this.setupEventListeners(); + this.updateConfiguration(); + this.setStartButtonState(false, 'CONNECT'); + this.initialized = true; + } catch (error) { + // Continue with default values if initialization fails + this.showError(`Failed to initialize CloudXR2DUI: ${error}`); + } + } + + /** + * Initializes all DOM element references by their IDs + * Throws an error if any required element is not found + */ + private initializeElements(): void { + this.startButton = this.getElement('startButton'); + this.serverIpInput = this.getElement('serverIpInput'); + this.portInput = this.getElement('portInput'); + this.proxyUrlInput = this.getElement('proxyUrl'); + this.immersiveSelect = this.getElement('immersive'); + this.deviceFrameRateSelect = this.getElement('deviceFrameRate'); + this.maxStreamingBitrateMbpsSelect = + this.getElement('maxStreamingBitrateMbps'); + this.perEyeWidthInput = this.getElement('perEyeWidth'); + this.perEyeHeightInput = this.getElement('perEyeHeight'); + this.serverTypeSelect = this.getElement('serverType'); + this.appSelect = this.getElement('app'); + this.referenceSpaceSelect = this.getElement('referenceSpace'); + this.xrOffsetXInput = this.getElement('xrOffsetX'); + this.xrOffsetYInput = this.getElement('xrOffsetY'); + this.xrOffsetZInput = this.getElement('xrOffsetZ'); + this.proxyDefaultText = this.getElement('proxyDefaultText'); + this.errorMessageBox = this.getElement('errorMessageBox'); + this.errorMessageText = this.getElement('errorMessageText'); + this.certAcceptanceLink = this.getElement('certAcceptanceLink'); + this.certLink = this.getElement('certLink'); + } + + /** + * Gets a DOM element by ID with type safety + * @param id - The element ID to find + * @returns The found element with the specified type + * @throws Error if element is not found + */ + private getElement(id: string): T { + const element = document.getElementById(id) as T; + if (!element) { + throw new Error(`Element with id '${id}' not found`); + } + return element; + } + + /** + * Gets the default configuration values + * @returns Default configuration object + */ + private getDefaultConfiguration(): CloudXRConfig { + const useSecure = typeof window !== 'undefined' ? window.location.protocol === 'https:' : false; + // Default port: HTTP → 49100, HTTPS without proxy → 48322, HTTPS with proxy → 443 + const defaultPort = useSecure ? 48322 : 49100; + return { + serverIP: '127.0.0.1', + port: defaultPort, + useSecureConnection: useSecure, + perEyeWidth: 2048, + perEyeHeight: 1792, + deviceFrameRate: 90, + maxStreamingBitrateMbps: 150, + immersiveMode: 'ar', + app: 'generic', + serverType: 'manual', + proxyUrl: '', + referenceSpaceType: 'auto', + }; + } + + /** + * Enables localStorage persistence for form inputs + * Automatically saves and restores user preferences + */ + private setupLocalStorage(): void { + enableLocalStorage(this.serverTypeSelect, 'serverType'); + enableLocalStorage(this.serverIpInput, 'serverIp'); + enableLocalStorage(this.portInput, 'port'); + enableLocalStorage(this.perEyeWidthInput, 'perEyeWidth'); + enableLocalStorage(this.perEyeHeightInput, 'perEyeHeight'); + enableLocalStorage(this.proxyUrlInput, 'proxyUrl'); + enableLocalStorage(this.deviceFrameRateSelect, 'deviceFrameRate'); + enableLocalStorage(this.maxStreamingBitrateMbpsSelect, 'maxStreamingBitrateMbps'); + enableLocalStorage(this.immersiveSelect, 'immersiveMode'); + enableLocalStorage(this.appSelect, 'app'); + enableLocalStorage(this.referenceSpaceSelect, 'referenceSpace'); + enableLocalStorage(this.xrOffsetXInput, 'xrOffsetX'); + enableLocalStorage(this.xrOffsetYInput, 'xrOffsetY'); + enableLocalStorage(this.xrOffsetZInput, 'xrOffsetZ'); + } + + /** + * Configures proxy settings based on the current protocol (HTTP/HTTPS) + * Sets appropriate placeholders and help text for port and proxy URL inputs + */ + private setupProxyConfiguration(): void { + // Update port placeholder based on protocol + if (window.location.protocol === 'https:') { + this.portInput.placeholder = 'Port (default: 48322, or 443 if proxy URL set)'; + } else { + this.portInput.placeholder = 'Port (default: 49100)'; + } + + // Set default text and placeholder based on protocol + if (window.location.protocol === 'https:') { + this.proxyDefaultText.textContent = + 'Optional: Leave empty for direct WSS connection, or provide URL for proxy routing (e.g., https://proxy.example.com/)'; + this.proxyUrlInput.placeholder = ''; + } else { + this.proxyDefaultText.textContent = 'Not needed for HTTP - uses direct WS connection'; + this.proxyUrlInput.placeholder = ''; + } + } + + /** + * Sets up event listeners for form input changes + * Handles both input and change events for better compatibility + */ + private setupEventListeners(): void { + // Update configuration when form inputs change + const updateConfig = () => this.updateConfiguration(); + + // Helper function to add listeners and store them for cleanup + const addListener = (element: HTMLElement, event: string, handler: EventListener) => { + element.addEventListener(event, handler); + this.eventListeners.push({ element, event, handler }); + }; + + // Add event listeners for all form fields + addListener(this.serverTypeSelect, 'change', updateConfig); + addListener(this.serverIpInput, 'input', updateConfig); + addListener(this.serverIpInput, 'change', updateConfig); + addListener(this.portInput, 'input', updateConfig); + addListener(this.portInput, 'change', updateConfig); + addListener(this.perEyeWidthInput, 'input', updateConfig); + addListener(this.perEyeWidthInput, 'change', updateConfig); + addListener(this.perEyeHeightInput, 'input', updateConfig); + addListener(this.perEyeHeightInput, 'change', updateConfig); + addListener(this.deviceFrameRateSelect, 'change', updateConfig); + addListener(this.maxStreamingBitrateMbpsSelect, 'change', updateConfig); + addListener(this.immersiveSelect, 'change', updateConfig); + addListener(this.appSelect, 'change', updateConfig); + addListener(this.referenceSpaceSelect, 'change', updateConfig); + addListener(this.xrOffsetXInput, 'input', updateConfig); + addListener(this.xrOffsetXInput, 'change', updateConfig); + addListener(this.xrOffsetYInput, 'input', updateConfig); + addListener(this.xrOffsetYInput, 'change', updateConfig); + addListener(this.xrOffsetZInput, 'input', updateConfig); + addListener(this.xrOffsetZInput, 'change', updateConfig); + addListener(this.proxyUrlInput, 'input', updateConfig); + addListener(this.proxyUrlInput, 'change', updateConfig); + + // Set up certificate acceptance link and store cleanup function + this.certLinkCleanup = setupCertificateAcceptanceLink( + this.serverIpInput, + this.portInput, + this.proxyUrlInput, + this.certAcceptanceLink, + this.certLink + ); + } + + /** + * Updates the current configuration from form values + * Calls the configuration change callback if provided + */ + private updateConfiguration(): void { + const useSecure = this.getDefaultConfiguration().useSecureConnection; + const portValue = parseInt(this.portInput.value); + const hasProxy = this.proxyUrlInput.value.trim().length > 0; + + // Smart default port based on connection type and proxy usage + let defaultPort = 49100; // HTTP default + if (useSecure) { + defaultPort = hasProxy ? 443 : 48322; // HTTPS with proxy → 443, HTTPS without → 48322 + } + + const newConfiguration: CloudXRConfig = { + serverIP: this.serverIpInput.value || this.getDefaultConfiguration().serverIP, + port: portValue || defaultPort, + useSecureConnection: useSecure, + perEyeWidth: + parseInt(this.perEyeWidthInput.value) || this.getDefaultConfiguration().perEyeWidth, + perEyeHeight: + parseInt(this.perEyeHeightInput.value) || this.getDefaultConfiguration().perEyeHeight, + deviceFrameRate: + parseInt(this.deviceFrameRateSelect.value) || + this.getDefaultConfiguration().deviceFrameRate, + maxStreamingBitrateMbps: + parseInt(this.maxStreamingBitrateMbpsSelect.value) || + this.getDefaultConfiguration().maxStreamingBitrateMbps, + immersiveMode: + (this.immersiveSelect.value as 'ar' | 'vr') || this.getDefaultConfiguration().immersiveMode, + app: this.appSelect.value || this.getDefaultConfiguration().app, + serverType: this.serverTypeSelect.value || this.getDefaultConfiguration().serverType, + proxyUrl: this.proxyUrlInput.value || this.getDefaultConfiguration().proxyUrl, + referenceSpaceType: + (this.referenceSpaceSelect.value as 'auto' | 'local-floor' | 'local' | 'viewer') || + this.getDefaultConfiguration().referenceSpaceType, + // Convert cm from UI into meters for config (respect 0; if invalid, use 0) + xrOffsetX: (() => { + const v = parseFloat(this.xrOffsetXInput.value); + return Number.isFinite(v) ? v / 100 : 0; + })(), + xrOffsetY: (() => { + const v = parseFloat(this.xrOffsetYInput.value); + return Number.isFinite(v) ? v / 100 : 0; + })(), + xrOffsetZ: (() => { + const v = parseFloat(this.xrOffsetZInput.value); + return Number.isFinite(v) ? v / 100 : 0; + })(), + }; + + this.currentConfiguration = newConfiguration; + + // Call the configuration change callback if provided + if (this.onConfigurationChange) { + this.onConfigurationChange(newConfiguration); + } + } + + /** + * Gets the current configuration + * @returns Current configuration object + */ + public getConfiguration(): CloudXRConfig { + return { ...this.currentConfiguration }; + } + + /** + * Sets the start button state + * @param disabled - Whether the button should be disabled + * @param text - Text to display on the button + */ + public setStartButtonState(disabled: boolean, text: string): void { + if (this.startButton) { + this.startButton.disabled = disabled; + this.startButton.innerHTML = text; + } + } + + /** + * Sets up the connect button click handler + * @param onConnect - Function to call when connect button is clicked + * @param onError - Function to call when an error occurs + */ + public setupConnectButtonHandler( + onConnect: () => Promise, + onError: (error: Error) => void + ): void { + if (this.startButton) { + // Remove any existing listener + if (this.handleConnectClick) { + this.startButton.removeEventListener('click', this.handleConnectClick); + } + + // Create new handler + this.handleConnectClick = async () => { + // Disable button during XR session + this.setStartButtonState(true, 'CONNECT (starting XR session...)'); + + try { + await onConnect(); + } catch (error) { + this.setStartButtonState(false, 'CONNECT'); + onError(error as Error); + } + }; + + // Add the new listener + this.startButton.addEventListener('click', this.handleConnectClick); + } + } + + /** + * Shows a status message in the UI with a specific type + * @param message - Message to display + * @param type - Message type: 'success', 'error', or 'info' + */ + public showStatus(message: string, type: 'success' | 'error' | 'info'): void { + if (this.errorMessageText && this.errorMessageBox) { + this.errorMessageText.textContent = message; + this.errorMessageBox.className = `error-message-box show ${type}`; + } + console[type === 'error' ? 'error' : 'info'](message); + } + + /** + * Shows an error message in the UI + * @param message - Error message to display + */ + public showError(message: string): void { + this.showStatus(message, 'error'); + } + + /** + * Hides the error message + */ + public hideError(): void { + if (this.errorMessageBox) { + this.errorMessageBox.classList.remove('show'); + } + } + + /** + * Cleans up event listeners and resources + * Should be called when the component unmounts + */ + public cleanup(): void { + // Remove all stored event listeners + this.eventListeners.forEach(({ element, event, handler }) => { + element.removeEventListener(event, handler); + }); + this.eventListeners = []; + + // Remove CONNECT button listener + if (this.startButton && this.handleConnectClick) { + this.startButton.removeEventListener('click', this.handleConnectClick); + this.handleConnectClick = null; + } + + // Clean up certificate acceptance link listeners + if (this.certLinkCleanup) { + this.certLinkCleanup(); + this.certLinkCleanup = null; + } + } +} diff --git a/deps/cloudxr/react/src/CloudXRComponent.tsx b/deps/cloudxr/react/src/CloudXRComponent.tsx new file mode 100644 index 0000000..9b36ce6 --- /dev/null +++ b/deps/cloudxr/react/src/CloudXRComponent.tsx @@ -0,0 +1,288 @@ +/** + * CloudXRComponent.tsx - CloudXR WebXR Integration Component + * + * This component handles the core CloudXR streaming functionality and WebXR integration. + * It manages: + * - CloudXR session lifecycle (creation, connection, disconnection, cleanup) + * - WebXR session event handling (sessionstart, sessionend) + * - WebGL state management and render target preservation + * - Frame-by-frame rendering loop with pose tracking and stream rendering + * - Server configuration and connection parameters + * - Status reporting back to parent components + * + * The component accepts configuration via props and communicates status changes + * and disconnect requests through callback props. It integrates with Three.js + * and React Three Fiber for WebXR rendering while preserving WebGL state + * for CloudXR's custom rendering pipeline. + */ + +import { getConnectionConfig, ConnectionConfiguration, CloudXRConfig } from '@helpers/utils'; +import { bindGL } from '@helpers/WebGLStateBinding'; +import * as CloudXR from '@nvidia/cloudxr'; +import { useThree, useFrame } from '@react-three/fiber'; +import { useXR } from '@react-three/xr'; +import { useRef, useEffect } from 'react'; +import type { WebGLRenderer } from 'three'; + +interface CloudXRComponentProps { + config: CloudXRConfig; + onStatusChange?: (isConnected: boolean, status: string) => void; + onError?: (error: string) => void; + onSessionReady?: (session: CloudXR.Session | null) => void; + onServerAddress?: (address: string) => void; +} + +// React component that integrates CloudXR with Three.js/WebXR +// This component handles the CloudXR session lifecycle and render loop +export default function CloudXRComponent({ + config, + onStatusChange, + onError, + onSessionReady, + onServerAddress, +}: CloudXRComponentProps) { + const threeRenderer: WebGLRenderer = useThree().gl; + const { session } = useXR(); + // React reference to the CloudXR session that persists across re-renders. + const cxrSessionRef = useRef(null); + + // Disable Three.js so it doesn't clear the framebuffer after CloudXR renders. + threeRenderer.autoClear = false; + + // Access Three.js WebXRManager and WebGL context. + const gl: WebGL2RenderingContext = threeRenderer.getContext() as WebGL2RenderingContext; + + const trackedGL = bindGL(gl); + + // Set up event listeners in useEffect to add them only once + useEffect(() => { + const webXRManager = threeRenderer.xr; + + if (webXRManager) { + const handleSessionStart = async () => { + // Explicitly request the desired reference space from the XRSession to avoid + // inheriting a default 'local-floor' space that could stack with UI offsets. + let referenceSpace: XRReferenceSpace | null = null; + try { + const xrSession: XRSession | null = (webXRManager as any).getSession + ? (webXRManager as any).getSession() + : null; + if (xrSession) { + if (config.referenceSpaceType === 'auto') { + const fallbacks: XRReferenceSpaceType[] = [ + 'local-floor', + 'local', + 'viewer', + 'unbounded', + ]; + for (const t of fallbacks) { + try { + referenceSpace = await xrSession.requestReferenceSpace(t); + if (referenceSpace) break; + } catch (_) {} + } + } else { + try { + referenceSpace = await xrSession.requestReferenceSpace( + config.referenceSpaceType as XRReferenceSpaceType + ); + } catch (error) { + console.error( + `Failed to request reference space '${config.referenceSpaceType}':`, + error + ); + } + } + } + } catch (error) { + console.error('Failed to request XR reference space:', error); + referenceSpace = null; + } + + if (!referenceSpace) { + // As a last resort, fall back to WebXRManager's current reference space + referenceSpace = webXRManager.getReferenceSpace(); + } + + if (referenceSpace) { + // Ensure that the session is not already created. + if (cxrSessionRef.current) { + console.error('CloudXR session already exists'); + return; + } + + const glBinding = webXRManager.getBinding(); + if (!glBinding) { + console.warn('No WebGL binding found'); + } + + // Apply proxy configuration logic + let connectionConfig: ConnectionConfiguration; + try { + connectionConfig = getConnectionConfig(config.serverIP, config.port, config.proxyUrl); + onServerAddress?.(connectionConfig.serverIP); + } catch (error) { + onStatusChange?.(false, 'Configuration Error'); + onError?.(`Proxy configuration failed: ${error}`); + return; + } + + // Apply XR offset if provided in config (meters) + const offsetX = config.xrOffsetX || 0; + const offsetY = config.xrOffsetY || 0; + const offsetZ = config.xrOffsetZ || 0; + if (offsetX !== 0 || offsetY !== 0 || offsetZ !== 0) { + const offsetTransform = new XRRigidTransform( + { x: offsetX, y: offsetY, z: offsetZ }, + { x: 0, y: 0, z: 0, w: 1 } + ); + referenceSpace = referenceSpace.getOffsetReferenceSpace(offsetTransform); + } + + // Fill in CloudXR session options. + const cloudXROptions: CloudXR.SessionOptions = { + serverAddress: connectionConfig.serverIP, + serverPort: connectionConfig.port, + useSecureConnection: connectionConfig.useSecureConnection, + perEyeWidth: config.perEyeWidth, + perEyeHeight: config.perEyeHeight, + gl: gl, + referenceSpace: referenceSpace, + deviceFrameRate: config.deviceFrameRate, + maxStreamingBitrateKbps: config.maxStreamingBitrateMbps * 1000, // Convert Mbps to Kbps + glBinding: glBinding, + telemetry: { + enabled: true, + appInfo: { + version: '6.0.0-beta', + product: 'CloudXR.js React Example', + }, + }, + }; + + // Store the render target and key GL bindings to restore after CloudXR rendering + const cloudXRDelegates: CloudXR.SessionDelegates = { + onWebGLStateChangeBegin: () => { + // Save the current render target before CloudXR changes state + trackedGL.save(); + }, + onWebGLStateChangeEnd: () => { + // Restore the tracked GL state to the state before CloudXR rendering. + trackedGL.restore(); + }, + onStreamStarted: () => { + console.debug('CloudXR stream started'); + onStatusChange?.(true, 'Connected'); + }, + onStreamStopped: (error?: Error) => { + if (error) { + onStatusChange?.(false, 'Error'); + onError?.(`CloudXR session stopped with error: ${error.message}`); + } else { + console.debug('CloudXR session stopped'); + onStatusChange?.(false, 'Disconnected'); + } + // Clear the session reference + cxrSessionRef.current = null; + onSessionReady?.(null); + }, + }; + + // Create the CloudXR session. + let cxrSession: CloudXR.Session; + try { + cxrSession = CloudXR.createSession(cloudXROptions, cloudXRDelegates); + } catch (error) { + onStatusChange?.(false, 'Session Creation Failed'); + onError?.(`Failed to create CloudXR session: ${error}`); + return; + } + + // Store the session in the ref so it persists across re-renders + cxrSessionRef.current = cxrSession; + + // Notify parent that session is ready + onSessionReady?.(cxrSession); + + // Start session (synchronous call that initiates connection) + try { + cxrSession.connect(); + console.log('CloudXR session connect initiated'); + // Note: The session will transition to Connected state via the onStreamStarted callback + // Use cxrSession.state to check if streaming has actually started + } catch (error) { + onStatusChange?.(false, 'Connection Failed'); + // Report error via callback + onError?.('Failed to connect CloudXR session'); + // Clean up the failed session + cxrSessionRef.current = null; + } + } + }; + + const handleSessionEnd = () => { + if (cxrSessionRef.current) { + cxrSessionRef.current.disconnect(); + cxrSessionRef.current = null; + onSessionReady?.(null); + } + }; + + // Add start+end session event listeners to the WebXRManager. + webXRManager.addEventListener('sessionstart', handleSessionStart); + webXRManager.addEventListener('sessionend', handleSessionEnd); + + // Cleanup function to remove listeners + return () => { + webXRManager.removeEventListener('sessionstart', handleSessionStart); + webXRManager.removeEventListener('sessionend', handleSessionEnd); + }; + } + }, [threeRenderer, config]); // Re-register handlers when renderer or config changes + + // Custom render loop - runs every frame + useFrame((state, delta) => { + const webXRManager = threeRenderer.xr; + + if (webXRManager.isPresenting && session) { + // Access the current WebXR XRFrame + const xrFrame = state.gl.xr.getFrame(); + if (xrFrame) { + // Get THREE WebXRManager from the the useFrame state. + const webXRManager = state.gl.xr; + + if (!cxrSessionRef || !cxrSessionRef.current) { + console.debug('Skipping frame, no session yet'); + // Clear the framebuffer as we've set autoClear to false. + threeRenderer.clear(); + return; + } + + // Get session from reference. + const cxrSession: CloudXR.Session = cxrSessionRef.current; + + // If the CloudXR session is not connected, skip the frame. + if (cxrSession.state !== CloudXR.SessionState.Connected) { + console.debug('Skipping frame, session not connected, state:', cxrSession.state); + // Clear the framebuffer as we've set autoClear to false. + threeRenderer.clear(); + return; + } + + // Get timestamp from useFrame state and convert to milliseconds. + const timestamp: DOMHighResTimeStamp = state.clock.elapsedTime * 1000; + + // Send the tracking state (including viewer pose and hand/controller data) to the server, this will trigger server-side rendering for frame. + cxrSession.sendTrackingStateToServer(timestamp, xrFrame); + + // Get the WebXR layer from THREE WebXRManager. + let layer: XRWebGLLayer = webXRManager.getBaseLayer() as XRWebGLLayer; + + // Render the current streamed CloudXR frame (not the frame that was just sent to the server). + cxrSession.render(timestamp, xrFrame, layer); + } + } + }, -1000); + + return null; +} diff --git a/deps/cloudxr/react/src/CloudXRUI.tsx b/deps/cloudxr/react/src/CloudXRUI.tsx new file mode 100644 index 0000000..c753072 --- /dev/null +++ b/deps/cloudxr/react/src/CloudXRUI.tsx @@ -0,0 +1,218 @@ +/** + * CloudXRUI.tsx - CloudXR User Interface Component + * + * This component renders the in-VR user interface for the CloudXR application using + * React Three UIKit. It provides: + * - CloudXR branding and title display + * - Server connection information and status display + * - Interactive control buttons (Start Teleop, Reset Teleop, Disconnect) + * - Responsive button layout with hover effects + * - Integration with parent component event handlers + * - Configurable position and rotation in world space for flexible UI placement + * + * The UI is positioned in 3D space and designed for VR/AR interaction with + * visual feedback and clear button labeling. All interactions are passed + * back to the parent component through callback props. + */ + +import { Container, Text, Image } from '@react-three/uikit'; +import { Button } from '@react-three/uikit-default'; +import React from 'react'; + +interface CloudXRUIProps { + onStartTeleop?: () => void; + onDisconnect?: () => void; + onResetTeleop?: () => void; + serverAddress?: string; + sessionStatus?: string; + playLabel?: string; + playDisabled?: boolean; + countdownSeconds?: number; + onCountdownIncrease?: () => void; + onCountdownDecrease?: () => void; + countdownDisabled?: boolean; + position?: [number, number, number]; + rotation?: [number, number, number]; +} + +export default function CloudXR3DUI({ + onStartTeleop, + onDisconnect, + onResetTeleop, + serverAddress = '127.0.0.1', + sessionStatus = 'Disconnected', + playLabel = 'Play', + playDisabled = false, + countdownSeconds, + onCountdownIncrease, + onCountdownDecrease, + countdownDisabled = false, + position = [1.8, 1.75, -1.3], + rotation = [0, -0.3, 0], +}: CloudXRUIProps) { + return ( + + + + {/* Title */} + + Controls + + + {/* Server Info */} + + Server address: {serverAddress} + + + Session status: {sessionStatus} + + + {/* Countdown Config Row */} + + + Countdown + + + + + {countdownSeconds}s + + + + + + {/* Button Grid */} + + {/* Start/reset row*/} + + + + + + + {/* Bottom Row */} + + + + + + + + ); +} diff --git a/deps/cloudxr/react/src/index.html b/deps/cloudxr/react/src/index.html new file mode 100644 index 0000000..404b491 --- /dev/null +++ b/deps/cloudxr/react/src/index.html @@ -0,0 +1,576 @@ + + + + + + + + + NVIDIA CloudXR.js React Three Fiber Example + + + + + +
+
+
+

NVIDIA CloudXR.js React Three Fiber Example

+
+ +
+ + +
+

Debug Settings

+ +
+ + + +
+ +
+
+ +
+ + + +
+ Select the target device frame rate for the XR session +
+
+ +
+ + + +
+ Select the maximum streaming bitrate (in Megabits per second) for the XR session +
+
+ +
+ + + + + +
+ Configure the per-eye resolution. Width and height must be multiples of 16. +
+
+ +
+ + +
+ Select the preferred reference space for XR tracking. "Auto" uses fallback logic (local-floor → local → viewer). Other options will attempt to use the specified space only. +
+ + + + + + + +
+ Configure the XR reference space offset in centimeters. These values will be applied to the reference space transform to adjust the origin position of the XR experience. +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/deps/cloudxr/react/src/index.tsx b/deps/cloudxr/react/src/index.tsx new file mode 100644 index 0000000..48169f4 --- /dev/null +++ b/deps/cloudxr/react/src/index.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; + +import App from './App'; + +// Start the React app immediately in the 3d-ui container +function startApp() { + const reactContainer = document.getElementById('3d-ui'); + + if (reactContainer) { + const root = ReactDOM.createRoot(reactContainer); + root.render( + + + + ); + } else { + console.error('3d-ui container not found'); + } +} + +// Initialize the app when DOM is ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', startApp); +} else { + startApp(); +} diff --git a/deps/cloudxr/react/tsconfig.json b/deps/cloudxr/react/tsconfig.json new file mode 100644 index 0000000..486f69a --- /dev/null +++ b/deps/cloudxr/react/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "CommonJS", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": false, + "outDir": "./dist", + "incremental": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noFallthroughCasesInSwitch": true, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@helpers/*": ["../helpers/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/deps/cloudxr/react/webpack-plugins/DownloadAssetsPlugin.js b/deps/cloudxr/react/webpack-plugins/DownloadAssetsPlugin.js new file mode 100644 index 0000000..9433fcf --- /dev/null +++ b/deps/cloudxr/react/webpack-plugins/DownloadAssetsPlugin.js @@ -0,0 +1,109 @@ +const fs = require('fs'); +const path = require('path'); +const https = require('https'); + +class DownloadAssetsPlugin { + constructor(assets) { + this.assets = assets; + this.hasRun = false; + this.createdDirs = new Set(); + } + + safeUnlink(filePath) { + try { + fs.unlinkSync(filePath); + } catch (err) { + // Ignore cleanup errors + } + } + + apply(compiler) { + compiler.hooks.beforeCompile.tapAsync('DownloadAssetsPlugin', (params, callback) => { + // Only run once per webpack process + if (this.hasRun) { + callback(); + return; + } + + console.log('📦 Checking and downloading required assets...'); + + const downloadPromises = this.assets.map(asset => this.downloadFile(asset)); + + Promise.allSettled(downloadPromises) + .then((results) => { + const failed = results.filter(r => r.status === 'rejected'); + if (failed.length > 0) { + console.warn(`⚠️ ${failed.length} asset(s) failed to download, continuing anyway...`); + } + console.log('✅ Asset check complete!'); + this.hasRun = true; + callback(); + }); + }); + } + + downloadFile({ url, output }) { + return new Promise((resolve, reject) => { + // Ensure directory exists (only once per unique path) + if (!this.createdDirs.has(output)) { + fs.mkdirSync(output, { recursive: true }); + this.createdDirs.add(output); + } + + const filename = path.basename(url); + const filePath = path.join(output, filename); + + // Skip if file already exists + if (fs.existsSync(filePath)) { + resolve(); + return; + } + + console.log(` Downloading ${filename}...`); + + const file = fs.createWriteStream(filePath); + + const downloadFromUrl = (downloadUrl) => { + https.get(downloadUrl, (response) => { + // Handle redirects + if (response.statusCode === 302 || response.statusCode === 301) { + file.close(); + this.safeUnlink(filePath); + downloadFromUrl(response.headers.location); + return; + } + + if (response.statusCode !== 200) { + file.close(); + this.safeUnlink(filePath); + reject(new Error(`Failed to download ${filename}: HTTP ${response.statusCode}`)); + return; + } + + response.pipe(file); + + file.on('finish', () => { + file.close(); + console.log(` ✓ Downloaded ${filename}`); + resolve(); + }); + + file.on('error', (err) => { + this.safeUnlink(filePath); + reject(err); + }); + }).on('error', (err) => { + if (fs.existsSync(filePath)) { + this.safeUnlink(filePath); + } + reject(err); + }); + }; + + downloadFromUrl(url); + }); + } +} + +module.exports = DownloadAssetsPlugin; + diff --git a/deps/cloudxr/react/webpack.common.js b/deps/cloudxr/react/webpack.common.js new file mode 100644 index 0000000..4304484 --- /dev/null +++ b/deps/cloudxr/react/webpack.common.js @@ -0,0 +1,168 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const DownloadAssetsPlugin = require('./webpack-plugins/DownloadAssetsPlugin'); + +const WEBXR_ASSETS_VERSION = '1.0.19'; + +module.exports = { + entry: './src/index.tsx', + + // Module rules define how different file types are processed + module: { + rules: [ + { + test: /\.tsx?$/, + use: { + loader: 'ts-loader', + options: { + // Only transpile, don't type-check (faster builds) + transpileOnly: true, + }, + }, + exclude: /node_modules/, + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'], + }, + ], + }, + + // Resolve configuration for module resolution + resolve: { + extensions: ['.tsx', '.ts', '.js'], + alias: { + // @helpers can be used instead of relative paths to the helpers directory + '@helpers': path.resolve(__dirname, '../helpers') + } + }, + + // Output configuration for bundled files + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, './build'), + }, + + // Webpack plugins that extend webpack's functionality + plugins: [ + // Generates HTML file and automatically injects bundled JavaScript + new HtmlWebpackPlugin({ + template: './src/index.html', + favicon: './favicon.ico' + }), + + // Inject environment variables + new webpack.DefinePlugin({ + 'process.env.WEBXR_ASSETS_VERSION': JSON.stringify(WEBXR_ASSETS_VERSION), + }), + + // Download external assets during build + new DownloadAssetsPlugin([ + // HDRI environment map + { + url: 'https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/potsdamer_platz_1k.hdr', + output: path.resolve(__dirname, 'public/assets/hdri') + }, + // WebXR controller profiles + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/profilesList.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles`) + }, + // Generic hand profile + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-hand/profile.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-hand`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-hand/left.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-hand`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-hand/right.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-hand`) + }, + // Generic trigger profile + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-trigger/profile.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-trigger`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-trigger/left.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-trigger`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-trigger/right.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/generic-trigger`) + }, + // Oculus Touch v2 + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v2/profile.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v2`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v2/left.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v2`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v2/right.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v2`) + }, + // Oculus Touch v3 + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v3/profile.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v3`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v3/left.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v3`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v3/right.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/oculus-touch-v3`) + }, + // Meta Quest Touch Plus + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/meta-quest-touch-plus/profile.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/meta-quest-touch-plus`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/meta-quest-touch-plus/left.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/meta-quest-touch-plus`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/meta-quest-touch-plus/right.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/meta-quest-touch-plus`) + }, + // Pico 4 Ultra + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/pico-4u/profile.json`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/pico-4u`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/pico-4u/left.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/pico-4u`) + }, + { + url: `https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/pico-4u/right.glb`, + output: path.resolve(__dirname, `public/npm/@webxr-input-profiles/assets@${WEBXR_ASSETS_VERSION}/dist/profiles/pico-4u`) + }, + ]), + + // Copies static assets from public directory to build output + new CopyWebpackPlugin({ + patterns: [ + { + from: 'public', + to: '.', + globOptions: { + // Don't copy index.html since HtmlWebpackPlugin handles it + ignore: ['**/index.html'], + }, + }, + { from: './favicon.ico', to: 'favicon.ico' }, + ], + }), + ], +}; diff --git a/deps/cloudxr/react/webpack.dev.js b/deps/cloudxr/react/webpack.dev.js new file mode 100644 index 0000000..8bd22f3 --- /dev/null +++ b/deps/cloudxr/react/webpack.dev.js @@ -0,0 +1,46 @@ +const { merge } = require('webpack-merge') +const common = require('./webpack.common.js') +const path = require('path') + +// Check if HTTPS mode is enabled via environment variable +const useHttps = process.env.HTTPS === 'true' + +module.exports = merge(common, { + mode: 'development', + devtool: 'eval-source-map', + devServer: { + allowedHosts: 'all', + hot: true, + open: false, + // Enable HTTPS with self-signed certificate when HTTPS=true + ...(useHttps && { server: 'https' }), + static: [ + { + directory: path.join(__dirname, './build'), + }, + { + directory: path.join(__dirname, './public'), + publicPath: '/', + }, + ], + watchFiles: { + paths: ['src/**/*', '../../build/**/*'], + options: { + usePolling: false, + ignored: /node_modules/, + }, + }, + client: { + progress: true, + overlay: { + errors: true, + warnings: false, + }, + }, + devMiddleware: { + writeToDisk: true, + }, + compress: true, + port: 8080, + }, +}) diff --git a/deps/cloudxr/react/webpack.prod.js b/deps/cloudxr/react/webpack.prod.js new file mode 100644 index 0000000..4f7d055 --- /dev/null +++ b/deps/cloudxr/react/webpack.prod.js @@ -0,0 +1,6 @@ +const { merge } = require('webpack-merge'); +const common = require('./webpack.common.js'); + +module.exports = merge(common, { + mode: 'production' +}); diff --git a/deps/cloudxr/simple/LICENSE b/deps/cloudxr/simple/LICENSE new file mode 100644 index 0000000..5e53852 --- /dev/null +++ b/deps/cloudxr/simple/LICENSE @@ -0,0 +1,307 @@ +NVIDIA SOFTWARE EVALUATION LICENSE AGREEMENT + +IMPORTANT NOTICE – PLEASE READ AND AGREE BEFORE USING THE SOFTWARE + +This software evaluation license agreement (“Agreement”) is a legal agreement between you, whether an +individual or entity, (“you”) and NVIDIA Corporation and its affiliates (“NVIDIA”) and governs the use of certain +NVIDIA CloudXR software and documentation that NVIDIA delivers to you under this Agreement (“Software”). +NVIDIA and you are each a “party” and collectively the “parties.” +This Agreement can be accepted only by an adult of legal age of majority in the country in which the Software is +used. If you don’t have the required age or authority to accept this Agreement, or if you don’t accept all the +terms and conditions of this Agreement, do not use the Software. + +1. License Grants. + +1.1 License Grant to You. The Software is licensed, not sold. Subject to the terms of this Agreement, +NVIDIA grants you a limited, non-exclusive, revocable, non-transferable, non-sublicensable (except +as expressly granted in this Agreement), license to: +(a) access, install and use copies of the Software, +(b) configure the Software using configuration files provided (if applicable), +(c) modify and create derivative works of any source code NVIDIA delivers to you as part of the +Software (“Derivatives”) (if applicable). + +All the foregoing grants are only for internal test and evaluation purposes and, as applicable, for use (a) in +client systems, or (b) in server systems with NVIDIA GPUs (“Purpose”). + +1.2 License Grant to NVIDIA. Subject to the terms of this Agreement, you grant NVIDIA a non-exclusive, +perpetual, irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, +under your intellectual property rights, to publicly perform, publicly display, reproduce, use, make, +have made, sell, offer for sale, distribute (through multiple tiers of distribution), import, create +derivative works of and otherwise commercialize and exploit at NVIDIA’s discretion any Derivatives +created by or for you. You may, but are not required to, deliver any Derivatives to NVIDIA. + +2. License Restrictions. Your license to use the Software and Derivatives is restricted as stated in this “License +Restrictions” Section. You will cooperate with NVIDIA and, upon NVIDIA’s written request, you will confirm +in writing and provide reasonably requested information to verify your compliance with the terms of this +Agreement. You may not: + +2.1 Use the Software or Derivatives for any purpose other than the Purpose, including but not limited to +in production; + +2.2 Sell, rent, sublicense, transfer, distribute or otherwise make available to others (except Authorized +Users as stated in the “Authorized Users” Section) any portion of the Software or Derivatives, except +as expressly granted in Section 1.1 (“License Grant to You”); + +2.3 Reverse engineer, decompile, or disassemble the Software components provided in binary form, nor +attempt in any other manner to obtain source code of such Software; + +2.4 Modify or create derivative works of the Software, except as expressly granted in Section 1.1 +(“License Grant to You”); + +2.5 Change or remove copyright or other proprietary notices in the Software; + +2.6 Bypass, disable, or circumvent any technical limitation, encryption, security, digital rights +management or authentication mechanism in the Software; + +2.7 Use the Software or Derivatives in any manner that would cause them to become subject to an open +source software license; subject to the terms in Section 7 (“Components Under Other Licenses”); + +2.8 Use the Software or Derivatives for the purpose of developing competing products or technologies +or assist a third party in such activities; + +2.9 Replace any Software components governed by this Agreement with other software that +implements NVIDIA APIs; + +2.10 Use the Software or Derivatives in violation of any applicable law or regulation in the relevant +jurisdictions; or + +2.11 Use the Software in or with any system or application where the use or failure of such system or +application developed or deployed with Software could result in injury, death or catastrophic +damage (“Mission Critical Applications”). NVIDIA will not be liable to you or any third party, in whole +or in part, for any claims or damages arising from uses in Mission Critical Applications. + +2.12 Disclose any evaluation or test results regarding the Software or Derivatives without NVIDIA’s prior +written consent. + +3. Authorized Users. You may allow employees and contractors of your entity or of your subsidiary(ies), and +for educational institutions also enrolled students, to internally access and use the Software as authorized +by this Agreement from your secure network to perform the work authorized by this Agreement on your +behalf. You are responsible for the compliance with the terms of this Agreement by your authorized users. +Any act or omission that if committed by you would constitute a breach of this Agreement will be deemed to +constitute a breach of this Agreement if committed by your authorized users. + +4. Pre-Release. Software versions identified as alpha, beta, preview, early access or otherwise as pre-release +may not be fully functional, may contain errors or design flaws, and may have reduced or different security, +privacy, availability and reliability standards relative to NVIDIA commercial offerings. You use pre-release +Software at your own risk. NVIDIA did not design or test the Software for use in production or business +critical systems. NVIDIA may choose not to make available a commercial version of pre-release Software. +NVIDIA may also choose to abandon development and terminate the availability of pre-release Software at +any time without liability. + +5. Your Privacy: Collection and Use of Information. + +5.1 Privacy Policy. Please review the NVIDIA Privacy Policy, located at https://www.nvidia.com/enus/about-nvidia/privacy-policy, which explains NVIDIA’s policy for collecting and using data, as well +as visit the NVIDIA Privacy Center, located at https://www.nvidia.com/en-us/privacy-center, to +manage your consent and privacy preferences. + +5.2 Collection Purposes. You also acknowledge that the Software collects data for the following +purposes: (a) properly configure and optimize products for use with Software; and (b) improve +NVIDIA products and services. Information collected by the Software includes: (i) application +configuration; (ii) browser version; (iii) and session metadata (i.e. performance and usage +statistics). Additionally, NVIDIA may collect certain personal information, such as your name +and email address or those of your authorized users, and other information necessary to +authenticate and enable you or your authorized users’ access to the Software. Where appropriate +you will disclose to, and obtain any necessary consent from, your authorized users to allow NVIDIA +to collect such information. + +5.3 Third Party Privacy Practices. The Software may contain links to third party websites and services. +NVIDIA encourages you to review the privacy statements on those sites and services that you choose +to visit to understand how they may collect, use and share your data. NVIDIA is not responsible for +the privacy statements or practices of third-party sites or services. + +6. Updates. NVIDIA may at any time and at its option, change, discontinue, or deprecate any part, or all, of the +Software, or change or remove features or functionality, or make available patches, workarounds or other +updates to the Software. Unless the updates are provided with their separate governing terms, they are +deemed part of the Software licensed to you under this Agreement, and your continued use of the Software +is deemed acceptance of such changes. + +7. Components Under Other Licenses. The Software may include or be distributed with components provided +with separate legal notices or terms that accompany the components, such as open source software licenses +and other license terms (“Other Licenses”). The components are subject to the applicable Other Licenses, +including any proprietary notices, disclaimers, requirements and extended use rights; except that this +Agreement will prevail regarding the use of third-party open source software, unless a third-party open +source software license requires its license terms to prevail. Open source software license means any +software, data or documentation subject to any license identified as an open source license by the Open +Source Initiative (http://opensource.org), Free Software Foundation (http://www.fsf.org) or other similar +open source organization or listed by the Software Package Data Exchange (SPDX) Workgroup under the +Linux Foundation (http://www.spdx.org). + +8. Ownership. + +8.1 NVIDIA Ownership. The Software, including all intellectual property rights, is and will remain the sole +and exclusive property of NVIDIA or its licensors. Except as expressly granted in this Agreement, +(a) NVIDIA reserves all rights, interests and remedies in connection with the Software, and (b) no +other license or right is granted to you by implication, estoppel or otherwise. + +8.2 Your Ownership. Subject to the rights of NVIDIA and its suppliers in the Software, which continue to +be licensed as stated in this Agreement, even when incorporated in your products, and the extent +permitted by applicable law, as between you and NVIDIA, you hold all rights, title and interest in and +to your services, applications and Derivatives you develop as permitted in this Agreement including +their respective intellectual property rights. + +9. Feedback. You may, but are not obligated to, provide suggestions, requests, fixes, modifications, +enhancements or other feedback regarding your use of the Software (“Feedback”). Feedback, even if +designated as confidential by you, will not create any confidentiality obligation for NVIDIA or its affiliates. If +you provide Feedback, you hereby grant NVIDIA, its affiliates and its designees a nonexclusive, perpetual, +irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and transferable license, under your +intellectual property rights, to publicly perform, publicly display, reproduce, use, make, have made, sell, +offer for sale, distribute (through multiple tiers of distribution), import, create derivative works of and +otherwise commercialize and exploit the Feedback at NVIDIA’s discretion. + +10. Confidentiality. You may use confidential information only to exercise your rights and perform your +obligations under this Agreement. You will not disclose, nor authorize others to disclose NVIDIA Confidential +Information to any third party, except as expressly authorized in this Agreement and as necessary for the +Purpose, without obtaining NVIDIA’s prior written approval. Each recipient of confidential information must +be subject to a written agreement that includes confidentiality obligations consistent with these terms and +must have a need to know for the Purpose. You will protect the NVIDIA Confidential Information with at +least the same degree of care that you use to protect your own similar confidential and proprietary +information, but no less than a reasonable degree of care. Confidential information includes, but is not +limited to, the Software, including its features and functionality, Derivatives, and any results of +benchmarking or other competitive analysis or regression or performance data relating to the Software. +No Publicity. You may not issue any public statements about this Agreement, disclose the Software or +Derivatives, or any information or results related to your use of the Software, without prior written approval +of NVIDIA. + +11. Term and Termination. + +11.1 Term. This Agreement has a duration of twelve (12) months starting from the date of initial +download (even if you download the same version or updates of the Software later and it is +accompanied by this Agreement or another Agreement), unless terminated earlier in accordance +with this Agreement. + +11.2 Termination for Convenience. Either party may terminate this Agreement at any time with thirty (30) +days’ advance written notice to the other party. + +11.3 Termination for Cause. If you commence or participate in any legal proceeding against NVIDIA with +respect to the Software, this Agreement will terminate immediately without notice. Either party may +terminate this Agreement upon notice for cause if: +(a) the other party fails to cure a material breach of this Agreement within ten (10) days of the +non-breaching party’s notice of the breach; or +(b) the other party breaches its confidentiality obligations or license rights under this +Agreement, which termination will be effective immediately upon written notice. + +11.4 Effect of Termination. Upon any expiration or termination of this Agreement, you will promptly +(a) stop using and return, delete or destroy NVIDIA confidential information and all Software +received under this Agreement, and (b) delete or destroy Derivatives created under this Agreement, +unless an authorized NVIDIA representative provides prior written approval that you may keep a +copy of the Derivatives solely for archival purposes. Upon written request, you will certify in writing +that you have complied with your obligations under this “Effect of Termination” Section. + +11.5 Survival. The “License Grant to NVIDIA”, “Updates”, “Components Under Other Licenses”, +“Ownership”, “Feedback”, “Confidentiality”, “No Publicity”, “Effect of Termination”, “Survival”, +“Disclaimer of Warranties”, “Limitation of Liability”, “Indemnity” and all “General” Sections of this +Agreement will survive any expiration or termination of this Agreement. + +12. Disclaimer of Warranties. THE SOFTWARE IS PROVIDED BY NVIDIA AS-IS AND WITH ALL FAULTS. TO THE +MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, NVIDIA DISCLAIMS ALL WARRANTIES AND +REPRESENTATIONS OF ANY KIND, WHETHER EXPRESS, IMPLIED OR STATUTORY, RELATING TO OR ARISING +UNDER THIS AGREEMENT, INCLUDING, WITHOUT LIMITATION, THE WARRANTIES OF TITLE, +NONINFRINGEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, USAGE OF TRADE AND +COURSE OF DEALING. NVIDIA DOES NOT WARRANT OR ASSUME RESPONSIBILITY FOR THE ACCURACY OR +COMPLETENESS OF ANY THIRD-PARTY INFORMATION, TEXT, GRAPHICS, LINKS CONTAINED IN THE +SOFTWARE. WITHOUT LIMITING THE FOREGOING, NVIDIA DOES NOT WARRANT THAT THE SOFTWARE WILL +MEET YOUR REQUIREMENTS, ANY DEFECTS OR ERRORS WILL BE CORRECTED, ANY CERTAIN CONTENT WILL +BE AVAILABLE; OR THAT THE SOFTWARE IS FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS. NO +INFORMATION OR ADVICE GIVEN BY NVIDIA WILL IN ANY WAY INCREASE THE SCOPE OF ANY WARRANTY +EXPRESSLY PROVIDED IN THIS AGREEMENT. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE +APPROPRIATENESS OF USING THE SOFTWARE OR DERIVATIVES AND ASSUME ANY RISKS ASSOCIATED WITH +YOUR USE OF THE SOFTWARE OR DERIVATIVES. + +13. Limitations of Liability. + +13.1 EXCLUSIONS. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL +NVIDIA BE LIABLE FOR ANY (A) INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL OR CONSEQUENTIAL +DAMAGES, OR (B) DAMAGES FOR THE (I) COST OF PROCURING SUBSTITUTE GOODS OR (II) LOSS OF +PROFITS, REVENUES, USE, DATA OR GOODWILL ARISING OUT OF OR RELATED TO THIS AGREEMENT, +WHETHER BASED ON BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, OR +OTHERWISE, AND EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND +EVEN IF A PARTY’S REMEDIES FAIL THEIR ESSENTIAL PURPOSE. + +13.2 DAMAGES CAP. ADDITIONALLY, TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, +NVIDIA’S TOTAL CUMULATIVE AGGREGATE LIABILITY FOR ANY AND ALL LIABILITIES, OBLIGATIONS OR +CLAIMS ARISING OUT OF OR RELATED TO THIS AGREEMENT WILL NOT EXCEED ONE HUNDRED U.S. +DOLLARS (US$100). + +14. Indemnity. You will defend, indemnify and hold harmless NVIDIA and its affiliates, and their respective +employees, contractors, agents, officers and directors, from and against any and all third party claims, +damages, obligations, losses, liabilities, costs or debt, fines, restitutions and expenses (including but not +limited to attorney’s fees and costs incident to establishing the right of indemnification) arising out of use of +the Software and Derivatives outside of the scope of this Agreement or in breach of the terms of this +Agreement. + +15. General. + +15.1 Governing Law and Jurisdiction. This Agreement will be governed in all respects by the laws of the +United States and the laws of the State of Delaware, without regard to conflict of laws principles or +the United Nations Convention on Contracts for the International Sale of Goods. The state and +federal courts residing in Santa Clara County, California will have exclusive jurisdiction over any +dispute or claim arising out of or related to this Agreement, and the parties irrevocably consent to +personal jurisdiction and venue in those courts; except that either party may apply for injunctive +remedies or an equivalent type of urgent legal relief in any jurisdiction. + +15.2 Independent Contractors. The parties are independent contractors, and this Agreement does not +create a joint venture, partnership, agency or other form of business association between the +parties. Neither party will have the power to bind the other party or incur any obligation on its +behalf without the other party’s prior written consent. Nothing in this Agreement prevents either +party from participating in similar arrangements with third parties. + +15.3 No Assignment. NVIDIA may assign, delegate or transfer its rights or obligations under this +Agreement by any means or operation of law. You may not, without NVIDIA’s prior written consent, +assign, delegate or transfer any of your rights or obligations under this Agreement by any means or +operation of law, and any attempt to do so is null and void. + +15.4 No Waiver. No failure or delay by a party to enforce any term or obligation of this Agreement will +operate as a waiver by that party, or prevent the enforcement of such term or obligation later. + +15.5 Trade Compliance. You agree to comply with all applicable export, import, trade and economic +sanctions laws and regulations, as amended, including without limitation U.S. Export Administration +Regulations and Office of Foreign Assets Control regulations. You confirm (a) your understanding +that export or reexport of certain NVIDIA products or technologies may require a license or other +approval from appropriate authorities and (b) that you will not export or reexport any products or +technology, directly or indirectly, without first obtaining any required license or other approval from +appropriate authorities, (i) to any countries that are subject to any U.S. or local export restrictions +(currently including, but not necessarily limited to, Belarus, Cuba, Iran, North Korea, Russia, Syria, +the Region of Crimea, Donetsk People’s Republic Region and Luhansk People’s Republic Region); (ii) +to any end-user who you know or have reason to know will utilize them in the design, development +or production of nuclear, chemical or biological weapons, missiles, rocket systems, unmanned air +vehicles capable of a maximum range of at least 300 kilometers, regardless of payload, or intended +for military end-use, or any weapons of mass destruction; (iii) to any end-user who has been +prohibited from participating in the U.S. or local export transactions by any governing authority; or +(iv) to any known military or military-intelligence end-user or for any known military or military- +intelligence end-use in accordance with U.S. trade compliance laws and regulations. + +15.6 Government Rights. The Software, documentation and technology (“Protected Items”) are +“Commercial products” as this term is defined at 48 C.F.R. 2.101, consisting of “commercial +computer software” and “commercial computer software documentation” as such terms are used +in, respectively, 48 C.F.R. 12.212 and 48 C.F.R. 227.7202 & 252.227-7014(a)(1). Before any Protected +Items are supplied to the U.S. Government, you will (i) inform the U.S. Government in writing that +the Protected Items are and must be treated as commercial computer software and commercial +computer software documentation developed at private expense; (ii) inform the U.S. Government +that the Protected Items are provided subject to the terms of the Agreement; and (iii) mark the +Protected Items as commercial computer software and commercial computer software +documentation developed at private expense. In no event will you permit the U.S. Government to +acquire rights in Protected Items beyond those specified in 48 C.F.R. 52.227-19(b)(1)-(2) or 252.227- +7013(c) except as expressly approved by NVIDIA in writing. + +15.7 Notices. Please direct your legal notices or other correspondence to legalnotices@nvidia.com with a +copy mailed to NVIDIA Corporation, 2788 San Tomas Expressway, Santa Clara, California 95051, +United States of America, Attention: Legal Department. If NVIDIA needs to contact you about the +Software, you consent to receive the notices by email and agree that such notices will satisfy any +legal communication requirements. + +15.8 Severability. If a court of competent jurisdiction rules that a provision of this Agreement is +unenforceable, that provision will be deemed modified to the extent necessary to make it +enforceable and the remainder of this Agreement will continue in full force and effect. + +15.9 Amendment. Any amendment to this Agreement must be in writing and signed by authorized +representatives of both parties. + +15.10 Entire Agreement. Regarding the subject matter of this Agreement, the parties agree that (a) this +Agreement constitutes the entire and exclusive agreement between the parties and supersedes all +prior and contemporaneous communications and (b) any additional or different terms or conditions, +whether contained in purchase orders, order acknowledgments, invoices or otherwise, will not be +binding and are null and void. + +(v. February 25, 2025) + +NVIDIA Confidential \ No newline at end of file diff --git a/deps/cloudxr/simple/README.md b/deps/cloudxr/simple/README.md new file mode 100644 index 0000000..ae57bd0 --- /dev/null +++ b/deps/cloudxr/simple/README.md @@ -0,0 +1,118 @@ +# CloudXR.js Simple Example + +A minimal WebGL example demonstrating WebXR streaming from a CloudXR server to a web browser. This example shows how to integrate WebXR with CloudXR to stream immersive VR/AR content. + +> **Note:** This example is for learning purposes, not production use. + +## Quick Start + +### Prerequisites +- Node.js (v20 or higher) + +### Installation + +1. **Navigate to the example folder** + ```bash + cd simple + ``` + +2. **Install Dependencies** + ```bash + # For this early access release, please run the following to install SDK from the given tarball. This step will not be needed when SDK is publicly accessible. + npm install ../nvidia-cloudxr-6.0.0-beta.tgz + + npm install + ``` + +3. **Build the Application** + ```bash + npm run build + ``` + +4. **Start Development Server** + ```bash + npm run dev-server + ``` + +5. **Open in Browser** + - Navigate to `http://localhost:8080` (or the port shown in terminal) + - For desktop browsers, IWER (Immersive Web Emulator Runtime) will automatically load to emulate a Meta Quest 3 headset + +### Basic Usage + +1. **Configure Connection** + - Enter CloudXR server IP address (default: localhost) + - Set port (default: 49100) + - Select AR or VR mode + +2. **Adjust Settings (Optional)** + - Per-eye resolution (must be multiples of 16) + - Target frame rate (72, 90, or 120 FPS) + - Streaming bitrate + - XR reference space and camera offsets + +3. **Start Streaming** + - Click "CONNECT" + - Grant XR permissions when prompted + +**Requirements:** +- CloudXR server running and accessible +- WebXR-compatible device (VR/AR headset) or desktop browser (IWER loads automatically for emulation) + +## Architecture + +### CloudXRClient Class + +The main application class (`CloudXRClient` in `main.ts`) handles: + +**Initialization:** +- UI element management and localStorage persistence +- Browser capability checks (WebXR, WebGL2, WebRTC) +- Event listener setup + +**Connection Flow:** +1. **WebGL Setup** - Creates high-performance WebGL2 context +2. **WebXR Session** - Enters immersive VR/AR mode +3. **Reference Space** - Configures coordinate system for tracking +4. **CloudXR Session** - Establishes streaming connection to server +5. **Render Loop** - Sends tracking data, receives video, renders frames + +**Key Components:** +- **WebXR Session** - Hardware access (headset, controllers) +- **WebGL Context** - Video rendering +- **CloudXR Session** - Streaming management (WebRTC-based) +- **XRWebGLLayer** - Bridge between WebXR and WebGL + +## Project Structure + +``` +simple/ +├── src/ +│ └── main.ts # Main application sample +├── index.html # UI and form elements sample +├── package.json # Dependencies and scripts +├── webpack.common.js # Webpack base configuration sample +├── webpack.dev.js # Development configuration sample +├── webpack.prod.js # Production configuration sample +└── tsconfig.json # TypeScript configuration sample +``` + +## Code Overview + +The `main.ts` file contains well-commented code explaining each step: + +1. **Browser Checks** - Validates WebXR, WebGL2, and WebRTC support +2. **Connection Setup** - Reads form inputs and validates configuration +3. **WebGL Initialization** - Creates optimized rendering context +4. **WebXR Session** - Enters immersive mode with requested features +5. **CloudXR Setup** - Configures streaming session with event handlers +6. **Render Loop** - Runs 72-120 times per second: + - Sends tracking data to server + - Receives video frame + - Renders to display + +Each method includes inline comments explaining the purpose and key concepts. + +## License + +See the [LICENSE](LICENSE) file for details. diff --git a/deps/cloudxr/simple/favicon.ico b/deps/cloudxr/simple/favicon.ico new file mode 100644 index 0000000..a1de915 Binary files /dev/null and b/deps/cloudxr/simple/favicon.ico differ diff --git a/deps/cloudxr/simple/index.html b/deps/cloudxr/simple/index.html new file mode 100644 index 0000000..47aab55 --- /dev/null +++ b/deps/cloudxr/simple/index.html @@ -0,0 +1,593 @@ + + + + + + + + + NVIDIA CloudXR.js Sample Client + + + + + +
+
+

NVIDIA CloudXR.js Sample Client

+
+ +
+ + +
+

Debug Settings

+ +
+ + + +
+ +
+
+ +
+ + + +
+ Select the target device frame rate for the XR session +
+
+ +
+ + + +
+ Select the maximum streaming bitrate (in Megabits per second) for the XR session +
+
+ +
+ + + + + +
+ Configure the per-eye resolution. Width and height must be multiples of 16. +
+
+ +
+ + +
+ Select the preferred reference space for XR tracking. "Auto" uses the original fallback logic (local-floor → local → viewer). Other options will attempt to use the specified space only. +
+ + + + + + + +
+ Configure the XR reference space offset in centimeters. These values will be applied to the reference space transform to adjust the origin position of the XR experience. +
+
+ +
+ + +
+ Enable or disable secondary smoothing on predicted positions to reduce jitter. This only affects position, not orientation. +
+
+ +
+ + +
+ Scale the pose prediction horizon (0.0 = no prediction, 1.0 = full prediction). This multiplier affects both position and orientation prediction strength. +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/deps/cloudxr/simple/package.json b/deps/cloudxr/simple/package.json new file mode 100644 index 0000000..3cd7e8f --- /dev/null +++ b/deps/cloudxr/simple/package.json @@ -0,0 +1,31 @@ +{ + "name": "cloudxr-simple-example", + "version": "6.0.0-beta", + "private": true, + "description": "CloudXR.js WebGL example application", + "author": "NVIDIA Corporation", + "license": "SEE LICENSE IN LICENSE", + "keywords": [], + "scripts": { + "build": "webpack --config ./webpack.prod.js", + "dev": "webpack --config ./webpack.dev.js", + "dev-server": "webpack serve --config ./webpack.dev.js --no-open", + "dev-server:https": "HTTPS=true webpack serve --config ./webpack.dev.js --no-open", + "clean": "rimraf build" + }, + "dependencies": { + "@nvidia/cloudxr": "dev" + }, + "devDependencies": { + "@types/node": "^22.13.14", + "@types/webxr": "^0.5.22", + "copy-webpack-plugin": "^13.0.0", + "gl-matrix": "^3.4.3", + "html-webpack-plugin": "^5.6.3", + "rimraf": "^5.0.5", + "ts-loader": "^9.5.1", + "typescript": "^5.8.2", + "webpack-dev-server": "^5.2.1", + "webpack-cli": "^6.0.1" + } +} \ No newline at end of file diff --git a/deps/cloudxr/simple/src/main.ts b/deps/cloudxr/simple/src/main.ts new file mode 100644 index 0000000..84505d5 --- /dev/null +++ b/deps/cloudxr/simple/src/main.ts @@ -0,0 +1,515 @@ +/** + * CloudXR.js Simple Example - WebXR Streaming Application + * + * CloudXR streams XR content from a powerful server to lightweight clients (think Netflix for VR/AR). + * Server does the heavy rendering, client displays video and sends back tracking data. + * + * Key Flow: + * 1. constructor() - Initialize UI and check browser support + * 2. connectToCloudXR() - Connect to server (called on CONNECT button click) + * 3. initializeWebGL() - Set up graphics rendering + * 4. createXRSession() - Enter VR/AR mode + * 5. createCloudXRSession() - Configure CloudXR streaming + * 6. onXRFrame() - Render loop: send tracking, receive & display video frames + */ + +import { checkCapabilities } from '@helpers/BrowserCapabilities'; +import { loadIWERIfNeeded } from '@helpers/LoadIWER'; +import { overridePressureObserver } from '@helpers/overridePressureObserver'; +import { + enableLocalStorage, + getConnectionConfig, + setupCertificateAcceptanceLink, +} from '@helpers/utils'; +import { getOrCreateCanvas, logOrThrow } from '@helpers/WebGlUtils'; +import * as CloudXR from '@nvidia/cloudxr'; + +// Override PressureObserver early to catch errors from buggy browser implementations +overridePressureObserver(); + +/** + * CloudXR Client - Main Application Class + * + * Architecture: WebXR (hardware access) + WebGL (rendering) + CloudXR (streaming) + */ +class CloudXRClient { + // UI Elements - Form inputs and display elements + private startButton: HTMLButtonElement; + private exitButton: HTMLButtonElement; + private serverIpInput: HTMLInputElement; + private portInput: HTMLInputElement; + private proxyUrlInput: HTMLInputElement; + private immersiveSelect: HTMLSelectElement; + private deviceFrameRateSelect: HTMLSelectElement; + private maxStreamingBitrateMbpsSelect: HTMLSelectElement; + private proxyDefaultText: HTMLElement; + private statusMessageBox: HTMLElement; + private statusMessageText: HTMLElement; + private perEyeWidthInput: HTMLInputElement; + private perEyeHeightInput: HTMLInputElement; + private referenceSpaceSelect: HTMLSelectElement; + private xrOffsetXInput: HTMLInputElement; + private xrOffsetYInput: HTMLInputElement; + private xrOffsetZInput: HTMLInputElement; + private certAcceptanceLink: HTMLElement; + private certLink: HTMLAnchorElement; + private enablePoseSmoothingSelect: HTMLSelectElement; + private posePredictionFactorInput: HTMLInputElement; + private posePredictionFactorValue: HTMLElement; + + // Core Session Components + private xrSession: XRSession | null = null; // WebXR session (hardware access) + private cloudxrSession: CloudXR.Session | null = null; // CloudXR session (streaming) + private gl: WebGL2RenderingContext | null = null; // WebGL context (rendering) + private baseLayer: XRWebGLLayer | null = null; // Bridge between WebXR and WebGL + private deviceFrameRate: number = 0; // Target frame rate for XR session + private hasSetTargetFrameRate: boolean = false; // Track if we've set target frame rate + + /** + * Initialize UI, enable localStorage, and check WebXR support + */ + constructor() { + // Get references to all UI elements + this.startButton = document.getElementById('startButton') as HTMLButtonElement; + this.exitButton = document.getElementById('exitButton') as HTMLButtonElement; + this.serverIpInput = document.getElementById('serverIpInput') as HTMLInputElement; + this.portInput = document.getElementById('portInput') as HTMLInputElement; + this.proxyUrlInput = document.getElementById('proxyUrl') as HTMLInputElement; + this.immersiveSelect = document.getElementById('immersive') as HTMLSelectElement; + this.deviceFrameRateSelect = document.getElementById('deviceFrameRate') as HTMLSelectElement; + this.maxStreamingBitrateMbpsSelect = document.getElementById( + 'maxStreamingBitrateMbps' + ) as HTMLSelectElement; + this.proxyDefaultText = document.getElementById('proxyDefaultText') as HTMLElement; + this.statusMessageBox = document.getElementById('statusMessageBox') as HTMLElement; + this.statusMessageText = document.getElementById('statusMessageText') as HTMLElement; + this.perEyeWidthInput = document.getElementById('perEyeWidth') as HTMLInputElement; + this.perEyeHeightInput = document.getElementById('perEyeHeight') as HTMLInputElement; + this.referenceSpaceSelect = document.getElementById('referenceSpace') as HTMLSelectElement; + this.xrOffsetXInput = document.getElementById('xrOffsetX') as HTMLInputElement; + this.xrOffsetYInput = document.getElementById('xrOffsetY') as HTMLInputElement; + this.xrOffsetZInput = document.getElementById('xrOffsetZ') as HTMLInputElement; + this.certAcceptanceLink = document.getElementById('certAcceptanceLink') as HTMLElement; + this.certLink = document.getElementById('certLink') as HTMLAnchorElement; + this.enablePoseSmoothingSelect = document.getElementById( + 'enablePoseSmoothing' + ) as HTMLSelectElement; + this.posePredictionFactorInput = document.getElementById( + 'posePredictionFactor' + ) as HTMLInputElement; + this.posePredictionFactorValue = document.getElementById( + 'posePredictionFactorValue' + ) as HTMLElement; + + // Enable localStorage to persist user settings + enableLocalStorage(this.serverIpInput, 'serverIp'); + enableLocalStorage(this.portInput, 'port'); + enableLocalStorage(this.proxyUrlInput, 'proxyUrl'); + enableLocalStorage(this.immersiveSelect, 'immersiveMode'); + enableLocalStorage(this.deviceFrameRateSelect, 'deviceFrameRate'); + enableLocalStorage(this.maxStreamingBitrateMbpsSelect, 'maxStreamingBitrateMbps'); + enableLocalStorage(this.perEyeWidthInput, 'perEyeWidth'); + enableLocalStorage(this.perEyeHeightInput, 'perEyeHeight'); + enableLocalStorage(this.referenceSpaceSelect, 'referenceSpace'); + enableLocalStorage(this.xrOffsetXInput, 'xrOffsetX'); + enableLocalStorage(this.xrOffsetYInput, 'xrOffsetY'); + enableLocalStorage(this.xrOffsetZInput, 'xrOffsetZ'); + enableLocalStorage(this.enablePoseSmoothingSelect, 'enablePoseSmoothing'); + enableLocalStorage(this.posePredictionFactorInput, 'posePredictionFactor'); + + // Update slider value display when it changes + this.posePredictionFactorInput.addEventListener('input', () => { + this.posePredictionFactorValue.textContent = this.posePredictionFactorInput.value; + }); + // Set initial display value + this.posePredictionFactorValue.textContent = this.posePredictionFactorInput.value; + + // Configure proxy information and port placeholder based on protocol + if (window.location.protocol === 'https:') { + this.proxyDefaultText.textContent = + 'Optional: Leave empty for direct WSS connection, or provide URL for proxy routing (e.g., https://proxy.example.com/)'; + this.portInput.placeholder = 'Port (default: 48322, or 443 if proxy URL set)'; + } else { + this.proxyDefaultText.textContent = 'Not needed for HTTP - uses direct WS connection'; + this.portInput.placeholder = 'Port (default: 49100)'; + } + + this.startButton.addEventListener('click', () => this.connectToCloudXR()); + this.exitButton.addEventListener('click', () => this.xrSession?.end()); + + // Set up certificate acceptance link + setupCertificateAcceptanceLink( + this.serverIpInput, + this.portInput, + this.proxyUrlInput, + this.certAcceptanceLink, + this.certLink + ); + + this.checkWebXRSupport(); + } + + /** + * Check browser support: WebXR, WebGL2, WebRTC, and video frame callbacks + * Also loads Immersive Web Emulator if needed (for desktop development) + */ + private async checkWebXRSupport(): Promise { + const { supportsImmersive, iwerLoaded } = await loadIWERIfNeeded(); + if (!supportsImmersive) { + this.showStatus('Immersive mode not supported', 'error'); + this.startButton.disabled = true; + return; + } + + this.startButton.disabled = true; + this.startButton.innerHTML = 'CONNECT (checking capabilities)'; + + const result = await checkCapabilities(); + if (!result.success) { + this.showStatus( + 'Browser does not meet required capabilities:\n' + result.failures.join('\n'), + 'error' + ); + this.startButton.innerHTML = 'CONNECT (capabilities check failed)'; + return; + } + + if (result.warnings.length > 0) { + this.showStatus('Performance notice:\n' + result.warnings.join('\n'), 'info'); + } else if (iwerLoaded) { + // Include IWER status in the final success message + this.showStatus( + 'CloudXR.js SDK is supported. Ready to connect!\nUsing IWER (Immersive Web Emulator Runtime) - Emulating Meta Quest 3.', + 'info' + ); + } else { + this.showStatus('CloudXR.js SDK is supported. Ready to connect!', 'success'); + } + + this.startButton.disabled = false; + this.startButton.innerHTML = 'CONNECT'; + } + + private showStatus(message: string, type: 'success' | 'error' | 'info'): void { + this.statusMessageText.textContent = message; + this.statusMessageBox.className = `status-message-box show ${type}`; + console[type === 'error' ? 'error' : 'info'](message); + } + + /** + * Main connection flow - orchestrates WebGL, WebXR, and CloudXR setup + * Steps: Read config → Initialize WebGL → Create XR session → Connect to CloudXR server + */ + private async connectToCloudXR(): Promise { + // Read configuration from UI form + const serverIp = this.serverIpInput.value.trim() || 'localhost'; + + // Determine default port based on connection type and proxy usage + const useSecureConnection = window.location.protocol === 'https:'; + const portValue = parseInt(this.portInput.value, 10); + const proxyUrl = this.proxyUrlInput.value; + const hasProxy = proxyUrl.trim().length > 0; + + let defaultPort = 49100; // HTTP default (direct CloudXR Runtime connection) + if (useSecureConnection) { + defaultPort = hasProxy ? 443 : 48322; // HTTPS with proxy → 443, HTTPS without → 48322 + } + + const port = portValue || defaultPort; + const perEyeWidth = parseInt(this.perEyeWidthInput.value, 10) || 2048; + const perEyeHeight = parseInt(this.perEyeHeightInput.value, 10) || 1792; + const deviceFrameRate = parseInt(this.deviceFrameRateSelect.value, 10); + const maxStreamingBitrateKbps = parseInt(this.maxStreamingBitrateMbpsSelect.value, 10) * 1000; + const immersiveMode = this.immersiveSelect.value as 'ar' | 'vr'; + const referenceSpaceType = this.referenceSpaceSelect.value as XRReferenceSpaceType; + const xrOffsetX = (parseFloat(this.xrOffsetXInput.value) || 0) / 100; // cm to meters + const xrOffsetY = (parseFloat(this.xrOffsetYInput.value) || 0) / 100; + const xrOffsetZ = (parseFloat(this.xrOffsetZInput.value) || 0) / 100; + + try { + this.startButton.disabled = true; + this.startButton.innerHTML = 'CONNECT (connecting)'; + this.showStatus(`Connecting to Server ${serverIp}:${port}...`, 'info'); + + // Initialize WebGL, WebXR session, and reference space + await this.initializeWebGL(); + await this.createXRSession(immersiveMode, deviceFrameRate); + + let referenceSpace = await this.getReferenceSpace(referenceSpaceType); + if (xrOffsetX !== 0 || xrOffsetY !== 0 || xrOffsetZ !== 0) { + const offsetTransform = new XRRigidTransform( + { x: xrOffsetX, y: xrOffsetY, z: xrOffsetZ }, + { x: 0, y: 0, z: 0, w: 1 } + ); + referenceSpace = referenceSpace.getOffsetReferenceSpace(offsetTransform); + } + + // Create CloudXR session and connect to server + await this.createCloudXRSession( + serverIp, + port, + proxyUrl, + perEyeWidth, + perEyeHeight, + maxStreamingBitrateKbps, + referenceSpace + ); + + this.cloudxrSession!.connect(); + this.startButton.innerHTML = 'CONNECT (waiting for streaming)'; + this.showStatus(`Connected to Server ${serverIp}:${port}...`, 'info'); + } catch (error) { + this.showStatus(`Connection failed: ${error}`, 'error'); + this.startButton.disabled = false; + this.startButton.innerHTML = 'CONNECT'; + + if (this.xrSession) { + try { + await this.xrSession.end(); + } catch (endError) { + console.error('Error ending XR session during cleanup:', endError); + this.clearSessionReferences(); + } + } else { + this.clearSessionReferences(); + } + } + } + + /** + * Initialize WebGL2 context for rendering (high-performance, XR-compatible) + */ + private async initializeWebGL(): Promise { + const webglCanvas = getOrCreateCanvas('webglCanvas'); + const gl = webglCanvas.getContext('webgl2', { + alpha: true, + depth: true, + stencil: false, + desynchronized: false, + antialias: false, // No antialiasing (video already rendered) + failIfMajorPerformanceCaveat: true, + powerPreference: 'high-performance', // Use discrete GPU if available + premultipliedAlpha: false, + preserveDrawingBuffer: false, + }) as WebGL2RenderingContext; + + if (!gl) throw new Error('Failed to create WebGL2 context'); + + await gl.makeXRCompatible(); // Required before using with XRWebGLLayer + this.gl = gl; + logOrThrow('Creating WebGL context', this.gl); + } + + /** + * Create WebXR session, XRWebGLLayer, and start render loop + */ + private async createXRSession( + immersiveMode: 'ar' | 'vr', + deviceFrameRate: number + ): Promise { + const mode = immersiveMode === 'vr' ? 'immersive-vr' : 'immersive-ar'; + const options = { + requiredFeatures: ['local-floor'], + optionalFeatures: ['hand-tracking', 'high-fixed-foveation-level'], + }; + + // Try requested mode, fallback to alternative if unsupported + try { + this.xrSession = await navigator.xr!.requestSession(mode, options); + } catch (error) { + console.warn(`${mode} session failed, trying alternative:`, error); + const altMode = immersiveMode === 'vr' ? 'immersive-ar' : 'immersive-vr'; + this.xrSession = await navigator.xr!.requestSession(altMode, options); + } + + // Create XRWebGLLayer - provides framebuffer for CloudXR to render into + this.baseLayer = new XRWebGLLayer(this.xrSession, this.gl!, { + alpha: true, + antialias: false, + depth: true, + framebufferScaleFactor: 1.2, + ignoreDepthValues: false, + stencil: false, + }); + + // Store frame rate for later use in render loop + this.deviceFrameRate = deviceFrameRate; + this.hasSetTargetFrameRate = false; + + this.xrSession.updateRenderState({ baseLayer: this.baseLayer }); + this.xrSession.addEventListener('end', () => this.handleXRSessionEnd()); + this.xrSession.requestAnimationFrame(this.onXRFrame.bind(this)); + } + + /** + * Get XR reference space with fallbacks + * Reference space types: 'local-floor' (room-scale), 'local' (seated), 'viewer' (head-locked) + */ + private async getReferenceSpace( + referenceSpaceType: XRReferenceSpaceType + ): Promise { + try { + return await this.xrSession!.requestReferenceSpace(referenceSpaceType); + } catch (error) { + console.warn(`'${referenceSpaceType}' not supported, trying fallbacks...`); + try { + return await this.xrSession!.requestReferenceSpace('local-floor'); + } catch { + try { + return await this.xrSession!.requestReferenceSpace('local'); + } catch { + return await this.xrSession!.requestReferenceSpace('viewer'); + } + } + } + } + + /** + * Configure CloudXR session and set up event handlers + * Establishes WebRTC connection, receives video stream, sends tracking data + */ + private async createCloudXRSession( + serverIp: string, + port: number, + proxyUrl: string, + perEyeWidth: number, + perEyeHeight: number, + maxStreamingBitrateKbps: number, + referenceSpace: XRReferenceSpace + ): Promise { + const connectionConfig = getConnectionConfig(serverIp, port, proxyUrl); + + const sessionOptions: CloudXR.SessionOptions = { + serverAddress: connectionConfig.serverIP, + serverPort: connectionConfig.port, + useSecureConnection: connectionConfig.useSecureConnection, + gl: this.gl!, + perEyeWidth, // Stream resolution: width = perEyeWidth * 2 (side-by-side) + perEyeHeight, // Stream resolution: height = perEyeHeight * 9/4 (includes metadata) + referenceSpace, + deviceFrameRate: parseInt(this.deviceFrameRateSelect.value, 10), + maxStreamingBitrateKbps, + enablePoseSmoothing: this.enablePoseSmoothingSelect.value === 'true', + posePredictionFactor: parseFloat(this.posePredictionFactorInput.value), + telemetry: { + enabled: true, + appInfo: { version: '6.0.0-beta', product: 'CloudXR.js WebGL Example' }, + }, + }; + + const delegates: CloudXR.SessionDelegates = { + onStreamStarted: () => { + console.log('CloudXR stream started'); + this.startButton.innerHTML = 'CONNECT (streaming)'; + this.exitButton.style.display = 'block'; + this.showStatus('Streaming started!', 'success'); + }, + onStreamStopped: (error?: Error) => { + if (error) { + console.error('Stream stopped with error:', error); + this.showStatus(`Stream stopped: ${error}`, 'error'); + } else { + console.log('Stream stopped normally'); + this.showStatus('Stream stopped', 'info'); + } + + if (this.xrSession) { + this.xrSession + .end() + .catch(endError => console.error('Error ending XR session:', endError)) + .finally(() => (this.exitButton.style.display = 'none')); + } else { + this.exitButton.style.display = 'none'; + } + + this.startButton.disabled = false; + this.startButton.innerHTML = 'CONNECT'; + }, + onWebGLStateChangeBegin: () => console.debug('WebGL state change begin'), + onWebGLStateChangeEnd: () => console.debug('WebGL state change end'), + onServerMessageReceived: (messageData: Uint8Array) => { + const messageString = new TextDecoder().decode(messageData); + console.debug('Server message:', messageString); + }, + }; + + try { + this.cloudxrSession = CloudXR.createSession(sessionOptions, delegates); + } catch (error) { + console.error('Failed to create CloudXR session:', error); + throw error; + } + } + + /** + * Main render loop - runs every frame (72-120 FPS) + * Sends tracking data to server, receives video frame, renders to display + */ + private async onXRFrame(timestamp: DOMHighResTimeStamp, frame: XRFrame): Promise { + this.xrSession!.requestAnimationFrame(this.onXRFrame.bind(this)); + + // Set target frame rate on first frame only + if (!this.hasSetTargetFrameRate && 'updateTargetFrameRate' in this.xrSession!) { + this.hasSetTargetFrameRate = true; + try { + await this.xrSession!.updateTargetFrameRate(this.deviceFrameRate); + console.debug( + `Target frame rate set to ${this.deviceFrameRate}, current: ${this.xrSession!.frameRate}` + ); + } catch (error) { + console.error('Failed to set target frame rate:', error); + } + } + + if (!this.cloudxrSession) { + console.debug('Skipping frame, CloudXR session not created yet'); + return; + } + + if (this.cloudxrSession.state !== CloudXR.SessionState.Connected) { + console.debug('Skipping frame, session not ready'); + return; + } + + try { + // Send tracking (head/hand positions) → Receive video → Render + this.cloudxrSession.sendTrackingStateToServer(timestamp, frame); + this.gl!.bindFramebuffer(this.gl!.FRAMEBUFFER, this.baseLayer!.framebuffer); + this.cloudxrSession.render(timestamp, frame, this.baseLayer!); + } catch (error) { + console.error('Error in render frame:', error); + } + } + + /** + * Cleanup when XR session ends (user exits, removes headset, or error occurs) + */ + private handleXRSessionEnd(): void { + try { + if (this.cloudxrSession) { + this.cloudxrSession.disconnect(); + this.cloudxrSession = null; + } + + this.clearSessionReferences(); + + this.startButton.disabled = false; + this.startButton.innerHTML = 'CONNECT'; + this.exitButton.style.display = 'none'; + } catch (error) { + this.showStatus(`Disconnect error: ${error}`, 'error'); + } + } + + private clearSessionReferences(): void { + this.baseLayer = null; + this.xrSession = null; + this.gl = null; + this.hasSetTargetFrameRate = false; + } +} + +// Application entry point - wait for DOM to load, then initialize client +document.addEventListener('DOMContentLoaded', () => { + new CloudXRClient(); +}); diff --git a/deps/cloudxr/simple/tsconfig.json b/deps/cloudxr/simple/tsconfig.json new file mode 100644 index 0000000..ec529e3 --- /dev/null +++ b/deps/cloudxr/simple/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES6", + "moduleResolution": "bundler", + "strict": true, + "lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"], + "skipLibCheck": true, + "types": ["webxr"], + "baseUrl": ".", + "paths": { + "@helpers/*": ["../helpers/*"] + } + }, + "include": ["**/*.ts", "types/**/*.d.ts"] +} diff --git a/deps/cloudxr/simple/webpack.common.js b/deps/cloudxr/simple/webpack.common.js new file mode 100644 index 0000000..8844eb9 --- /dev/null +++ b/deps/cloudxr/simple/webpack.common.js @@ -0,0 +1,41 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + entry: './src/main.ts', + + // Module rules define how different file types are processed + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + + // Resolve configuration for module resolution + resolve: { + extensions: ['.ts', '.js'], + alias: { + // @helpers can be used instead of relative paths to the helpers directory + '@helpers': path.resolve(__dirname, '../helpers') + } + }, + + // Output configuration for bundled files + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, './build'), + }, + + // Webpack plugins that extend webpack's functionality + plugins: [ + // Generates HTML file and automatically injects bundled JavaScript + new HtmlWebpackPlugin({ + template: './index.html', + favicon: './favicon.ico' + }) + ] +}; \ No newline at end of file diff --git a/deps/cloudxr/simple/webpack.dev.js b/deps/cloudxr/simple/webpack.dev.js new file mode 100644 index 0000000..4a8c508 --- /dev/null +++ b/deps/cloudxr/simple/webpack.dev.js @@ -0,0 +1,42 @@ +const { merge } = require('webpack-merge') +const common = require('./webpack.common.js') +const path = require('path') + +// Check if HTTPS mode is enabled via environment variable +const useHttps = process.env.HTTPS === 'true' + +module.exports = merge(common, { + mode: 'development', + devtool: 'eval-source-map', + devServer: { + allowedHosts: 'all', + hot: true, + open: false, + port: 8080, + // Enable HTTPS with self-signed certificate when HTTPS=true + ...(useHttps && { server: 'https' }), + static: { + directory: path.join(__dirname, './build'), + watch: true, + }, + watchFiles: { + paths: ['src/**/*', '../../build/**/*'], + options: { + usePolling: false, + ignored: /node_modules/, + }, + }, + client: { + progress: true, + overlay: { + errors: true, + warnings: false, + }, + }, + devMiddleware: { + writeToDisk: true, + }, + }, +}) + +console.log(module.exports); \ No newline at end of file diff --git a/deps/cloudxr/simple/webpack.prod.js b/deps/cloudxr/simple/webpack.prod.js new file mode 100644 index 0000000..0668927 --- /dev/null +++ b/deps/cloudxr/simple/webpack.prod.js @@ -0,0 +1,8 @@ +const { merge } = require('webpack-merge'); +const common = require('./webpack.common.js'); + +module.exports = merge(common, { + mode: 'production' +}); + +console.log(module.exports); \ No newline at end of file diff --git a/scripts/environments/teleoperation/teleop_xr_agent.py b/scripts/environments/teleoperation/teleop_xr_agent.py index 4f4c2dc..87d5276 100644 --- a/scripts/environments/teleoperation/teleop_xr_agent.py +++ b/scripts/environments/teleoperation/teleop_xr_agent.py @@ -44,11 +44,18 @@ parser.add_argument("--stream-to", type=str, default=None, dest="stream_to") parser.add_argument("--stream-port", type=int, default=12345, dest="stream_port") parser.add_argument("--stream-bitrate", type=int, default=20_000_000, dest="stream_bitrate") parser.add_argument("--debug-viewports", action="store_true", dest="debug_viewports", default=True) +parser.add_argument( + "--cloudxr", action="store_true", dest="cloudxr", default=False, + help="Enable CloudXR streaming mode (replaces --stream-to). " + "Requires CloudXR Runtime Docker container running on this workstation.", +) AppLauncher.add_app_launcher_args(parser) args_cli = parser.parse_args() app_launcher_args = vars(args_cli) -app_launcher_args["xr"] = False +# Enable Isaac Sim XR extensions when using CloudXR; disable for normal streaming +app_launcher_args["xr"] = args_cli.cloudxr +app_launcher_args["enable_cameras"] = not args_cli.cloudxr # CloudXR renders its own stereo views app_launcher = AppLauncher(app_launcher_args) simulation_app = app_launcher.app @@ -93,7 +100,8 @@ def main() -> None: pos_sensitivity=pos_sens, rot_sensitivity=rot_sens, base_speed=args.base_speed, base_turn=args.base_turn, drive_speed=args.drive_speed, drive_turn=args.drive_turn, - stream_to=args.stream_to, stream_port=args.stream_port, + stream_to=None if args.cloudxr else args.stream_to, + stream_port=args.stream_port, stream_bitrate=args.stream_bitrate, debug_viewports=args.debug_viewports or bool(args.stream_to), )