diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b485d8d4ee4ec0f9c34879b437253b5d5dac65d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/build/ +/dist/ +/generated/ +/models/ +/fixtures/ +/.cache/ +/.direnv/ +/result +compile_commands.json +__pycache__/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..f68790b8911c2e6aa7276a198b50c845fce283d2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ggml"] + path = ggml + url = https://github.com/ggml-org/ggml.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..10c981350f9c123294500cf8dc855dbed32af7c8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,141 @@ +cmake_minimum_required(VERSION 3.25) +project(motionbricks VERSION 0.1.0 LANGUAGES C CXX) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +include(GNUInstallDirs) + +option(MOTIONBRICKS_BUILD_TESTS "Build motion-bricks.cpp tests" ON) +option(MOTIONBRICKS_ENABLE_GGML "Use the pinned GGML submodule when present" ON) +option(MOTIONBRICKS_ENABLE_VULKAN "Enable the GGML Vulkan backend" ON) +option(MOTIONBRICKS_ENABLE_FUZZERS "Build libFuzzer targets" OFF) +option(MOTIONBRICKS_SANITIZE "Enable AddressSanitizer and UndefinedBehaviorSanitizer" OFF) +option(MOTIONBRICKS_DOWNLOAD_MODELS "Download and verify the published runtime model/style bundle" ON) +set(MOTIONBRICKS_GGML_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ggml" CACHE PATH + "Pinned GGML submodule source directory") +set(MOTIONBRICKS_MODEL_REPOSITORY "LocalAI-io/MotionBricks-G1-GGML" CACHE STRING + "Hugging Face repository containing the runtime bundle") +set(MOTIONBRICKS_MODEL_REVISION "cc2a47603dbc203a4f18f35dd06ed3611833f506" CACHE STRING + "Pinned Hugging Face revision containing the runtime bundle") +set(MOTIONBRICKS_REFERENCE_BUNDLE "${CMAKE_CURRENT_SOURCE_DIR}/generated/g1-f32" CACHE PATH + "Downloaded or locally converted MotionBricks runtime bundle") +set(MOTIONBRICKS_REFERENCE_STYLES "${CMAKE_CURRENT_SOURCE_DIR}/generated/styles" CACHE PATH + "Downloaded or locally converted MotionBricks style bundle") + +if(MOTIONBRICKS_ENABLE_GGML AND MOTIONBRICKS_DOWNLOAD_MODELS) + find_package(Python3 COMPONENTS Interpreter REQUIRED) + execute_process( + COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/download_gguf_weights.py" + --output "${CMAKE_CURRENT_SOURCE_DIR}/generated" + --repo "${MOTIONBRICKS_MODEL_REPOSITORY}" + --revision "${MOTIONBRICKS_MODEL_REVISION}" + RESULT_VARIABLE MOTIONBRICKS_MODEL_DOWNLOAD_RESULT + COMMAND_ECHO STDOUT) + if(NOT MOTIONBRICKS_MODEL_DOWNLOAD_RESULT EQUAL 0) + message(FATAL_ERROR + "MotionBricks model download/verification failed. Set " + "MOTIONBRICKS_DOWNLOAD_MODELS=OFF for a source-only build.") + endif() +endif() + +set(MOTIONBRICKS_SOURCES + src/capi.cpp + src/decoder.cpp + src/error.cpp + src/model.cpp + src/neural_runtime.cpp + src/pose.cpp) +list(APPEND MOTIONBRICKS_SOURCES src/root.cpp) +list(APPEND MOTIONBRICKS_SOURCES src/motion_rep.cpp) +list(APPEND MOTIONBRICKS_SOURCES src/planner.cpp) +list(APPEND MOTIONBRICKS_SOURCES src/style.cpp) +list(APPEND MOTIONBRICKS_SOURCES src/agent.cpp) + +add_library(motionbricks_shared SHARED ${MOTIONBRICKS_SOURCES}) +add_library(motionbricks::motionbricks ALIAS motionbricks_shared) +set_target_properties(motionbricks_shared PROPERTIES + OUTPUT_NAME motionbricks + EXPORT_NAME motionbricks + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN YES + VERSION ${PROJECT_VERSION} + SOVERSION 0) +target_compile_definitions(motionbricks_shared PRIVATE MOTIONBRICKS_BUILD MOTIONBRICKS_SHARED) + +add_library(motionbricks_static STATIC ${MOTIONBRICKS_SOURCES}) +add_library(motionbricks::motionbricks_static ALIAS motionbricks_static) +set_target_properties(motionbricks_static PROPERTIES + OUTPUT_NAME motionbricks + EXPORT_NAME motionbricks_static) +target_compile_definitions(motionbricks_static PRIVATE MOTIONBRICKS_BUILD) + +foreach(target motionbricks_shared motionbricks_static) + target_include_directories(${target} + PUBLIC + "$" + "$" + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") + if(MSVC) + target_compile_options(${target} PRIVATE /W4 /permissive-) + else() + target_compile_options(${target} PRIVATE + -Wall -Wextra -Wpedantic -Wconversion -Wshadow) + endif() +endforeach() + +if(MOTIONBRICKS_ENABLE_GGML) + if(EXISTS "${MOTIONBRICKS_GGML_SOURCE_DIR}/CMakeLists.txt") + set(GGML_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set(GGML_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(GGML_NATIVE OFF CACHE BOOL "" FORCE) + set(GGML_VULKAN ${MOTIONBRICKS_ENABLE_VULKAN} CACHE BOOL "" FORCE) + set(GGML_BACKEND_DL OFF CACHE BOOL "" FORCE) + add_subdirectory("${MOTIONBRICKS_GGML_SOURCE_DIR}" + "${CMAKE_BINARY_DIR}/ggml" EXCLUDE_FROM_ALL) + # GGML is currently an internal build-tree dependency. Its installed + # runtime/package contract will be added with the first neural graph. + target_link_libraries(motionbricks_shared PRIVATE "$") + target_link_libraries(motionbricks_static PRIVATE "$") + target_compile_definitions(motionbricks_shared PRIVATE MOTIONBRICKS_HAVE_GGML=1) + target_compile_definitions(motionbricks_static PRIVATE MOTIONBRICKS_HAVE_GGML=1) + if(MOTIONBRICKS_ENABLE_VULKAN AND TARGET ggml-vulkan) + target_link_libraries(motionbricks_shared PRIVATE "$") + target_link_libraries(motionbricks_static PRIVATE "$") + target_compile_definitions(motionbricks_shared PRIVATE MOTIONBRICKS_HAVE_VULKAN=1) + target_compile_definitions(motionbricks_static PRIVATE MOTIONBRICKS_HAVE_VULKAN=1) + endif() + else() + message(STATUS "GGML submodule not present; building the ABI scaffold only") + endif() +endif() + +if(MOTIONBRICKS_SANITIZE) + if(MSVC) + message(FATAL_ERROR "MOTIONBRICKS_SANITIZE currently requires Clang or GCC") + endif() + foreach(target motionbricks_shared motionbricks_static) + target_compile_options(${target} PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer) + target_link_options(${target} PRIVATE -fsanitize=address,undefined) + endforeach() +endif() + +add_executable(motionbricks-cli src/cli.cpp) +target_link_libraries(motionbricks-cli PRIVATE motionbricks::motionbricks) +set_target_properties(motionbricks-cli PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +include(CTest) +if(BUILD_TESTING AND MOTIONBRICKS_BUILD_TESTS) + add_subdirectory(tests) +endif() + +install(TARGETS motionbricks_shared motionbricks_static motionbricks-cli + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000000000000000000000000000000000000..0564d8ea51ed11cab93a83ce016ee91617ed288f --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,50 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" + } + }, + { + "name": "release", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "asan-ubsan", + "inherits": "debug", + "binaryDir": "${sourceDir}/build/asan-ubsan", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", + "MOTIONBRICKS_ENABLE_VULKAN": "OFF", + "MOTIONBRICKS_SANITIZE": "ON" + } + } + ], + "buildPresets": [ + {"name": "debug", "configurePreset": "debug"}, + {"name": "release", "configurePreset": "release"}, + {"name": "asan-ubsan", "configurePreset": "asan-ubsan"} + ], + "testPresets": [ + { + "name": "debug", + "configurePreset": "debug", + "output": {"outputOnFailure": true} + }, + { + "name": "asan-ubsan", + "configurePreset": "asan-ubsan", + "output": {"outputOnFailure": true} + } + ] +} diff --git a/FORK_PROVENANCE.md b/FORK_PROVENANCE.md new file mode 100644 index 0000000000000000000000000000000000000000..830f553dea42528c7997acb85294c51743ec3c42 --- /dev/null +++ b/FORK_PROVENANCE.md @@ -0,0 +1,11 @@ +# Fork provenance + +- **Upstream:** `localai-org/motion-bricks.cpp (github)` +- **Upstream commit / SHA:** `6fdb75e15ddb7f97dd1a4abb8017a57b936bc7a3` +- **License:** Apache-2.0 +- **Kind:** Code mirror +- **Workspace consumer:** MotionBricks-G1-GGML (converter/inference) +- **Forked to:** `chibifire/motion-bricks.cpp` +- **Forked on:** 2026-09-05 +- **Author:** Ernest Lee +- **Reason:** shipping-surface completeness under our own HF org so a base/source we depend on cannot disappear or relicense out from under us diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3f966888f5a1dd82135bf5d9e907a8910e2f5ab8 --- /dev/null +++ b/README.md @@ -0,0 +1,161 @@ +# motion-bricks.cpp + +A C++23/GGML port of NVIDIA MotionBricks for CPU and Vulkan inference, with a +stable C ABI suitable for PureGo. + +The released batch-one G1 inference path is implemented end to end: strict +GGUF loading, root/duration planning, pose-token prediction, VQ decoding, +418/414/413 feature conversion, style alignment, and skeletal animation +output. CPU and Vulkan use the same public API and preserve the same duration +and pose-token decisions in the reference suite. + +## Build + +The normal build uses CMake and does not depend on Nix: + +```sh +cmake --preset debug +cmake --build --preset debug +ctest --preset debug +``` + +Configuration downloads and SHA-256-verifies the published 0.73 GB G1 F32 +GGUF and style bundles into `generated/` when they are not already present. +The same operation can be run explicitly: + +```sh +python scripts/download_gguf_weights.py +``` + +For an offline or source-only build, preserve an existing local bundle or use +`cmake --preset debug -DMOTIONBRICKS_DOWNLOAD_MODELS=OFF`. The repository and +revision are configurable with `MOTIONBRICKS_MODEL_REPOSITORY` and +`MOTIONBRICKS_MODEL_REVISION`. + +On NixOS, enter the reproducible development shell first: + +```sh +nix develop +cmake --preset debug +cmake --build --preset debug +ctest --preset debug +``` + +When the pinned `ggml/` submodule is present, it is included automatically. +The non-neural ABI and validation subset can also be built without GGML: + +```sh +cmake -S . -B build/debug -G Ninja -DMOTIONBRICKS_ENABLE_GGML=OFF +``` + +The Go binding and demo use PureGo to load `libmotionbricks` at runtime; they +do not use cgo or `import "C"`. Once the native shared library has been built, +the Go components therefore need no C compiler and can be built with cgo +explicitly disabled: + +```sh +cd demo +CGO_ENABLED=0 go build -o ../build/debug/bin/motionbricks-demo . +``` + +`CGO_ENABLED=0` is optional but recommended for making this property explicit +in builds and CI. It affects only the Go build—the native C++ library is still +built separately with CMake. + +The sanitizer lane is: + +```sh +cmake --preset asan-ubsan +cmake --build --preset asan-ubsan +ctest --preset asan-ubsan +``` + +## Current ABI + +The installed C API uses only fixed-width scalars, pointers, and opaque heap +handles. Callers never reproduce a C or C++ structure layout. All constructors +have matching free functions, and no C++ exception crosses the ABI boundary. + +The current CLI can report ABI information: + +```sh +./build/debug/bin/motionbricks-cli abi +``` + +After producing the trusted safetensors intermediates described in +`reference/README.md`, build and inspect an F32 runtime bundle with: + +```sh +python scripts/convert_to_gguf.py \ + --safe-directory generated/safe \ + --output generated/g1-f32 +./build/debug/bin/motionbricks-cli inspect generated/g1-f32 +``` + +The released G1 inference path contains exactly **183,148,382 learned F32 +parameters**. The bundle loader validates the upstream revision, source +checkpoint identities, component roles, tensor counts, parameter counts, +anchor shapes, and the 34-joint parent topology before accepting a model. + +Convert the 15 original demo styles with: + +```sh +python scripts/convert_styles.py \ + --safe-directory generated/safe \ + --output generated/styles +``` + +At runtime the high-level flow is: load one immutable model, load one or more +`.mbstyle` assets, create an agent, reset it from an initial style (or supply +at least four frames of G1 context), set movement/facing/style on a command, +then call `mb_agent_plan`. The returned motion owns row-major F32 root +translations `[frames,3]` and local XYZW rotations `[frames,34,4]`. Call +`mb_agent_advance` as playback progresses so replanning uses the generated +motion as its next context. + +The current implementation covers original preprocessed G1 styles. Direct +Kimodo GLB-to-`.mbstyle` conversion remains subsequent integration work. + +## Weights + +Ready-to-run native weights and all 15 upstream style primitives are published +as [MotionBricks-G1-GGML](https://huggingface.co/LocalAI-io/MotionBricks-G1-GGML) +under the Hugging Face `LocalAI-io` organisation. NVIDIA currently distributes +MotionBricks checkpoints through Git LFS in +[`NVlabs/GR00T-WholeBodyControl`](https://github.com/NVlabs/GR00T-WholeBodyControl/tree/a0732b642c0333077e127a2f56ab0014c196bca4/motionbricks), +not a separate Hugging Face model repository, so the model card links to that +pinned upstream revision. The downloader verifies the version-controlled +distribution manifest before accepting any file. + +The default build is pinned to Hugging Face commit +`cc2a47603dbc203a4f18f35dd06ed3611833f506` rather than the mutable `main` +branch. + +## Interactive demo + +The initial Go/Three.js demo renders the model's 34-joint skeleton alongside +the four actual placed target-keyframe ghosts. It lets you steer with W/A/S/D, +turn facing with the arrow keys, orbit/zoom the camera, and switch among the +converted upstream styles. It uses the reusable PureGo binding and the same +stateful native agent as other applications. See the +[demo guide](docs/DEMO.md) for build, run, architecture, and headless-Chromium +test instructions. + +## Design + +- [Human-led design](docs/motions-bricks.md) +- [Implementation sketch and plan](docs/IMPLEMENTATION.md) +- [Versioned formats](docs/FORMATS.md) +- [Go/Three.js demo](docs/DEMO.md) +- [Pinned upstream reference](reference/README.md) + +## License + +motion-bricks.cpp source code is licensed under the +[Apache License 2.0](LICENSE). NVIDIA's original model weights and converted +GGUF/style distributions remain under the NVIDIA Open Model License reproduced +with the published model. It permits derivative models and redistribution with +conditions including retention of the agreement and attribution, Trustworthy +AI terms, and trade compliance. Bundled third-party components retain their own +licenses; the vendored Three.js files are covered by +`demo/web/vendor/THREE-LICENSE.txt`. diff --git a/bindings/go/abi_test.go b/bindings/go/abi_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f0855d9adcf53306f75151c011f4c90a24c88d9e --- /dev/null +++ b/bindings/go/abi_test.go @@ -0,0 +1,104 @@ +//go:build linux || darwin || freebsd + +package motionbricks + +import ( + "math" + "os" + "testing" + "unsafe" + + "github.com/ebitengine/purego" +) + +const ( + statusOK = uint32(0) + deviceAuto = uint32(0) + deviceVulkan = uint32(2) + abiVersion = uint32(1) + errorCapacity = uint64(256) +) + +// TestOpaqueABIRoundTrip intentionally mirrors no C struct. Every object is an +// opaque uintptr and every record field is accessed through a function. +func TestOpaqueABIRoundTrip(t *testing.T) { + library := os.Getenv("MOTIONBRICKS_LIB") + if library == "" { + t.Skip("set MOTIONBRICKS_LIB to the motion-bricks.cpp shared library") + } + + handle, err := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_LOCAL) + if err != nil { + t.Fatalf("dlopen %s: %v", library, err) + } + defer purego.Dlclose(handle) + + var version func() uint32 + var optionsCreate func(unsafe.Pointer, unsafe.Pointer, uint64) uint32 + var optionsFree func(uintptr) + var optionsSetDevice func(uintptr, uint32, unsafe.Pointer, uint64) uint32 + var optionsGetDevice func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + var commandCreate func(unsafe.Pointer, unsafe.Pointer, uint64) uint32 + var commandFree func(uintptr) + var commandSetMovement func(uintptr, float32, float32, float32, unsafe.Pointer, uint64) uint32 + var commandGetMovement func(uintptr, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + + purego.RegisterLibFunc(&version, handle, "mb_abi_version") + purego.RegisterLibFunc(&optionsCreate, handle, "mb_runtime_options_create") + purego.RegisterLibFunc(&optionsFree, handle, "mb_runtime_options_free") + purego.RegisterLibFunc(&optionsSetDevice, handle, "mb_runtime_options_set_device") + purego.RegisterLibFunc(&optionsGetDevice, handle, "mb_runtime_options_get_device") + purego.RegisterLibFunc(&commandCreate, handle, "mb_command_create") + purego.RegisterLibFunc(&commandFree, handle, "mb_command_free") + purego.RegisterLibFunc(&commandSetMovement, handle, "mb_command_set_movement_direction") + purego.RegisterLibFunc(&commandGetMovement, handle, "mb_command_get_movement_direction") + + if got := version(); got != abiVersion { + t.Fatalf("ABI version = %d, want %d", got, abiVersion) + } + + errorBuffer := make([]byte, errorCapacity) + errorPointer := unsafe.Pointer(&errorBuffer[0]) + var options uintptr + if status := optionsCreate(unsafe.Pointer(&options), errorPointer, errorCapacity); status != statusOK { + t.Fatalf("options create status %d: %q", status, errorBuffer) + } + if options == 0 { + t.Fatal("options create returned a null handle") + } + defer optionsFree(options) + + var device uint32 = math.MaxUint32 + if status := optionsGetDevice(options, unsafe.Pointer(&device), errorPointer, errorCapacity); status != statusOK { + t.Fatalf("get default device status %d", status) + } + if device != deviceAuto { + t.Fatalf("default device = %d, want auto", device) + } + if status := optionsSetDevice(options, deviceVulkan, errorPointer, errorCapacity); status != statusOK { + t.Fatalf("set Vulkan status %d", status) + } + if status := optionsGetDevice(options, unsafe.Pointer(&device), errorPointer, errorCapacity); status != statusOK || device != deviceVulkan { + t.Fatalf("Vulkan round trip status=%d device=%d", status, device) + } + + var command uintptr + if status := commandCreate(unsafe.Pointer(&command), errorPointer, errorCapacity); status != statusOK { + t.Fatalf("command create status %d", status) + } + if command == 0 { + t.Fatal("command create returned a null handle") + } + defer commandFree(command) + + if status := commandSetMovement(command, 1.0, 0.0, -0.25, errorPointer, errorCapacity); status != statusOK { + t.Fatalf("set movement status %d", status) + } + var x, y, z float32 + if status := commandGetMovement(command, unsafe.Pointer(&x), unsafe.Pointer(&y), unsafe.Pointer(&z), errorPointer, errorCapacity); status != statusOK { + t.Fatalf("get movement status %d", status) + } + if x != 1.0 || y != 0.0 || z != -0.25 { + t.Fatalf("movement = (%g, %g, %g)", x, y, z) + } +} diff --git a/bindings/go/go.mod b/bindings/go/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..ee6f64f3a72b084152905c59a64796f156367d2c --- /dev/null +++ b/bindings/go/go.mod @@ -0,0 +1,5 @@ +module github.com/localai/motion-bricks.cpp/bindings/go + +go 1.23 + +require github.com/ebitengine/purego v0.10.0 diff --git a/bindings/go/go.sum b/bindings/go/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..118766fd9861a09e1bf9e98a0079e5eb160d38ea --- /dev/null +++ b/bindings/go/go.sum @@ -0,0 +1,2 @@ +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= diff --git a/bindings/go/motionbricks.go b/bindings/go/motionbricks.go new file mode 100644 index 0000000000000000000000000000000000000000..b128d6b095ce9b16c8b51e530d390e69856bb138 --- /dev/null +++ b/bindings/go/motionbricks.go @@ -0,0 +1,410 @@ +//go:build linux || darwin || freebsd + +// Package motionbricks provides a PureGo wrapper around the stable opaque C ABI. +package motionbricks + +import ( + "errors" + "fmt" + "math" + "unsafe" + + "github.com/ebitengine/purego" +) + +type Device uint32 + +const ( + DeviceAuto Device = 0 + DeviceCPU Device = 1 + DeviceVulkan Device = 2 +) + +const errorBufferSize = 1024 + +type Library struct { + handle uintptr + abiVersion func() uint32 + statusString func(uint32) uintptr + optionsCreate func(unsafe.Pointer, unsafe.Pointer, uint64) uint32 + optionsFree func(uintptr) + optionsSetDevice func(uintptr, uint32, unsafe.Pointer, uint64) uint32 + modelLoad func(unsafe.Pointer, uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + modelFree func(uintptr) + modelJointCount func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + modelJointName func(uintptr, uint32, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + modelJointParent func(uintptr, uint32, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + modelNeutralPosition func(uintptr, uint32, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + styleLoad func(uintptr, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + styleFree func(uintptr) + styleName func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + styleSpeed func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + agentCreate func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + agentFree func(uintptr) + agentReset func(uintptr, uintptr, unsafe.Pointer, uint64) uint32 + agentSetContext func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64, uint64, unsafe.Pointer, uint64) uint32 + agentPlan func(uintptr, uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + agentAdvance func(uintptr, uint32, unsafe.Pointer, uint64) uint32 + commandCreate func(unsafe.Pointer, unsafe.Pointer, uint64) uint32 + commandFree func(uintptr) + commandSetStyle func(uintptr, uintptr, unsafe.Pointer, uint64) uint32 + commandSetMovement func(uintptr, float32, float32, float32, unsafe.Pointer, uint64) uint32 + commandSetFacing func(uintptr, float32, float32, float32, unsafe.Pointer, uint64) uint32 + commandSetSpeed func(uintptr, float32, unsafe.Pointer, uint64) uint32 + commandSetWorldTarget func(uintptr, float32, float32, float32, float32, uint32, unsafe.Pointer, uint64) uint32 + commandSetSeed func(uintptr, uint64, unsafe.Pointer, uint64) uint32 + motionFree func(uintptr) + motionFrames func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + motionJoints func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + motionRoots func(uintptr, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + motionRotations func(uintptr, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + motionTargetFrames func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + motionTargetRoots func(uintptr, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32 + motionTargetRotations func(uintptr, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32 +} + +func Open(path string) (*Library, error) { + handle, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_LOCAL) + if err != nil { + return nil, err + } + library := &Library{handle: handle} + register := func(target any, name string) { purego.RegisterLibFunc(target, handle, name) } + register(&library.abiVersion, "mb_abi_version") + register(&library.statusString, "mb_status_string") + register(&library.optionsCreate, "mb_runtime_options_create") + register(&library.optionsFree, "mb_runtime_options_free") + register(&library.optionsSetDevice, "mb_runtime_options_set_device") + register(&library.modelLoad, "mb_model_load") + register(&library.modelFree, "mb_model_free") + register(&library.modelJointCount, "mb_model_get_joint_count") + register(&library.modelJointName, "mb_model_get_joint_name") + register(&library.modelJointParent, "mb_model_get_joint_parent") + register(&library.modelNeutralPosition, "mb_model_get_neutral_joint_position") + register(&library.styleLoad, "mb_style_load") + register(&library.styleFree, "mb_style_free") + register(&library.styleName, "mb_style_get_name") + register(&library.styleSpeed, "mb_style_get_speed") + register(&library.agentCreate, "mb_agent_create") + register(&library.agentFree, "mb_agent_free") + register(&library.agentReset, "mb_agent_reset") + register(&library.agentSetContext, "mb_agent_set_context") + register(&library.agentPlan, "mb_agent_plan") + register(&library.agentAdvance, "mb_agent_advance") + register(&library.commandCreate, "mb_command_create") + register(&library.commandFree, "mb_command_free") + register(&library.commandSetStyle, "mb_command_set_style") + register(&library.commandSetMovement, "mb_command_set_movement_direction") + register(&library.commandSetFacing, "mb_command_set_facing_direction") + register(&library.commandSetSpeed, "mb_command_set_target_speed") + register(&library.commandSetWorldTarget, "mb_command_set_world_target") + register(&library.commandSetSeed, "mb_command_set_seed") + register(&library.motionFree, "mb_motion_free") + register(&library.motionFrames, "mb_motion_get_frame_count") + register(&library.motionJoints, "mb_motion_get_joint_count") + register(&library.motionRoots, "mb_motion_get_root_translations") + register(&library.motionRotations, "mb_motion_get_local_rotations_xyzw") + register(&library.motionTargetFrames, "mb_motion_get_target_frame_count") + register(&library.motionTargetRoots, "mb_motion_get_target_root_translations") + register(&library.motionTargetRotations, "mb_motion_get_target_local_rotations_xyzw") + if version := library.abiVersion(); version != 1 { + library.Close() + return nil, fmt.Errorf("motionbricks ABI version %d is unsupported", version) + } + return library, nil +} + +func (l *Library) Close() error { + if l == nil || l.handle == 0 { + return nil + } + err := purego.Dlclose(l.handle) + l.handle = 0 + return err +} + +func cString(value string) ([]byte, error) { + for _, char := range value { + if char == 0 { + return nil, errors.New("string contains NUL") + } + } + return append([]byte(value), 0), nil +} + +func goString(pointer uintptr) string { + if pointer == 0 { + return "" + } + const maximum = 1 << 20 + bytes := make([]byte, 0, 64) + for index := uintptr(0); index < maximum; index++ { + value := *(*byte)(unsafe.Pointer(pointer + index)) + if value == 0 { + return string(bytes) + } + bytes = append(bytes, value) + } + return "" +} + +func errorPointer(buffer []byte) unsafe.Pointer { return unsafe.Pointer(&buffer[0]) } + +func (l *Library) check(operation string, status uint32, buffer []byte) error { + if status == 0 { + return nil + } + message := "" + for index, value := range buffer { + if value == 0 { + message = string(buffer[:index]) + break + } + } + if message == "" { + message = goString(l.statusString(status)) + } + return fmt.Errorf("%s: %s", operation, message) +} + +type Model struct { + library *Library + handle uintptr +} +type Style struct { + library *Library + handle uintptr + Name string + Speed float32 +} +type Agent struct { + model *Model + handle uintptr +} +type Command struct { + library *Library + handle uintptr +} + +type Joint struct { + Name string `json:"name"` + Parent int32 `json:"parent"` + Position [3]float32 `json:"position"` +} +type Motion struct { + Frames uint64 `json:"frames"` + Joints uint64 `json:"joints"` + Roots []float32 `json:"roots"` + Rotations []float32 `json:"rotations"` + Targets *Keyframes `json:"-"` +} +type Keyframes struct { + Frames uint64 `json:"frames"` + Joints uint64 `json:"joints"` + Roots []float32 `json:"roots"` + Rotations []float32 `json:"rotations"` +} + +func (l *Library) LoadModel(path string, device Device) (*Model, error) { + pathBytes, err := cString(path) + if err != nil { + return nil, err + } + buffer := make([]byte, errorBufferSize) + var options uintptr + if err = l.check("create runtime options", l.optionsCreate(unsafe.Pointer(&options), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + defer l.optionsFree(options) + if err = l.check("select device", l.optionsSetDevice(options, uint32(device), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + var handle uintptr + status := l.modelLoad(unsafe.Pointer(&pathBytes[0]), options, unsafe.Pointer(&handle), errorPointer(buffer), uint64(len(buffer))) + if err = l.check("load model", status, buffer); err != nil { + return nil, err + } + return &Model{library: l, handle: handle}, nil +} + +func (m *Model) Close() { + if m != nil && m.handle != 0 { + m.library.modelFree(m.handle) + m.handle = 0 + } +} + +func (m *Model) Skeleton() ([]Joint, error) { + buffer := make([]byte, errorBufferSize) + var count uint32 + if err := m.library.check("get joint count", m.library.modelJointCount(m.handle, unsafe.Pointer(&count), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + joints := make([]Joint, count) + for index := uint32(0); index < count; index++ { + var name uintptr + if err := m.library.check("get joint name", m.library.modelJointName(m.handle, index, unsafe.Pointer(&name), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + joints[index].Name = goString(name) + if err := m.library.check("get joint parent", m.library.modelJointParent(m.handle, index, unsafe.Pointer(&joints[index].Parent), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + position := &joints[index].Position + if err := m.library.check("get neutral position", m.library.modelNeutralPosition(m.handle, index, unsafe.Pointer(&position[0]), unsafe.Pointer(&position[1]), unsafe.Pointer(&position[2]), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + } + return joints, nil +} + +func (m *Model) LoadStyle(path string) (*Style, error) { + pathBytes, err := cString(path) + if err != nil { + return nil, err + } + buffer := make([]byte, errorBufferSize) + var handle uintptr + if err = m.library.check("load style", m.library.styleLoad(m.handle, unsafe.Pointer(&pathBytes[0]), unsafe.Pointer(&handle), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + style := &Style{library: m.library, handle: handle} + var name uintptr + if err = m.library.check("get style name", m.library.styleName(handle, unsafe.Pointer(&name), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + style.Close() + return nil, err + } + style.Name = goString(name) + if err = m.library.check("get style speed", m.library.styleSpeed(handle, unsafe.Pointer(&style.Speed), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + style.Close() + return nil, err + } + return style, nil +} + +func (s *Style) Close() { + if s != nil && s.handle != 0 { + s.library.styleFree(s.handle) + s.handle = 0 + } +} + +func (m *Model) NewAgent() (*Agent, error) { + buffer := make([]byte, errorBufferSize) + var handle uintptr + if err := m.library.check("create agent", m.library.agentCreate(m.handle, unsafe.Pointer(&handle), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + return &Agent{model: m, handle: handle}, nil +} +func (a *Agent) Close() { + if a != nil && a.handle != 0 { + a.model.library.agentFree(a.handle) + a.handle = 0 + } +} +func (a *Agent) Reset(style *Style) error { + buffer := make([]byte, errorBufferSize) + return a.model.library.check("reset agent", a.model.library.agentReset(a.handle, style.handle, errorPointer(buffer), uint64(len(buffer))), buffer) +} +func (a *Agent) Advance(frames uint32) error { + buffer := make([]byte, errorBufferSize) + return a.model.library.check("advance agent", a.model.library.agentAdvance(a.handle, frames, errorPointer(buffer), uint64(len(buffer))), buffer) +} +func (a *Agent) SetContext(roots, rotations []float32, frames uint64) error { + if frames < 4 || len(roots) != int(frames*3) || len(rotations) != int(frames*34*4) { + return errors.New("invalid G1 context shape") + } + buffer := make([]byte, errorBufferSize) + return a.model.library.check("set agent context", a.model.library.agentSetContext(a.handle, unsafe.Pointer(&roots[0]), unsafe.Pointer(&rotations[0]), frames, 34, errorPointer(buffer), uint64(len(buffer))), buffer) +} + +func (l *Library) NewCommand() (*Command, error) { + buffer := make([]byte, errorBufferSize) + var handle uintptr + if err := l.check("create command", l.commandCreate(unsafe.Pointer(&handle), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + return &Command{library: l, handle: handle}, nil +} +func (c *Command) Close() { + if c != nil && c.handle != 0 { + c.library.commandFree(c.handle) + c.handle = 0 + } +} +func (c *Command) SetStyle(style *Style) error { + buffer := make([]byte, errorBufferSize) + return c.library.check("set command style", c.library.commandSetStyle(c.handle, style.handle, errorPointer(buffer), uint64(len(buffer))), buffer) +} +func (c *Command) SetMovement(x, y, z float32) error { + buffer := make([]byte, errorBufferSize) + return c.library.check("set movement", c.library.commandSetMovement(c.handle, x, y, z, errorPointer(buffer), uint64(len(buffer))), buffer) +} +func (c *Command) SetFacing(x, y, z float32) error { + buffer := make([]byte, errorBufferSize) + return c.library.check("set facing", c.library.commandSetFacing(c.handle, x, y, z, errorPointer(buffer), uint64(len(buffer))), buffer) +} +func (c *Command) SetSpeed(speed float32) error { + if math.IsNaN(float64(speed)) { + return errors.New("speed is NaN") + } + buffer := make([]byte, errorBufferSize) + return c.library.check("set speed", c.library.commandSetSpeed(c.handle, speed, errorPointer(buffer), uint64(len(buffer))), buffer) +} +func (c *Command) SetSeed(seed uint64) error { + buffer := make([]byte, errorBufferSize) + return c.library.check("set seed", c.library.commandSetSeed(c.handle, seed, errorPointer(buffer), uint64(len(buffer))), buffer) +} +func (c *Command) SetWorldTarget(x, y, z, heading float32, enabled bool) error { + var flag uint32 + if enabled { + flag = 1 + } + buffer := make([]byte, errorBufferSize) + return c.library.check("set world target", c.library.commandSetWorldTarget(c.handle, x, y, z, heading, flag, errorPointer(buffer), uint64(len(buffer))), buffer) +} + +func (a *Agent) Plan(command *Command) (*Motion, error) { + buffer := make([]byte, errorBufferSize) + var handle uintptr + if err := a.model.library.check("plan motion", a.model.library.agentPlan(a.handle, command.handle, unsafe.Pointer(&handle), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + defer a.model.library.motionFree(handle) + motion := &Motion{} + var rootsPointer, rotationsPointer, targetRootsPointer, targetRotationsPointer uintptr + var rootsCount, rotationsCount, targetRootsCount, targetRotationsCount uint64 + if err := a.model.library.check("get frame count", a.model.library.motionFrames(handle, unsafe.Pointer(&motion.Frames), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + if err := a.model.library.check("get joint count", a.model.library.motionJoints(handle, unsafe.Pointer(&motion.Joints), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + if err := a.model.library.check("get roots", a.model.library.motionRoots(handle, unsafe.Pointer(&rootsPointer), unsafe.Pointer(&rootsCount), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + if err := a.model.library.check("get rotations", a.model.library.motionRotations(handle, unsafe.Pointer(&rotationsPointer), unsafe.Pointer(&rotationsCount), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + targets := &Keyframes{Joints: motion.Joints} + if err := a.model.library.check("get target frame count", a.model.library.motionTargetFrames(handle, unsafe.Pointer(&targets.Frames), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + if err := a.model.library.check("get target roots", a.model.library.motionTargetRoots(handle, unsafe.Pointer(&targetRootsPointer), unsafe.Pointer(&targetRootsCount), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + if err := a.model.library.check("get target rotations", a.model.library.motionTargetRotations(handle, unsafe.Pointer(&targetRotationsPointer), unsafe.Pointer(&targetRotationsCount), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil { + return nil, err + } + if rootsCount != motion.Frames*3 || rotationsCount != motion.Frames*motion.Joints*4 || + targetRootsCount != targets.Frames*3 || targetRotationsCount != targets.Frames*targets.Joints*4 { + return nil, errors.New("native motion data has inconsistent dimensions") + } + motion.Roots = append([]float32(nil), unsafe.Slice((*float32)(unsafe.Pointer(rootsPointer)), rootsCount)...) + motion.Rotations = append([]float32(nil), unsafe.Slice((*float32)(unsafe.Pointer(rotationsPointer)), rotationsCount)...) + targets.Roots = append([]float32(nil), unsafe.Slice((*float32)(unsafe.Pointer(targetRootsPointer)), targetRootsCount)...) + targets.Rotations = append([]float32(nil), unsafe.Slice((*float32)(unsafe.Pointer(targetRotationsPointer)), targetRotationsCount)...) + motion.Targets = targets + return motion, nil +} diff --git a/demo/go.mod b/demo/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..c91fad2460b31dbf956a6fb729daa99ec7564fef --- /dev/null +++ b/demo/go.mod @@ -0,0 +1,21 @@ +module github.com/localai/motion-bricks.cpp/demo + +go 1.26 + +require ( + github.com/chromedp/cdproto v0.0.0-20260714215040-dc233986426f + github.com/chromedp/chromedp v0.16.0 + github.com/localai/motion-bricks.cpp/bindings/go v0.0.0 +) + +require ( + github.com/chromedp/sysutil v1.1.0 // indirect + github.com/ebitengine/purego v0.10.0 // indirect + github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.4.0 // indirect + golang.org/x/sys v0.47.0 // indirect +) + +replace github.com/localai/motion-bricks.cpp/bindings/go => ../bindings/go diff --git a/demo/go.sum b/demo/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..5cac7ffd165b5c655446a58d25d8b776ca71f92b --- /dev/null +++ b/demo/go.sum @@ -0,0 +1,23 @@ +github.com/chromedp/cdproto v0.0.0-20260714215040-dc233986426f h1:0Z1zcSLEmnj2c2CmJYBqewtS6pxhB39bNWUSEUAWjgk= +github.com/chromedp/cdproto v0.0.0-20260714215040-dc233986426f/go.mod h1:RwFsSODCtFExll+GhHM6R92SARHR3Z3oipaxLHj46C0= +github.com/chromedp/chromedp v0.16.0 h1:rOO4deOm4CbZgBCa8mD9g2rDyIoNs0BkgvNrlbp5ouk= +github.com/chromedp/chromedp v0.16.0/go.mod h1:rbuGKFT1vMcFcFqKfPIO1GpX/N+2s8onm2qMxZLbU5U= +github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= +github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 h1:KZaTBSyshWX3MP5jukJcNSuXDQTO+rNpt0J564dX/eg= +github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= +github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= diff --git a/demo/main.go b/demo/main.go new file mode 100644 index 0000000000000000000000000000000000000000..d5148bd5d1bd207363d44caabd4c7ba0f6172b8b --- /dev/null +++ b/demo/main.go @@ -0,0 +1,371 @@ +package main + +import ( + "crypto/rand" + "embed" + "encoding/hex" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "io/fs" + "log" + "math" + "net/http" + "os" + "os/signal" + "path/filepath" + "sort" + "strings" + "sync" + "syscall" + "time" + + mb "github.com/localai/motion-bricks.cpp/bindings/go" +) + +//go:embed web/* web/vendor/* +var webFiles embed.FS + +type styleInfo struct { + Name string `json:"name"` + Speed float32 `json:"speed"` +} + +type session struct { + mu sync.Mutex + agent *mb.Agent + planned bool +} + +type demoServer struct { + library *mb.Library + model *mb.Model + styles map[string]*mb.Style + ordered []styleInfo + joints []mb.Joint + planMu sync.Mutex + mu sync.Mutex + sessions map[string]*session + static http.Handler +} + +type sessionRequest struct { + Style string `json:"style"` +} +type planRequest struct { + Session string `json:"session"` + Style string `json:"style"` + Move [2]float32 `json:"move"` + Facing [2]float32 `json:"facing"` + Speed *float32 `json:"speed,omitempty"` + Seed uint64 `json:"seed"` + Advance uint32 `json:"advance"` +} +type planResponse struct { + Session string `json:"session"` + Style string `json:"style"` + Motion *mb.Motion `json:"motion"` + Targets *mb.Keyframes `json:"targets"` +} + +func parseDevice(value string) (mb.Device, error) { + switch strings.ToLower(value) { + case "auto": + return mb.DeviceAuto, nil + case "cpu": + return mb.DeviceCPU, nil + case "vulkan": + return mb.DeviceVulkan, nil + default: + return 0, fmt.Errorf("unknown device %q", value) + } +} + +func loadDemoServer(libraryPath, modelPath, styleDirectory string, device mb.Device) (*demoServer, error) { + library, err := mb.Open(libraryPath) + if err != nil { + return nil, fmt.Errorf("open native library: %w", err) + } + model, err := library.LoadModel(modelPath, device) + if err != nil { + library.Close() + return nil, err + } + paths, err := filepath.Glob(filepath.Join(styleDirectory, "*.mbstyle")) + if err != nil || len(paths) == 0 { + model.Close() + library.Close() + return nil, fmt.Errorf("no .mbstyle files in %s", styleDirectory) + } + sort.Strings(paths) + server := &demoServer{library: library, model: model, styles: make(map[string]*mb.Style), sessions: make(map[string]*session)} + for _, path := range paths { + style, loadErr := model.LoadStyle(path) + if loadErr != nil { + server.Close() + return nil, fmt.Errorf("load %s: %w", path, loadErr) + } + if _, exists := server.styles[style.Name]; exists { + style.Close() + server.Close() + return nil, fmt.Errorf("duplicate style %q", style.Name) + } + server.styles[style.Name] = style + server.ordered = append(server.ordered, styleInfo{Name: style.Name, Speed: style.Speed}) + } + sort.Slice(server.ordered, func(i, j int) bool { return server.ordered[i].Name < server.ordered[j].Name }) + server.joints, err = model.Skeleton() + if err != nil { + server.Close() + return nil, err + } + root, err := fs.Sub(webFiles, "web") + if err != nil { + server.Close() + return nil, err + } + server.static = http.FileServer(http.FS(root)) + return server, nil +} + +func (s *demoServer) Close() { + if s == nil { + return + } + s.mu.Lock() + for _, item := range s.sessions { + item.agent.Close() + } + s.sessions = nil + s.mu.Unlock() + for _, style := range s.styles { + style.Close() + } + if s.model != nil { + s.model.Close() + } + if s.library != nil { + _ = s.library.Close() + } +} + +func randomID() (string, error) { + var value [16]byte + if _, err := rand.Read(value[:]); err != nil { + return "", err + } + return hex.EncodeToString(value[:]), nil +} + +func jsonResponse(writer http.ResponseWriter, status int, value any) { + writer.Header().Set("Content-Type", "application/json") + writer.Header().Set("Cache-Control", "no-store") + writer.WriteHeader(status) + _ = json.NewEncoder(writer).Encode(value) +} +func apiError(writer http.ResponseWriter, status int, err error) { + jsonResponse(writer, status, map[string]string{"error": err.Error()}) +} +func decodeJSON(request *http.Request, output any) error { + decoder := json.NewDecoder(io.LimitReader(request.Body, 1<<20)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(output); err != nil { + return fmt.Errorf("invalid JSON: %w", err) + } + return nil +} +func finite(values ...float32) bool { + for _, value := range values { + if math.IsNaN(float64(value)) || math.IsInf(float64(value), 0) { + return false + } + } + return true +} + +func (s *demoServer) style(name string) (*mb.Style, error) { + style := s.styles[name] + if style == nil { + return nil, fmt.Errorf("unknown style %q", name) + } + return style, nil +} + +func (s *demoServer) commandPlan(item *session, style *mb.Style, request planRequest) (*mb.Motion, error) { + if !finite(request.Move[0], request.Move[1], request.Facing[0], request.Facing[1]) { + return nil, errors.New("control vector is not finite") + } + if math.Hypot(float64(request.Facing[0]), float64(request.Facing[1])) < 1e-6 { + return nil, errors.New("facing vector is zero") + } + item.mu.Lock() + defer item.mu.Unlock() + if item.planned && request.Advance > 0 { + if err := item.agent.Advance(request.Advance); err != nil { + return nil, err + } + } + command, err := s.library.NewCommand() + if err != nil { + return nil, err + } + defer command.Close() + if err = command.SetStyle(style); err != nil { + return nil, err + } + if err = command.SetMovement(request.Move[0], 0, request.Move[1]); err != nil { + return nil, err + } + if err = command.SetFacing(request.Facing[0], 0, request.Facing[1]); err != nil { + return nil, err + } + if request.Speed != nil { + if !finite(*request.Speed) || *request.Speed < 0 { + return nil, errors.New("speed is invalid") + } + if err = command.SetSpeed(*request.Speed); err != nil { + return nil, err + } + } + if err = command.SetSeed(request.Seed); err != nil { + return nil, err + } + s.planMu.Lock() + motion, err := item.agent.Plan(command) + s.planMu.Unlock() + if err == nil { + item.planned = true + } + return motion, err +} + +func (s *demoServer) routes() http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("GET /api/health", func(w http.ResponseWriter, _ *http.Request) { + jsonResponse(w, http.StatusOK, map[string]any{"ok": true}) + }) + mux.HandleFunc("GET /api/meta", func(w http.ResponseWriter, _ *http.Request) { + jsonResponse(w, http.StatusOK, map[string]any{"fps": 30, "joints": s.joints, "styles": s.ordered}) + }) + mux.HandleFunc("POST /api/session", s.createSession) + mux.HandleFunc("POST /api/plan", s.plan) + mux.Handle("/", s.static) + return securityHeaders(mux) +} + +func securityHeaders(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "no-store") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("Referrer-Policy", "no-referrer") + w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'") + next.ServeHTTP(w, r) + }) +} + +func (s *demoServer) createSession(w http.ResponseWriter, r *http.Request) { + var request sessionRequest + if err := decodeJSON(r, &request); err != nil { + apiError(w, http.StatusBadRequest, err) + return + } + if request.Style == "" { + if _, ok := s.styles["idle"]; ok { + request.Style = "idle" + } else { + request.Style = s.ordered[0].Name + } + } + style, err := s.style(request.Style) + if err != nil { + apiError(w, http.StatusBadRequest, err) + return + } + agent, err := s.model.NewAgent() + if err != nil { + apiError(w, http.StatusInternalServerError, err) + return + } + if err = agent.Reset(style); err != nil { + agent.Close() + apiError(w, http.StatusInternalServerError, err) + return + } + id, err := randomID() + if err != nil { + agent.Close() + apiError(w, http.StatusInternalServerError, err) + return + } + item := &session{agent: agent} + s.mu.Lock() + s.sessions[id] = item + s.mu.Unlock() + motion, err := s.commandPlan(item, style, planRequest{Move: [2]float32{0, 0}, Facing: [2]float32{0, 1}, Seed: 1}) + if err != nil { + s.mu.Lock() + delete(s.sessions, id) + s.mu.Unlock() + agent.Close() + apiError(w, http.StatusInternalServerError, err) + return + } + jsonResponse(w, http.StatusOK, planResponse{Session: id, Style: style.Name, Motion: motion, Targets: motion.Targets}) +} + +func (s *demoServer) plan(w http.ResponseWriter, r *http.Request) { + var request planRequest + if err := decodeJSON(r, &request); err != nil { + apiError(w, http.StatusBadRequest, err) + return + } + s.mu.Lock() + item := s.sessions[request.Session] + s.mu.Unlock() + if item == nil { + apiError(w, http.StatusNotFound, errors.New("unknown session")) + return + } + style, err := s.style(request.Style) + if err != nil { + apiError(w, http.StatusBadRequest, err) + return + } + motion, err := s.commandPlan(item, style, request) + if err != nil { + apiError(w, http.StatusInternalServerError, err) + return + } + jsonResponse(w, http.StatusOK, planResponse{Session: request.Session, Style: style.Name, Motion: motion, Targets: motion.Targets}) +} + +func main() { + listen := flag.String("listen", "127.0.0.1:8080", "HTTP listen address") + libraryPath := flag.String("library", os.Getenv("MOTIONBRICKS_LIB"), "path to libmotionbricks") + modelPath := flag.String("model", os.Getenv("MOTIONBRICKS_MODEL"), "model bundle directory") + stylesPath := flag.String("styles", os.Getenv("MOTIONBRICKS_STYLES"), "style directory") + deviceName := flag.String("device", "cpu", "auto, cpu, or vulkan") + flag.Parse() + if *libraryPath == "" || *modelPath == "" || *stylesPath == "" { + log.Fatal("-library, -model, and -styles are required") + } + device, err := parseDevice(*deviceName) + if err != nil { + log.Fatal(err) + } + demo, err := loadDemoServer(*libraryPath, *modelPath, *stylesPath, device) + if err != nil { + log.Fatal(err) + } + defer demo.Close() + server := &http.Server{Addr: *listen, Handler: demo.routes(), ReadHeaderTimeout: 5 * time.Second} + stopped := make(chan os.Signal, 1) + signal.Notify(stopped, os.Interrupt, syscall.SIGTERM) + go func() { <-stopped; _ = server.Close() }() + log.Printf("MotionBricks demo: http://%s", *listen) + if err = server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Fatal(err) + } +} diff --git a/demo/main_test.go b/demo/main_test.go new file mode 100644 index 0000000000000000000000000000000000000000..093c2aa7729a44e3afabb217bc965d2320c7b2a1 --- /dev/null +++ b/demo/main_test.go @@ -0,0 +1,236 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + cdplog "github.com/chromedp/cdproto/log" + "github.com/chromedp/cdproto/runtime" + "github.com/chromedp/chromedp" + mb "github.com/localai/motion-bricks.cpp/bindings/go" +) + +func TestParseDevice(t *testing.T) { + for name, expected := range map[string]mb.Device{"auto": mb.DeviceAuto, "cpu": mb.DeviceCPU, "vulkan": mb.DeviceVulkan, "CPU": mb.DeviceCPU} { + actual, err := parseDevice(name) + if err != nil || actual != expected { + t.Fatalf("parseDevice(%q)=(%d,%v), want %d", name, actual, err, expected) + } + } + if _, err := parseDevice("cuda"); err == nil { + t.Fatal("unknown device was accepted") + } +} + +func TestNativeHTTPFlow(t *testing.T) { + libraryPath, modelPath, stylesPath := os.Getenv("MOTIONBRICKS_LIB"), os.Getenv("MOTIONBRICKS_MODEL"), os.Getenv("MOTIONBRICKS_STYLES") + if libraryPath == "" || modelPath == "" || stylesPath == "" { + t.Skip("native demo paths are not configured") + } + demo, err := loadDemoServer(libraryPath, modelPath, stylesPath, mb.DeviceCPU) + if err != nil { + t.Fatal(err) + } + defer demo.Close() + server := httptest.NewServer(demo.routes()) + defer server.Close() + response, err := http.Get(server.URL + "/api/meta") + if err != nil { + t.Fatal(err) + } + var metadata struct { + FPS int `json:"fps"` + Joints []mb.Joint `json:"joints"` + Styles []styleInfo `json:"styles"` + } + if err = json.NewDecoder(response.Body).Decode(&metadata); err != nil { + t.Fatal(err) + } + response.Body.Close() + if response.StatusCode != http.StatusOK || metadata.FPS != 30 || len(metadata.Joints) != 34 || len(metadata.Styles) < 10 { + t.Fatalf("invalid metadata: status=%d fps=%d joints=%d styles=%d", response.StatusCode, metadata.FPS, len(metadata.Joints), len(metadata.Styles)) + } + post := func(path string, value any, output any) { + t.Helper() + body, _ := json.Marshal(value) + reply, postErr := http.Post(server.URL+path, "application/json", bytes.NewReader(body)) + if postErr != nil { + t.Fatal(postErr) + } + defer reply.Body.Close() + if reply.StatusCode != http.StatusOK { + var failure any + _ = json.NewDecoder(reply.Body).Decode(&failure) + t.Fatalf("%s: status=%d body=%v", path, reply.StatusCode, failure) + } + if err := json.NewDecoder(reply.Body).Decode(output); err != nil { + t.Fatal(err) + } + } + var initial planResponse + post("/api/session", sessionRequest{Style: "walk"}, &initial) + if initial.Session == "" || initial.Motion == nil || initial.Motion.Joints != 34 || initial.Motion.Frames < 24 || + initial.Targets == nil || initial.Targets.Frames != 4 || initial.Targets.Joints != 34 || + len(initial.Targets.Roots) != 12 || len(initial.Targets.Rotations) != 4*34*4 { + t.Fatalf("invalid initial response: %+v", initial) + } + turnedStyle := "" + for _, style := range metadata.Styles { + if style.Name == "walk_zombie" { + turnedStyle = style.Name + break + } + if style.Name != "walk" && turnedStyle == "" { + turnedStyle = style.Name + } + } + if turnedStyle == "" { + t.Fatal("no alternate upstream style is available") + } + var turned planResponse + post("/api/plan", planRequest{Session: initial.Session, Style: turnedStyle, Move: [2]float32{1, 0}, Facing: [2]float32{1, 0}, Advance: 3, Seed: 77}, &turned) + if turned.Style != turnedStyle || turned.Motion == nil || turned.Motion.Joints != 34 || len(turned.Motion.Rotations) != int(turned.Motion.Frames*34*4) || + turned.Targets == nil || turned.Targets.Frames != 4 || len(turned.Targets.Rotations) != 4*34*4 { + t.Fatalf("invalid turned response: style=%q motion=%+v", turned.Style, turned.Motion) + } +} + +func TestHeadlessChrome(t *testing.T) { + libraryPath, modelPath, stylesPath := os.Getenv("MOTIONBRICKS_LIB"), os.Getenv("MOTIONBRICKS_MODEL"), os.Getenv("MOTIONBRICKS_STYLES") + if libraryPath == "" || modelPath == "" || stylesPath == "" { + t.Skip("native demo paths are not configured") + } + chrome := os.Getenv("MOTIONBRICKS_CHROME") + if chrome == "" { + var err error + chrome, err = exec.LookPath("chromium") + if err != nil { + t.Skip("chromium is not installed") + } + } + demo, err := loadDemoServer(libraryPath, modelPath, stylesPath, mb.DeviceCPU) + if err != nil { + t.Fatal(err) + } + defer demo.Close() + server := httptest.NewServer(demo.routes()) + defer server.Close() + + options := append([]chromedp.ExecAllocatorOption{}, chromedp.DefaultExecAllocatorOptions[:]...) + options = append(options, + chromedp.ExecPath(chrome), + chromedp.Flag("no-sandbox", true), + chromedp.Flag("disable-dev-shm-usage", true), + chromedp.Flag("use-angle", "swiftshader"), + chromedp.Flag("enable-unsafe-swiftshader", true), + chromedp.WindowSize(1280, 800), + ) + allocator, cancelAllocator := chromedp.NewExecAllocator(context.Background(), options...) + defer cancelAllocator() + browser, cancelBrowser := chromedp.NewContext(allocator) + defer cancelBrowser() + chromedp.ListenTarget(browser, func(event any) { + switch event := event.(type) { + case *cdplog.EventEntryAdded: + t.Logf("browser log: %s", event.Entry.Text) + case *runtime.EventConsoleAPICalled: + t.Logf("browser console: %v", event.Args) + case *runtime.EventExceptionThrown: + t.Logf("browser exception: %s", event.ExceptionDetails.Text) + } + }) + ctx, cancel := context.WithTimeout(browser, 30*time.Second) + defer cancel() + + waitFor := func(expression, description string) chromedp.Action { + return chromedp.ActionFunc(func(ctx context.Context) error { + deadline := time.Now().Add(25 * time.Second) + for time.Now().Before(deadline) { + var ready bool + if evaluateErr := chromedp.Evaluate(expression, &ready).Do(ctx); evaluateErr != nil { + return evaluateErr + } + if ready { + return nil + } + time.Sleep(100 * time.Millisecond) + } + var diagnostic any + _ = chromedp.Evaluate(`({status: document.documentElement.dataset.testStatus, sequence: document.documentElement.dataset.planSequence, moveX: document.documentElement.dataset.plannedMoveX, moveZ: document.documentElement.dataset.plannedMoveZ, scripts: [...document.scripts].map(s => ({src:s.src,type:s.type})), resources: performance.getEntriesByType("resource").map(r => r.name)})`, &diagnostic).Do(ctx) + return fmt.Errorf("%w for %s: %#v", errors.New("timeout waiting for browser"), description, diagnostic) + }) + } + var initialScreenshot, movingScreenshot, screenshot []byte + var message string + err = chromedp.Run(ctx, + chromedp.ActionFunc(func(ctx context.Context) error { + if enableErr := cdplog.Enable().Do(ctx); enableErr != nil { + return enableErr + } + return runtime.Enable().Do(ctx) + }), + chromedp.Navigate(server.URL+"/"), + waitFor(`document.documentElement.dataset.testStatus === "ready"`, "initial plan"), + chromedp.FullScreenshot(&initialScreenshot, 90), + chromedp.Click(`.pad button[data-key="w"]`, chromedp.ByQuery), + waitFor(`Number(document.documentElement.dataset.planSequence) >= 2 && document.documentElement.dataset.plannedMoveZ === "1"`, "forward pad-button plan"), + chromedp.FullScreenshot(&movingScreenshot, 90), + chromedp.Click(`.pad button[data-key="w"]`, chromedp.ByQuery), + waitFor(`Number(document.documentElement.dataset.planSequence) >= 3 && document.documentElement.dataset.plannedMoveX === "0" && document.documentElement.dataset.plannedMoveZ === "0"`, "pad-button stop plan"), + chromedp.Evaluate(`dispatchEvent(new KeyboardEvent("keydown", {key:"d", bubbles:true}))`, nil), + waitFor(`Number(document.documentElement.dataset.planSequence) >= 4 && document.documentElement.dataset.plannedMoveX === "1"`, "keyboard-right plan"), + chromedp.Evaluate(`dispatchEvent(new KeyboardEvent("keyup", {key:"d", bubbles:true}))`, nil), + waitFor(`Number(document.documentElement.dataset.planSequence) >= 5 && document.documentElement.dataset.plannedMoveX === "0" && document.documentElement.dataset.plannedMoveZ === "0"`, "keyboard stop plan"), + chromedp.Navigate(server.URL+"/?test=1"), + waitFor(`document.documentElement.dataset.testStatus === "passed" || document.documentElement.dataset.testStatus === "failed"`, "style-and-turn self-test"), + chromedp.Text("#test-result", &message, chromedp.ByQuery), + chromedp.FullScreenshot(&screenshot, 90), + ) + if err != nil { + t.Fatal(err) + } + var status string + if err = chromedp.Run(ctx, chromedp.Evaluate(`document.documentElement.dataset.testStatus`, &status)); err != nil { + t.Fatal(err) + } + if status != "passed" { + t.Fatalf("browser self-test status=%q: %s", status, message) + } + if len(initialScreenshot) < 10_000 || len(movingScreenshot) < 10_000 || len(screenshot) < 10_000 { + t.Fatalf("rendered screenshots are unexpectedly small: initial=%d moving=%d final=%d", len(initialScreenshot), len(movingScreenshot), len(screenshot)) + } + artifact := os.Getenv("MOTIONBRICKS_SCREENSHOT") + if artifact == "" { + artifact = filepath.Join(t.TempDir(), "motionbricks-demo.png") + } + extension := filepath.Ext(artifact) + if extension == "" { + extension = ".png" + } + base := strings.TrimSuffix(artifact, filepath.Ext(artifact)) + artifacts := []struct { + path string + data []byte + }{ + {path: base + "-initial" + extension, data: initialScreenshot}, + {path: base + "-moving" + extension, data: movingScreenshot}, + {path: artifact, data: screenshot}, + } + for _, item := range artifacts { + if err = os.WriteFile(item.path, item.data, 0o600); err != nil { + t.Fatal(err) + } + } + t.Logf("%s; screenshots: %s, %s, %s (final %s)", message, artifacts[0].path, artifacts[1].path, artifacts[2].path, fmt.Sprintf("%d bytes", len(screenshot))) +} diff --git a/demo/web/app.js b/demo/web/app.js new file mode 100644 index 0000000000000000000000000000000000000000..e2f0687f40afb1505b79149f36bc1eec10fca7e5 --- /dev/null +++ b/demo/web/app.js @@ -0,0 +1,499 @@ +import * as THREE from './vendor/three.module.min.js'; + +const viewport = document.querySelector('#viewport'); +const styleSelect = document.querySelector('#style-select'); +const statusElement = document.querySelector('#status'); +const planInfo = document.querySelector('#plan-info'); +const targetInfo = document.querySelector('#target-info'); +const testResult = document.querySelector('#test-result'); +const showAllTargets = document.querySelector('#show-all-targets'); +const targetFrame = document.querySelector('#target-frame'); +const targetFrameLabel = document.querySelector('#target-frame-label'); +const resetCamera = document.querySelector('#reset-camera'); +const query = new URLSearchParams(location.search); + +const state = { + meta: null, session: '', motion: null, targets: null, playhead: 0, + lastTime: performance.now(), move: [0, 0], facing: [0, 1], keys: new Set(), + padKey: '', pending: false, replanQueued: false, seed: 10, style: '', rig: null, targetRigs: [], + generatedPath: null, targetPath: null, +}; + +const scene = new THREE.Scene(); +scene.fog = new THREE.FogExp2(0x090b10, 0.045); +const camera = new THREE.PerspectiveCamera(42, 1, 0.02, 100); +const renderer = new THREE.WebGLRenderer({antialias: true, alpha: true}); +renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); +renderer.outputColorSpace = THREE.SRGBColorSpace; +renderer.shadowMap.enabled = true; +renderer.shadowMap.type = THREE.PCFSoftShadowMap; +viewport.append(renderer.domElement); + +scene.add(new THREE.HemisphereLight(0xcde4ff, 0x253226, 2.5)); +const keyLight = new THREE.DirectionalLight(0xffffff, 3.2); +keyLight.position.set(3, 7, 4); +keyLight.castShadow = true; +scene.add(keyLight); +const rimLight = new THREE.DirectionalLight(0x67e8c3, 1.2); +rimLight.position.set(-4, 3, -3); +scene.add(rimLight); +const floor = new THREE.Mesh( + new THREE.PlaneGeometry(40, 40), + new THREE.MeshStandardMaterial({color: 0x0d121a, roughness: 0.96, metalness: 0.02}), +); +floor.rotation.x = -Math.PI / 2; +floor.position.y = -0.012; +floor.receiveShadow = true; +scene.add(floor); +const grid = new THREE.GridHelper(24, 48, 0x3a4b5d, 0x202b38); +grid.position.y = 0; +scene.add(grid); + +const cylinderGeometry = new THREE.CylinderGeometry(1, 1, 1, 8, 1, false); +const sphereGeometry = new THREE.SphereGeometry(1, 14, 10); +const diamondGeometry = new THREE.OctahedronGeometry(1, 0); +const up = new THREE.Vector3(0, 1, 0); +const startPoint = new THREE.Vector3(); +const endPoint = new THREE.Vector3(); +const direction = new THREE.Vector3(); +const midpoint = new THREE.Vector3(); + +function labelSprite(text, color, opacity) { + const canvas = document.createElement('canvas'); + canvas.width = 128; + canvas.height = 64; + const context = canvas.getContext('2d'); + context.font = '700 30px system-ui'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.fillStyle = color; + context.fillText(text, 64, 32); + const texture = new THREE.CanvasTexture(canvas); + texture.colorSpace = THREE.SRGBColorSpace; + const sprite = new THREE.Sprite(new THREE.SpriteMaterial({map: texture, transparent: true, opacity, depthTest: false})); + sprite.scale.set(0.32, 0.16, 1); + sprite.renderOrder = 20; + return sprite; +} + +class SkeletonRig { + constructor(joints, options) { + this.bones = []; + this.jointMeshes = []; + this.segments = []; + this.group = new THREE.Group(); + this.radius = options.radius; + this.jointRadius = options.jointRadius; + this.rootRadius = options.rootRadius; + this.labelHeight = options.labelHeight ?? 0.34; + this.boneMaterial = new THREE.MeshStandardMaterial({ + color: options.color, emissive: options.emissive, emissiveIntensity: options.emissiveIntensity, + roughness: 0.42, transparent: options.opacity < 1, opacity: options.opacity, + depthWrite: options.opacity >= 0.95, + }); + this.jointMaterial = new THREE.MeshStandardMaterial({ + color: options.jointColor, emissive: options.emissive, emissiveIntensity: options.emissiveIntensity, + roughness: 0.35, transparent: options.opacity < 1, opacity: options.opacity, + depthWrite: options.opacity >= 0.95, + }); + this.rootMaterial = new THREE.MeshStandardMaterial({ + color: options.rootColor, emissive: options.rootEmissive, emissiveIntensity: 1.4, + transparent: options.opacity < 1, opacity: options.opacity, depthWrite: options.opacity >= 0.95, + }); + for (let index = 0; index < joints.length; index++) { + const joint = joints[index]; + const bone = new THREE.Bone(); + bone.name = joint.name; + if (joint.parent < 0) { + bone.position.set(0, 0, 0); + } else { + const parent = joints[joint.parent].position; + bone.position.set(joint.position[0] - parent[0], joint.position[1] - parent[1], joint.position[2] - parent[2]); + this.bones[joint.parent].add(bone); + } + this.bones.push(bone); + const geometry = options.diamonds ? diamondGeometry : sphereGeometry; + const marker = new THREE.Mesh(geometry, index === 0 ? this.rootMaterial : this.jointMaterial); + marker.castShadow = !options.diamonds; + marker.renderOrder = options.renderOrder; + this.jointMeshes.push(marker); + this.group.add(marker); + if (joint.parent >= 0) { + const segment = new THREE.Mesh(cylinderGeometry, this.boneMaterial); + segment.castShadow = !options.diamonds; + segment.renderOrder = options.renderOrder; + this.segments.push({mesh: segment, child: index, parent: joint.parent}); + this.group.add(segment); + } + } + scene.add(this.bones[0]); + scene.add(this.group); + this.label = options.label ? labelSprite(options.label, options.labelColor, options.opacity) : null; + if (this.label) scene.add(this.label); + } + + pose(roots, rotations, frame, frameCount) { + const index = Math.min(frameCount - 1, Math.max(0, frame)); + this.bones[0].position.fromArray(roots, index * 3); + for (let joint = 0; joint < this.bones.length; joint++) { + this.bones[joint].quaternion.fromArray(rotations, (index * this.bones.length + joint) * 4); + } + this.updateGeometry(); + } + + updateGeometry() { + this.bones[0].updateMatrixWorld(true); + for (let index = 0; index < this.bones.length; index++) { + this.bones[index].getWorldPosition(endPoint); + const marker = this.jointMeshes[index]; + marker.position.copy(endPoint); + const radius = index === 0 ? this.rootRadius : this.jointRadius; + marker.scale.setScalar(radius); + } + for (const segment of this.segments) { + this.bones[segment.parent].getWorldPosition(startPoint); + this.bones[segment.child].getWorldPosition(endPoint); + direction.subVectors(endPoint, startPoint); + const length = direction.length(); + midpoint.addVectors(startPoint, endPoint).multiplyScalar(0.5); + segment.mesh.position.copy(midpoint); + segment.mesh.quaternion.setFromUnitVectors(up, direction.normalize()); + segment.mesh.scale.set(this.radius, length, this.radius); + } + if (this.label) { + this.bones[0].getWorldPosition(endPoint); + this.label.position.set(endPoint.x, endPoint.y + this.labelHeight, endPoint.z); + } + } + + setVisible(visible) { + this.group.visible = visible; + this.bones[0].visible = visible; + if (this.label) this.label.visible = visible; + } + + setOpacity(opacity) { + for (const material of [this.boneMaterial, this.jointMaterial, this.rootMaterial]) { + material.opacity = opacity; + material.transparent = opacity < 1; + material.depthWrite = opacity >= 0.95; + } + if (this.label) this.label.material.opacity = opacity; + } +} + +function makeLine(color, dashed = false) { + const material = dashed + ? new THREE.LineDashedMaterial({color, dashSize: 0.09, gapSize: 0.055, transparent: true, opacity: 0.9}) + : new THREE.LineBasicMaterial({color, transparent: true, opacity: 0.72}); + const line = new THREE.Line(new THREE.BufferGeometry(), material); + line.renderOrder = 1; + scene.add(line); + return line; +} + +function setGroundPath(line, roots, frames) { + const points = []; + for (let frame = 0; frame < frames; frame++) { + points.push(new THREE.Vector3(roots[frame * 3], 0.018, roots[frame * 3 + 2])); + } + line.geometry.dispose(); + line.geometry = new THREE.BufferGeometry().setFromPoints(points); + if (line.material.isLineDashedMaterial) line.computeLineDistances(); +} + +function makeSkeletons(joints) { + state.rig = new SkeletonRig(joints, { + color: 0x55efc4, jointColor: 0xd9fff3, rootColor: 0xffd166, + emissive: 0x0c5b49, rootEmissive: 0x6a3b00, emissiveIntensity: 0.8, + radius: 0.022, jointRadius: 0.034, rootRadius: 0.062, + opacity: 1, diamonds: false, renderOrder: 5, + }); + for (let frame = 0; frame < 4; frame++) { + state.targetRigs.push(new SkeletonRig(joints, { + color: 0xff763b, jointColor: 0xffb06b, rootColor: 0xff4f8b, + emissive: 0x7b1f00, rootEmissive: 0x790025, emissiveIntensity: 1, + radius: 0.018, jointRadius: 0.032, rootRadius: 0.056, opacity: 0.9, diamonds: true, + renderOrder: 8 + frame, label: `T${frame}`, labelColor: frame === 3 ? '#ffb477' : '#c97354', + labelHeight: 0.34 + (3 - frame) * 0.11, + })); + } + state.generatedPath = makeLine(0x43e8bf); + state.targetPath = makeLine(0xff6b3d, true); +} + +function updateTargetVisibility() { + const frames = state.targets?.frames ?? 0; + const maximum = Math.max(0, frames - 1); + targetFrame.max = String(maximum); + const selected = THREE.MathUtils.clamp(Number(targetFrame.value), 0, maximum); + targetFrame.value = String(selected); + targetFrameLabel.textContent = `T${selected}`; + const overlay = showAllTargets.checked; + const overlayOpacities = [0.16, 0.25, 0.42, 0.9]; + for (let frame = 0; frame < state.targetRigs.length; frame++) { + const exists = frame < frames; + state.targetRigs[frame].setOpacity(overlay ? overlayOpacities[frame] : 0.9); + state.targetRigs[frame].setVisible(exists && (overlay || frame === selected)); + } + state.targetPath.visible = frames > 0; + targetInfo.textContent = frames > 0 + ? (overlay ? `${frames} consecutive constraints overlaid` : `T${selected} of ${frames} consecutive constraints`) + : '—'; +} + +const cameraView = {yaw: 0.68, pitch: 0.24, distance: 4.8, dragging: false, x: 0, y: 0}; +const focus = new THREE.Vector3(); +const desiredCamera = new THREE.Vector3(); +const visibleBounds = new THREE.Box3(); +function resetCameraView() { + cameraView.yaw = 0.68; + cameraView.pitch = 0.24; + cameraView.distance = 4.8; +} +resetCamera.addEventListener('click', resetCameraView); +renderer.domElement.addEventListener('pointerdown', event => { + cameraView.dragging = true; cameraView.x = event.clientX; cameraView.y = event.clientY; + renderer.domElement.setPointerCapture(event.pointerId); +}); +renderer.domElement.addEventListener('pointermove', event => { + if (!cameraView.dragging) return; + cameraView.yaw -= (event.clientX - cameraView.x) * 0.006; + cameraView.pitch = THREE.MathUtils.clamp(cameraView.pitch + (event.clientY - cameraView.y) * 0.004, -0.05, 1.05); + cameraView.x = event.clientX; cameraView.y = event.clientY; +}); +renderer.domElement.addEventListener('pointerup', event => { + cameraView.dragging = false; renderer.domElement.releasePointerCapture(event.pointerId); +}); +renderer.domElement.addEventListener('pointercancel', () => { cameraView.dragging = false; }); +renderer.domElement.addEventListener('wheel', event => { + cameraView.distance = THREE.MathUtils.clamp(cameraView.distance * Math.exp(event.deltaY * 0.001), 2.1, 8); + event.preventDefault(); +}, {passive: false}); +renderer.domElement.addEventListener('dblclick', resetCameraView); + +function resize() { + const width = Math.max(1, viewport.clientWidth), height = Math.max(1, viewport.clientHeight); + renderer.setSize(width, height, false); + camera.aspect = width / height; + camera.updateProjectionMatrix(); +} +addEventListener('resize', resize); +resize(); + +async function api(path, body) { + const response = await fetch(path, { + method: body ? 'POST' : 'GET', headers: {'Content-Type': 'application/json'}, + body: body ? JSON.stringify(body) : undefined, + }); + const value = await response.json(); + if (!response.ok) throw new Error(value.error || `${response.status} ${response.statusText}`); + return value; +} + +function installStyles(styles) { + for (const style of styles) { + const option = document.createElement('option'); + option.value = style.name; + option.textContent = `${style.name.replaceAll('_', ' ')} · ${style.speed.toFixed(1)} m/s`; + styleSelect.append(option); + } + state.style = styles.some(item => item.name === 'walk') ? 'walk' : styles[0].name; + styleSelect.value = state.style; + styleSelect.addEventListener('change', () => { state.style = styleSelect.value; void requestPlan(); }); +} + +function useMotion(response, plannedMove = state.move) { + state.session = response.session; + state.style = response.style; + styleSelect.value = response.style; + state.motion = response.motion; + state.targets = response.targets; + state.playhead = 0; + state.lastTime = performance.now(); + planInfo.textContent = `${response.motion.frames} frames · ${response.style.replaceAll('_', ' ')}`; + statusElement.textContent = 'Playing'; + document.documentElement.dataset.planSequence = String(Number(document.documentElement.dataset.planSequence || 0) + 1); + document.documentElement.dataset.plannedMoveX = String(plannedMove[0]); + document.documentElement.dataset.plannedMoveZ = String(plannedMove[1]); + setGroundPath(state.generatedPath, response.motion.roots, response.motion.frames); + setGroundPath(state.targetPath, response.targets.roots, response.targets.frames); + for (let frame = 0; frame < response.targets.frames; frame++) { + state.targetRigs[frame].pose(response.targets.roots, response.targets.rotations, frame, response.targets.frames); + } + updateTargetVisibility(); +} + +async function requestPlan(advance = Math.floor(state.playhead)) { + if (!state.session) return null; + if (state.pending) { + state.replanQueued = true; + return null; + } + state.pending = true; + statusElement.textContent = 'Planning…'; + const plannedMove = [...state.move]; + try { + const response = await api('/api/plan', { + session: state.session, style: state.style, move: state.move, facing: state.facing, + seed: state.seed++, advance, + }); + useMotion(response, plannedMove); + return response; + } finally { + state.pending = false; + if (state.replanQueued) { + state.replanQueued = false; + queueMicrotask(() => void requestPlan(Math.floor(state.playhead))); + } + } +} + +function updateControl() { + let x = 0, z = 0; + const active = key => state.keys.has(key) || state.padKey === key; + if (active('w')) z += 1; + if (active('s')) z -= 1; + if (active('a')) x -= 1; + if (active('d')) x += 1; + const length = Math.hypot(x, z); + if (length > 0) { x /= length; z /= length; state.facing = [x, z]; } + state.move = [x, z]; + document.querySelectorAll('.pad button').forEach(button => { + const pressed = active(button.dataset.key); + button.classList.toggle('active', pressed); + button.setAttribute('aria-pressed', String(pressed)); + }); +} + +let controlTimer = 0; +function schedulePlan(delay = 0) { clearTimeout(controlTimer); controlTimer = setTimeout(() => void requestPlan(), delay); } +addEventListener('keydown', event => { + const key = event.key.toLowerCase(); + if (['w', 'a', 's', 'd'].includes(key) && !state.keys.has(key)) { + state.keys.add(key); updateControl(); schedulePlan(); event.preventDefault(); + } + if (event.code === 'Space' || event.key === 'Escape') { + state.keys.clear(); state.padKey = ''; updateControl(); schedulePlan(); event.preventDefault(); + } + if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') { + const angle = Math.atan2(state.facing[0], state.facing[1]) + (event.key === 'ArrowLeft' ? 0.25 : -0.25); + state.facing = [Math.sin(angle), Math.cos(angle)]; schedulePlan(35); event.preventDefault(); + } +}); +addEventListener('keyup', event => { + const key = event.key.toLowerCase(); + if (state.keys.delete(key)) { updateControl(); schedulePlan(); } +}); +for (const button of document.querySelectorAll('.pad button')) { + button.setAttribute('aria-pressed', 'false'); + button.addEventListener('click', event => { + state.padKey = state.padKey === button.dataset.key ? '' : button.dataset.key; + updateControl(); + schedulePlan(); + event.preventDefault(); + }); +} +showAllTargets.addEventListener('change', updateTargetVisibility); +targetFrame.addEventListener('input', updateTargetVisibility); + +function updateCamera() { + visibleBounds.makeEmpty(); + for (const marker of state.rig.jointMeshes) visibleBounds.expandByPoint(marker.position); + visibleBounds.getCenter(focus); + const horizontal = Math.cos(cameraView.pitch) * cameraView.distance; + desiredCamera.set( + focus.x + Math.sin(cameraView.yaw) * horizontal, + focus.y + Math.sin(cameraView.pitch) * cameraView.distance, + focus.z + Math.cos(cameraView.yaw) * horizontal, + ); + if (camera.userData.positioned) camera.position.lerp(desiredCamera, 0.12); + else { camera.position.copy(desiredCamera); camera.userData.positioned = true; } + camera.lookAt(focus); +} + +function renderMotion(frame) { + if (!state.motion) return; + const index = Math.min(state.motion.frames - 1, Math.max(0, frame)); + state.rig.pose(state.motion.roots, state.motion.rotations, index, state.motion.frames); + updateCamera(); +} + +function animate(now) { + requestAnimationFrame(animate); + const delta = Math.min(0.1, (now - state.lastTime) / 1000); + state.lastTime = now; + if (state.motion) { + state.playhead += delta * state.meta.fps; + if (state.playhead >= state.motion.frames - 5 && !state.pending) void requestPlan(Math.floor(state.playhead)); + renderMotion(Math.floor(state.playhead)); + } + renderer.render(scene, camera); +} + +async function selfTest() { + const alternate = state.meta.styles.find(item => item.name === 'walk_zombie') || state.meta.styles.find(item => item.name !== 'walk'); + if (!alternate) throw new Error('no alternate upstream style available'); + state.style = alternate.name; + styleSelect.value = alternate.name; + state.move = [1, 0]; + state.facing = [1, 0]; + const response = await requestPlan(3); + if (!response || response.style !== alternate.name) throw new Error('style change was not applied'); + if (response.motion.joints !== 34 || response.motion.frames < 24 || response.motion.rotations.length !== response.motion.frames * 34 * 4) { + throw new Error('invalid skeletal animation response'); + } + if (response.targets.frames !== 4 || response.targets.joints !== 34 || response.targets.roots.length !== 12 || response.targets.rotations.length !== 4 * 34 * 4) { + throw new Error('invalid placed target-keyframe response'); + } + renderMotion(2); + renderer.render(scene, camera); + let visibleTargets = state.targetRigs.filter(rig => rig.group.visible).length; + if (!renderer.domElement.width || state.rig.bones.length !== 34 || visibleTargets !== 1 || !state.targetRigs[3].group.visible) { + throw new Error('animated and target skeletons were not rendered'); + } + const expectedFocus = new THREE.Vector3(); + const animatedBounds = new THREE.Box3(); + for (const marker of state.rig.jointMeshes) animatedBounds.expandByPoint(marker.position); + animatedBounds.getCenter(expectedFocus); + if (focus.distanceTo(expectedFocus) > 1e-6) throw new Error('camera is not anchored to the animated skeleton'); + targetFrame.value = '1'; + updateTargetVisibility(); + if (!state.targetRigs[1].group.visible || state.targetRigs.filter(rig => rig.group.visible).length !== 1) { + throw new Error('target-frame inspector did not select T1'); + } + showAllTargets.checked = true; + updateTargetVisibility(); + visibleTargets = state.targetRigs.filter(rig => rig.group.visible).length; + if (visibleTargets !== 4) throw new Error('four-pose target overlay did not render'); + showAllTargets.checked = false; + targetFrame.value = '3'; + updateTargetVisibility(); + document.documentElement.dataset.animatedJoints = String(state.rig.bones.length); + document.documentElement.dataset.targetFrames = String(response.targets.frames); + document.documentElement.dataset.visibleTargets = '1'; + document.documentElement.dataset.cameraSubject = 'animated'; + document.documentElement.dataset.testStatus = 'passed'; + testResult.textContent = `Headless check passed: animated-camera anchor + target inspector/overlay, ${alternate.name}, right turn`; +} + +async function start() { + try { + state.meta = await api('/api/meta'); + installStyles(state.meta.styles); + makeSkeletons(state.meta.joints); + const initial = await api('/api/session', {style: state.style}); + useMotion(initial); + renderMotion(0); + requestAnimationFrame(animate); + if (query.get('test') === '1') await selfTest(); + else document.documentElement.dataset.testStatus = 'ready'; + } catch (error) { + console.error(error); + statusElement.textContent = 'Error'; + testResult.textContent = error.message; + document.documentElement.dataset.testStatus = 'failed'; + } +} + +await start(); diff --git a/demo/web/index.html b/demo/web/index.html new file mode 100644 index 0000000000000000000000000000000000000000..98289870bcd87ce3ece8c69fd6489fde896dbb65 --- /dev/null +++ b/demo/web/index.html @@ -0,0 +1,50 @@ + + + + + + + MotionBricks G1 + + + +
+
+ +
+ + + diff --git a/demo/web/style.css b/demo/web/style.css new file mode 100644 index 0000000000000000000000000000000000000000..5b82cabc3fdac3272146349af2bde94c4ab9efc0 --- /dev/null +++ b/demo/web/style.css @@ -0,0 +1,39 @@ +:root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui, sans-serif; background:#090b10; color:#edf1f7; } +* { box-sizing:border-box; } +html, body, main { width:100%; height:100%; margin:0; overflow:hidden; } +main { display:grid; grid-template-columns:minmax(0,1fr) 320px; } +#viewport { min-width:0; background:radial-gradient(circle at 50% 36%,#20293a 0,#10141d 38%,#080a0f 76%); } +#viewport canvas { display:block; width:100%; height:100%; } +aside { padding:36px 28px; border-left:1px solid #2a3240; background:rgba(11,14,20,.96); box-shadow:-18px 0 60px #0008; } +.eyebrow { margin:0 0 10px; color:#6be6ba; letter-spacing:.18em; font-size:11px; font-weight:750; } +h1 { margin:0 0 12px; font-size:30px; letter-spacing:-.04em; } +.lede { color:#9eabbc; line-height:1.45; margin:0 0 18px; } +.legend { display:flex; gap:16px; margin:0 0 24px; color:#aeb9c8; font-size:12px; } +.legend span { display:flex; align-items:center; gap:7px; } +.legend i { display:block; width:20px; height:5px; border-radius:4px; box-shadow:0 0 10px currentColor; } +.animated-swatch { color:#55efc4; background:#55efc4; } +.target-swatch { color:#ff763b; background:#ff763b; transform:rotate(-8deg); } +label { display:block; color:#c8d1dd; font-size:12px; font-weight:700; margin-bottom:8px; } +select { width:100%; color:#f2f6fb; background:#171c26; border:1px solid #343d4d; border-radius:8px; padding:11px; font:inherit; } +.target-frame-control { margin-top:16px; } +.target-frame-control label { display:flex; justify-content:space-between; margin-bottom:6px; } +.target-frame-control output { color:#ff9a68; font-variant-numeric:tabular-nums; } +#target-frame { display:block; width:100%; margin:0; accent-color:#ff763b; } +.view-options { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:14px; } +.view-options label { display:flex; align-items:center; gap:7px; margin:0; font-weight:600; color:#b7c2d0; } +.view-options input { accent-color:#ff763b; } +#reset-camera { color:#dbe4ef; background:#171c26; border:1px solid #343d4d; border-radius:7px; padding:7px 9px; font:inherit; font-size:11px; white-space:nowrap; } +#reset-camera:hover { border-color:#59677b; background:#202735; } +.target-help, .camera-help { margin:9px 0 0; color:#647286; font-size:11px; line-height:1.35; } +.pad { margin:28px auto; width:176px; display:grid; grid-template:52px 52px / 52px 52px 52px; gap:8px; } +.pad button { border:1px solid #3b4658; border-radius:10px; background:#181e29; color:#dae3ed; font-weight:800; font-size:16px; box-shadow:0 4px 12px #0005; } +.pad button:first-child { grid-column:2; } +.pad button:nth-child(2) { grid-row:2; grid-column:1; } +.pad button:nth-child(3) { grid-row:2; grid-column:2; } +.pad button:nth-child(4) { grid-row:2; grid-column:3; } +.pad button.active { background:#2e735f; border-color:#77f0c4; transform:translateY(1px); } +dl { border-top:1px solid #2b3341; margin-top:30px; padding-top:10px; } +dl div { display:flex; justify-content:space-between; gap:10px; padding:8px 0; } +dt { color:#778599; } dd { margin:0; text-align:right; color:#dce4ee; } +#test-result { min-height:1.3em; color:#6be6ba; font-size:12px; } +@media (max-width:760px) { main { grid-template-columns:1fr; grid-template-rows:minmax(0,1fr) auto; } aside { padding:16px 20px; border-left:0; border-top:1px solid #2a3240; } .lede, dl, .target-help, .camera-help { display:none; } .legend { margin-bottom:12px; } .view-options { max-width:420px; } .pad { position:absolute; right:18px; bottom:12px; margin:0; transform:scale(.8); transform-origin:bottom right; } } diff --git a/demo/web/vendor/THREE-LICENSE.txt b/demo/web/vendor/THREE-LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..cf781430404b0ae0ba7d8dea3457a3515dd7c9b9 --- /dev/null +++ b/demo/web/vendor/THREE-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright © 2010-2025 three.js authors + +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/demo/web/vendor/three.core.min.js b/demo/web/vendor/three.core.min.js new file mode 100644 index 0000000000000000000000000000000000000000..70c40977daa0f6e1e312c6b58e9bfe49cb5a87a4 --- /dev/null +++ b/demo/web/vendor/three.core.min.js @@ -0,0 +1,6 @@ +/** + * @license + * Copyright 2010-2025 Three.js Authors + * SPDX-License-Identifier: MIT + */ +const t="180",e={LEFT:0,MIDDLE:1,RIGHT:2,ROTATE:0,DOLLY:1,PAN:2},s={ROTATE:0,PAN:1,DOLLY_PAN:2,DOLLY_ROTATE:3},i=0,r=1,n=2,a=3,o=0,h=1,l=2,c=3,u=0,d=1,p=2,m=0,y=1,g=2,f=3,x=4,b=5,v=100,w=101,M=102,S=103,_=104,A=200,T=201,z=202,C=203,I=204,B=205,k=206,E=207,R=208,P=209,O=210,N=211,V=212,F=213,L=214,j=0,D=1,W=2,U=3,H=4,q=5,J=6,X=7,Y=0,Z=1,G=2,$=0,Q=1,K=2,tt=3,et=4,st=5,it=6,rt=7,nt="attached",at="detached",ot=300,ht=301,lt=302,ct=303,ut=304,dt=306,pt=1e3,mt=1001,yt=1002,gt=1003,ft=1004,xt=1004,bt=1005,vt=1005,wt=1006,Mt=1007,St=1007,_t=1008,At=1008,Tt=1009,zt=1010,Ct=1011,It=1012,Bt=1013,kt=1014,Et=1015,Rt=1016,Pt=1017,Ot=1018,Nt=1020,Vt=35902,Ft=35899,Lt=1021,jt=1022,Dt=1023,Wt=1026,Ut=1027,Ht=1028,qt=1029,Jt=1030,Xt=1031,Yt=1032,Zt=1033,Gt=33776,$t=33777,Qt=33778,Kt=33779,te=35840,ee=35841,se=35842,ie=35843,re=36196,ne=37492,ae=37496,oe=37808,he=37809,le=37810,ce=37811,ue=37812,de=37813,pe=37814,me=37815,ye=37816,ge=37817,fe=37818,xe=37819,be=37820,ve=37821,we=36492,Me=36494,Se=36495,_e=36283,Ae=36284,Te=36285,ze=36286,Ce=2200,Ie=2201,Be=2202,ke=2300,Ee=2301,Re=2302,Pe=2400,Oe=2401,Ne=2402,Ve=2500,Fe=2501,Le=0,je=1,De=2,We=3200,Ue=3201,He=3202,qe=3203,Je=0,Xe=1,Ye="",Ze="srgb",Ge="srgb-linear",$e="linear",Qe="srgb",Ke=0,ts=7680,es=7681,ss=7682,is=7683,rs=34055,ns=34056,as=5386,os=512,hs=513,ls=514,cs=515,us=516,ds=517,ps=518,ms=519,ys=512,gs=513,fs=514,xs=515,bs=516,vs=517,ws=518,Ms=519,Ss=35044,_s=35048,As=35040,Ts=35045,zs=35049,Cs=35041,Is=35046,Bs=35050,ks=35042,Es="100",Rs="300 es",Ps=2e3,Os=2001,Ns={COMPUTE:"compute",RENDER:"render"},Vs={PERSPECTIVE:"perspective",LINEAR:"linear",FLAT:"flat"},Fs={NORMAL:"normal",CENTROID:"centroid",SAMPLE:"sample",FIRST:"first",EITHER:"either"};class Ls{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const s=this._listeners;void 0===s[t]&&(s[t]=[]),-1===s[t].indexOf(e)&&s[t].push(e)}hasEventListener(t,e){const s=this._listeners;return void 0!==s&&(void 0!==s[t]&&-1!==s[t].indexOf(e))}removeEventListener(t,e){const s=this._listeners;if(void 0===s)return;const i=s[t];if(void 0!==i){const t=i.indexOf(e);-1!==t&&i.splice(t,1)}}dispatchEvent(t){const e=this._listeners;if(void 0===e)return;const s=e[t.type];if(void 0!==s){t.target=this;const e=s.slice(0);for(let s=0,i=e.length;s>8&255]+js[t>>16&255]+js[t>>24&255]+"-"+js[255&e]+js[e>>8&255]+"-"+js[e>>16&15|64]+js[e>>24&255]+"-"+js[63&s|128]+js[s>>8&255]+"-"+js[s>>16&255]+js[s>>24&255]+js[255&i]+js[i>>8&255]+js[i>>16&255]+js[i>>24&255]).toLowerCase()}function qs(t,e,s){return Math.max(e,Math.min(s,t))}function Js(t,e){return(t%e+e)%e}function Xs(t,e,s){return(1-s)*t+s*e}function Ys(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return t/4294967295;case Uint16Array:return t/65535;case Uint8Array:return t/255;case Int32Array:return Math.max(t/2147483647,-1);case Int16Array:return Math.max(t/32767,-1);case Int8Array:return Math.max(t/127,-1);default:throw new Error("Invalid component type.")}}function Zs(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return Math.round(4294967295*t);case Uint16Array:return Math.round(65535*t);case Uint8Array:return Math.round(255*t);case Int32Array:return Math.round(2147483647*t);case Int16Array:return Math.round(32767*t);case Int8Array:return Math.round(127*t);default:throw new Error("Invalid component type.")}}const Gs={DEG2RAD:Ws,RAD2DEG:Us,generateUUID:Hs,clamp:qs,euclideanModulo:Js,mapLinear:function(t,e,s,i,r){return i+(t-e)*(r-i)/(s-e)},inverseLerp:function(t,e,s){return t!==e?(s-t)/(e-t):0},lerp:Xs,damp:function(t,e,s,i){return Xs(t,e,1-Math.exp(-s*i))},pingpong:function(t,e=1){return e-Math.abs(Js(t,2*e)-e)},smoothstep:function(t,e,s){return t<=e?0:t>=s?1:(t=(t-e)/(s-e))*t*(3-2*t)},smootherstep:function(t,e,s){return t<=e?0:t>=s?1:(t=(t-e)/(s-e))*t*t*(t*(6*t-15)+10)},randInt:function(t,e){return t+Math.floor(Math.random()*(e-t+1))},randFloat:function(t,e){return t+Math.random()*(e-t)},randFloatSpread:function(t){return t*(.5-Math.random())},seededRandom:function(t){void 0!==t&&(Ds=t);let e=Ds+=1831565813;return e=Math.imul(e^e>>>15,1|e),e^=e+Math.imul(e^e>>>7,61|e),((e^e>>>14)>>>0)/4294967296},degToRad:function(t){return t*Ws},radToDeg:function(t){return t*Us},isPowerOfTwo:function(t){return!(t&t-1)&&0!==t},ceilPowerOfTwo:function(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))},floorPowerOfTwo:function(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))},setQuaternionFromProperEuler:function(t,e,s,i,r){const n=Math.cos,a=Math.sin,o=n(s/2),h=a(s/2),l=n((e+i)/2),c=a((e+i)/2),u=n((e-i)/2),d=a((e-i)/2),p=n((i-e)/2),m=a((i-e)/2);switch(r){case"XYX":t.set(o*c,h*u,h*d,o*l);break;case"YZY":t.set(h*d,o*c,h*u,o*l);break;case"ZXZ":t.set(h*u,h*d,o*c,o*l);break;case"XZX":t.set(o*c,h*m,h*p,o*l);break;case"YXY":t.set(h*p,o*c,h*m,o*l);break;case"ZYZ":t.set(h*m,h*p,o*c,o*l);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}},normalize:Zs,denormalize:Ys};class $s{constructor(t=0,e=0){$s.prototype.isVector2=!0,this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t){return this.x+=t.x,this.y+=t.y,this}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=this.x,s=this.y,i=t.elements;return this.x=i[0]*e+i[3]*s+i[6],this.y=i[1]*e+i[4]*s+i[7],this}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this}clamp(t,e){return this.x=qs(this.x,t.x,e.x),this.y=qs(this.y,t.y,e.y),this}clampScalar(t,e){return this.x=qs(this.x,t,e),this.y=qs(this.y,t,e),this}clampLength(t,e){const s=this.length();return this.divideScalar(s||1).multiplyScalar(qs(s,t,e))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const s=this.dot(t)/e;return Math.acos(qs(s,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,s=this.y-t.y;return e*e+s*s}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this}lerpVectors(t,e,s){return this.x=t.x+(e.x-t.x)*s,this.y=t.y+(e.y-t.y)*s,this}equals(t){return t.x===this.x&&t.y===this.y}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this}rotateAround(t,e){const s=Math.cos(e),i=Math.sin(e),r=this.x-t.x,n=this.y-t.y;return this.x=r*s-n*i+t.x,this.y=r*i+n*s+t.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}*[Symbol.iterator](){yield this.x,yield this.y}}class Qs{constructor(t=0,e=0,s=0,i=1){this.isQuaternion=!0,this._x=t,this._y=e,this._z=s,this._w=i}static slerpFlat(t,e,s,i,r,n,a){let o=s[i+0],h=s[i+1],l=s[i+2],c=s[i+3];const u=r[n+0],d=r[n+1],p=r[n+2],m=r[n+3];if(0===a)return t[e+0]=o,t[e+1]=h,t[e+2]=l,void(t[e+3]=c);if(1===a)return t[e+0]=u,t[e+1]=d,t[e+2]=p,void(t[e+3]=m);if(c!==m||o!==u||h!==d||l!==p){let t=1-a;const e=o*u+h*d+l*p+c*m,s=e>=0?1:-1,i=1-e*e;if(i>Number.EPSILON){const r=Math.sqrt(i),n=Math.atan2(r,e*s);t=Math.sin(t*n)/r,a=Math.sin(a*n)/r}const r=a*s;if(o=o*t+u*r,h=h*t+d*r,l=l*t+p*r,c=c*t+m*r,t===1-a){const t=1/Math.sqrt(o*o+h*h+l*l+c*c);o*=t,h*=t,l*=t,c*=t}}t[e]=o,t[e+1]=h,t[e+2]=l,t[e+3]=c}static multiplyQuaternionsFlat(t,e,s,i,r,n){const a=s[i],o=s[i+1],h=s[i+2],l=s[i+3],c=r[n],u=r[n+1],d=r[n+2],p=r[n+3];return t[e]=a*p+l*c+o*d-h*u,t[e+1]=o*p+l*u+h*c-a*d,t[e+2]=h*p+l*d+a*u-o*c,t[e+3]=l*p-a*c-o*u-h*d,t}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get w(){return this._w}set w(t){this._w=t,this._onChangeCallback()}set(t,e,s,i){return this._x=t,this._y=e,this._z=s,this._w=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(t){return this._x=t.x,this._y=t.y,this._z=t.z,this._w=t.w,this._onChangeCallback(),this}setFromEuler(t,e=!0){const s=t._x,i=t._y,r=t._z,n=t._order,a=Math.cos,o=Math.sin,h=a(s/2),l=a(i/2),c=a(r/2),u=o(s/2),d=o(i/2),p=o(r/2);switch(n){case"XYZ":this._x=u*l*c+h*d*p,this._y=h*d*c-u*l*p,this._z=h*l*p+u*d*c,this._w=h*l*c-u*d*p;break;case"YXZ":this._x=u*l*c+h*d*p,this._y=h*d*c-u*l*p,this._z=h*l*p-u*d*c,this._w=h*l*c+u*d*p;break;case"ZXY":this._x=u*l*c-h*d*p,this._y=h*d*c+u*l*p,this._z=h*l*p+u*d*c,this._w=h*l*c-u*d*p;break;case"ZYX":this._x=u*l*c-h*d*p,this._y=h*d*c+u*l*p,this._z=h*l*p-u*d*c,this._w=h*l*c+u*d*p;break;case"YZX":this._x=u*l*c+h*d*p,this._y=h*d*c+u*l*p,this._z=h*l*p-u*d*c,this._w=h*l*c-u*d*p;break;case"XZY":this._x=u*l*c-h*d*p,this._y=h*d*c-u*l*p,this._z=h*l*p+u*d*c,this._w=h*l*c+u*d*p;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+n)}return!0===e&&this._onChangeCallback(),this}setFromAxisAngle(t,e){const s=e/2,i=Math.sin(s);return this._x=t.x*i,this._y=t.y*i,this._z=t.z*i,this._w=Math.cos(s),this._onChangeCallback(),this}setFromRotationMatrix(t){const e=t.elements,s=e[0],i=e[4],r=e[8],n=e[1],a=e[5],o=e[9],h=e[2],l=e[6],c=e[10],u=s+a+c;if(u>0){const t=.5/Math.sqrt(u+1);this._w=.25/t,this._x=(l-o)*t,this._y=(r-h)*t,this._z=(n-i)*t}else if(s>a&&s>c){const t=2*Math.sqrt(1+s-a-c);this._w=(l-o)/t,this._x=.25*t,this._y=(i+n)/t,this._z=(r+h)/t}else if(a>c){const t=2*Math.sqrt(1+a-s-c);this._w=(r-h)/t,this._x=(i+n)/t,this._y=.25*t,this._z=(o+l)/t}else{const t=2*Math.sqrt(1+c-s-a);this._w=(n-i)/t,this._x=(r+h)/t,this._y=(o+l)/t,this._z=.25*t}return this._onChangeCallback(),this}setFromUnitVectors(t,e){let s=t.dot(e)+1;return s<1e-8?(s=0,Math.abs(t.x)>Math.abs(t.z)?(this._x=-t.y,this._y=t.x,this._z=0,this._w=s):(this._x=0,this._y=-t.z,this._z=t.y,this._w=s)):(this._x=t.y*e.z-t.z*e.y,this._y=t.z*e.x-t.x*e.z,this._z=t.x*e.y-t.y*e.x,this._w=s),this.normalize()}angleTo(t){return 2*Math.acos(Math.abs(qs(this.dot(t),-1,1)))}rotateTowards(t,e){const s=this.angleTo(t);if(0===s)return this;const i=Math.min(1,e/s);return this.slerp(t,i),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(t){return this._x*t._x+this._y*t._y+this._z*t._z+this._w*t._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let t=this.length();return 0===t?(this._x=0,this._y=0,this._z=0,this._w=1):(t=1/t,this._x=this._x*t,this._y=this._y*t,this._z=this._z*t,this._w=this._w*t),this._onChangeCallback(),this}multiply(t){return this.multiplyQuaternions(this,t)}premultiply(t){return this.multiplyQuaternions(t,this)}multiplyQuaternions(t,e){const s=t._x,i=t._y,r=t._z,n=t._w,a=e._x,o=e._y,h=e._z,l=e._w;return this._x=s*l+n*a+i*h-r*o,this._y=i*l+n*o+r*a-s*h,this._z=r*l+n*h+s*o-i*a,this._w=n*l-s*a-i*o-r*h,this._onChangeCallback(),this}slerp(t,e){if(0===e)return this;if(1===e)return this.copy(t);const s=this._x,i=this._y,r=this._z,n=this._w;let a=n*t._w+s*t._x+i*t._y+r*t._z;if(a<0?(this._w=-t._w,this._x=-t._x,this._y=-t._y,this._z=-t._z,a=-a):this.copy(t),a>=1)return this._w=n,this._x=s,this._y=i,this._z=r,this;const o=1-a*a;if(o<=Number.EPSILON){const t=1-e;return this._w=t*n+e*this._w,this._x=t*s+e*this._x,this._y=t*i+e*this._y,this._z=t*r+e*this._z,this.normalize(),this}const h=Math.sqrt(o),l=Math.atan2(h,a),c=Math.sin((1-e)*l)/h,u=Math.sin(e*l)/h;return this._w=n*c+this._w*u,this._x=s*c+this._x*u,this._y=i*c+this._y*u,this._z=r*c+this._z*u,this._onChangeCallback(),this}slerpQuaternions(t,e,s){return this.copy(t).slerp(e,s)}random(){const t=2*Math.PI*Math.random(),e=2*Math.PI*Math.random(),s=Math.random(),i=Math.sqrt(1-s),r=Math.sqrt(s);return this.set(i*Math.sin(t),i*Math.cos(t),r*Math.sin(e),r*Math.cos(e))}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._w===this._w}fromArray(t,e=0){return this._x=t[e],this._y=t[e+1],this._z=t[e+2],this._w=t[e+3],this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._w,t}fromBufferAttribute(t,e){return this._x=t.getX(e),this._y=t.getY(e),this._z=t.getZ(e),this._w=t.getW(e),this._onChangeCallback(),this}toJSON(){return this.toArray()}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._w}}class Ks{constructor(t=0,e=0,s=0){Ks.prototype.isVector3=!0,this.x=t,this.y=e,this.z=s}set(t,e,s){return void 0===s&&(s=this.z),this.x=t,this.y=e,this.z=s,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this}multiplyVectors(t,e){return this.x=t.x*e.x,this.y=t.y*e.y,this.z=t.z*e.z,this}applyEuler(t){return this.applyQuaternion(ei.setFromEuler(t))}applyAxisAngle(t,e){return this.applyQuaternion(ei.setFromAxisAngle(t,e))}applyMatrix3(t){const e=this.x,s=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[3]*s+r[6]*i,this.y=r[1]*e+r[4]*s+r[7]*i,this.z=r[2]*e+r[5]*s+r[8]*i,this}applyNormalMatrix(t){return this.applyMatrix3(t).normalize()}applyMatrix4(t){const e=this.x,s=this.y,i=this.z,r=t.elements,n=1/(r[3]*e+r[7]*s+r[11]*i+r[15]);return this.x=(r[0]*e+r[4]*s+r[8]*i+r[12])*n,this.y=(r[1]*e+r[5]*s+r[9]*i+r[13])*n,this.z=(r[2]*e+r[6]*s+r[10]*i+r[14])*n,this}applyQuaternion(t){const e=this.x,s=this.y,i=this.z,r=t.x,n=t.y,a=t.z,o=t.w,h=2*(n*i-a*s),l=2*(a*e-r*i),c=2*(r*s-n*e);return this.x=e+o*h+n*c-a*l,this.y=s+o*l+a*h-r*c,this.z=i+o*c+r*l-n*h,this}project(t){return this.applyMatrix4(t.matrixWorldInverse).applyMatrix4(t.projectionMatrix)}unproject(t){return this.applyMatrix4(t.projectionMatrixInverse).applyMatrix4(t.matrixWorld)}transformDirection(t){const e=this.x,s=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[4]*s+r[8]*i,this.y=r[1]*e+r[5]*s+r[9]*i,this.z=r[2]*e+r[6]*s+r[10]*i,this.normalize()}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this}divideScalar(t){return this.multiplyScalar(1/t)}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this.z=Math.min(this.z,t.z),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this.z=Math.max(this.z,t.z),this}clamp(t,e){return this.x=qs(this.x,t.x,e.x),this.y=qs(this.y,t.y,e.y),this.z=qs(this.z,t.z,e.z),this}clampScalar(t,e){return this.x=qs(this.x,t,e),this.y=qs(this.y,t,e),this.z=qs(this.z,t,e),this}clampLength(t,e){const s=this.length();return this.divideScalar(s||1).multiplyScalar(qs(s,t,e))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this.z=Math.trunc(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(t){return this.x*t.x+this.y*t.y+this.z*t.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this.z+=(t.z-this.z)*e,this}lerpVectors(t,e,s){return this.x=t.x+(e.x-t.x)*s,this.y=t.y+(e.y-t.y)*s,this.z=t.z+(e.z-t.z)*s,this}cross(t){return this.crossVectors(this,t)}crossVectors(t,e){const s=t.x,i=t.y,r=t.z,n=e.x,a=e.y,o=e.z;return this.x=i*o-r*a,this.y=r*n-s*o,this.z=s*a-i*n,this}projectOnVector(t){const e=t.lengthSq();if(0===e)return this.set(0,0,0);const s=t.dot(this)/e;return this.copy(t).multiplyScalar(s)}projectOnPlane(t){return ti.copy(this).projectOnVector(t),this.sub(ti)}reflect(t){return this.sub(ti.copy(t).multiplyScalar(2*this.dot(t)))}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const s=this.dot(t)/e;return Math.acos(qs(s,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,s=this.y-t.y,i=this.z-t.z;return e*e+s*s+i*i}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)+Math.abs(this.z-t.z)}setFromSpherical(t){return this.setFromSphericalCoords(t.radius,t.phi,t.theta)}setFromSphericalCoords(t,e,s){const i=Math.sin(e)*t;return this.x=i*Math.sin(s),this.y=Math.cos(e)*t,this.z=i*Math.cos(s),this}setFromCylindrical(t){return this.setFromCylindricalCoords(t.radius,t.theta,t.y)}setFromCylindricalCoords(t,e,s){return this.x=t*Math.sin(e),this.y=s,this.z=t*Math.cos(e),this}setFromMatrixPosition(t){const e=t.elements;return this.x=e[12],this.y=e[13],this.z=e[14],this}setFromMatrixScale(t){const e=this.setFromMatrixColumn(t,0).length(),s=this.setFromMatrixColumn(t,1).length(),i=this.setFromMatrixColumn(t,2).length();return this.x=e,this.y=s,this.z=i,this}setFromMatrixColumn(t,e){return this.fromArray(t.elements,4*e)}setFromMatrix3Column(t,e){return this.fromArray(t.elements,3*e)}setFromEuler(t){return this.x=t._x,this.y=t._y,this.z=t._z,this}setFromColor(t){return this.x=t.r,this.y=t.g,this.z=t.b,this}equals(t){return t.x===this.x&&t.y===this.y&&t.z===this.z}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this.z=t[e+2],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t[e+2]=this.z,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this.z=t.getZ(e),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}randomDirection(){const t=Math.random()*Math.PI*2,e=2*Math.random()-1,s=Math.sqrt(1-e*e);return this.x=s*Math.cos(t),this.y=e,this.z=s*Math.sin(t),this}*[Symbol.iterator](){yield this.x,yield this.y,yield this.z}}const ti=new Ks,ei=new Qs;class si{constructor(t,e,s,i,r,n,a,o,h){si.prototype.isMatrix3=!0,this.elements=[1,0,0,0,1,0,0,0,1],void 0!==t&&this.set(t,e,s,i,r,n,a,o,h)}set(t,e,s,i,r,n,a,o,h){const l=this.elements;return l[0]=t,l[1]=i,l[2]=a,l[3]=e,l[4]=r,l[5]=o,l[6]=s,l[7]=n,l[8]=h,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(t){const e=this.elements,s=t.elements;return e[0]=s[0],e[1]=s[1],e[2]=s[2],e[3]=s[3],e[4]=s[4],e[5]=s[5],e[6]=s[6],e[7]=s[7],e[8]=s[8],this}extractBasis(t,e,s){return t.setFromMatrix3Column(this,0),e.setFromMatrix3Column(this,1),s.setFromMatrix3Column(this,2),this}setFromMatrix4(t){const e=t.elements;return this.set(e[0],e[4],e[8],e[1],e[5],e[9],e[2],e[6],e[10]),this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const s=t.elements,i=e.elements,r=this.elements,n=s[0],a=s[3],o=s[6],h=s[1],l=s[4],c=s[7],u=s[2],d=s[5],p=s[8],m=i[0],y=i[3],g=i[6],f=i[1],x=i[4],b=i[7],v=i[2],w=i[5],M=i[8];return r[0]=n*m+a*f+o*v,r[3]=n*y+a*x+o*w,r[6]=n*g+a*b+o*M,r[1]=h*m+l*f+c*v,r[4]=h*y+l*x+c*w,r[7]=h*g+l*b+c*M,r[2]=u*m+d*f+p*v,r[5]=u*y+d*x+p*w,r[8]=u*g+d*b+p*M,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[3]*=t,e[6]*=t,e[1]*=t,e[4]*=t,e[7]*=t,e[2]*=t,e[5]*=t,e[8]*=t,this}determinant(){const t=this.elements,e=t[0],s=t[1],i=t[2],r=t[3],n=t[4],a=t[5],o=t[6],h=t[7],l=t[8];return e*n*l-e*a*h-s*r*l+s*a*o+i*r*h-i*n*o}invert(){const t=this.elements,e=t[0],s=t[1],i=t[2],r=t[3],n=t[4],a=t[5],o=t[6],h=t[7],l=t[8],c=l*n-a*h,u=a*o-l*r,d=h*r-n*o,p=e*c+s*u+i*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const m=1/p;return t[0]=c*m,t[1]=(i*h-l*s)*m,t[2]=(a*s-i*n)*m,t[3]=u*m,t[4]=(l*e-i*o)*m,t[5]=(i*r-a*e)*m,t[6]=d*m,t[7]=(s*o-h*e)*m,t[8]=(n*e-s*r)*m,this}transpose(){let t;const e=this.elements;return t=e[1],e[1]=e[3],e[3]=t,t=e[2],e[2]=e[6],e[6]=t,t=e[5],e[5]=e[7],e[7]=t,this}getNormalMatrix(t){return this.setFromMatrix4(t).invert().transpose()}transposeIntoArray(t){const e=this.elements;return t[0]=e[0],t[1]=e[3],t[2]=e[6],t[3]=e[1],t[4]=e[4],t[5]=e[7],t[6]=e[2],t[7]=e[5],t[8]=e[8],this}setUvTransform(t,e,s,i,r,n,a){const o=Math.cos(r),h=Math.sin(r);return this.set(s*o,s*h,-s*(o*n+h*a)+n+t,-i*h,i*o,-i*(-h*n+o*a)+a+e,0,0,1),this}scale(t,e){return this.premultiply(ii.makeScale(t,e)),this}rotate(t){return this.premultiply(ii.makeRotation(-t)),this}translate(t,e){return this.premultiply(ii.makeTranslation(t,e)),this}makeTranslation(t,e){return t.isVector2?this.set(1,0,t.x,0,1,t.y,0,0,1):this.set(1,0,t,0,1,e,0,0,1),this}makeRotation(t){const e=Math.cos(t),s=Math.sin(t);return this.set(e,-s,0,s,e,0,0,0,1),this}makeScale(t,e){return this.set(t,0,0,0,e,0,0,0,1),this}equals(t){const e=this.elements,s=t.elements;for(let t=0;t<9;t++)if(e[t]!==s[t])return!1;return!0}fromArray(t,e=0){for(let s=0;s<9;s++)this.elements[s]=t[s+e];return this}toArray(t=[],e=0){const s=this.elements;return t[e]=s[0],t[e+1]=s[1],t[e+2]=s[2],t[e+3]=s[3],t[e+4]=s[4],t[e+5]=s[5],t[e+6]=s[6],t[e+7]=s[7],t[e+8]=s[8],t}clone(){return(new this.constructor).fromArray(this.elements)}}const ii=new si;function ri(t){for(let e=t.length-1;e>=0;--e)if(t[e]>=65535)return!0;return!1}const ni={Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array};function ai(t,e){return new ni[t](e)}function oi(t){return document.createElementNS("http://www.w3.org/1999/xhtml",t)}function hi(){const t=oi("canvas");return t.style.display="block",t}const li={};function ci(t){t in li||(li[t]=!0,console.warn(t))}function ui(t,e,s){return new Promise(function(i,r){setTimeout(function n(){switch(t.clientWaitSync(e,t.SYNC_FLUSH_COMMANDS_BIT,0)){case t.WAIT_FAILED:r();break;case t.TIMEOUT_EXPIRED:setTimeout(n,s);break;default:i()}},s)})}const di=(new si).set(.4123908,.3575843,.1804808,.212639,.7151687,.0721923,.0193308,.1191948,.9505322),pi=(new si).set(3.2409699,-1.5373832,-.4986108,-.9692436,1.8759675,.0415551,.0556301,-.203977,1.0569715);function mi(){const t={enabled:!0,workingColorSpace:Ge,spaces:{},convert:function(t,e,s){return!1!==this.enabled&&e!==s&&e&&s?(this.spaces[e].transfer===Qe&&(t.r=gi(t.r),t.g=gi(t.g),t.b=gi(t.b)),this.spaces[e].primaries!==this.spaces[s].primaries&&(t.applyMatrix3(this.spaces[e].toXYZ),t.applyMatrix3(this.spaces[s].fromXYZ)),this.spaces[s].transfer===Qe&&(t.r=fi(t.r),t.g=fi(t.g),t.b=fi(t.b)),t):t},workingToColorSpace:function(t,e){return this.convert(t,this.workingColorSpace,e)},colorSpaceToWorking:function(t,e){return this.convert(t,e,this.workingColorSpace)},getPrimaries:function(t){return this.spaces[t].primaries},getTransfer:function(t){return""===t?$e:this.spaces[t].transfer},getToneMappingMode:function(t){return this.spaces[t].outputColorSpaceConfig.toneMappingMode||"standard"},getLuminanceCoefficients:function(t,e=this.workingColorSpace){return t.fromArray(this.spaces[e].luminanceCoefficients)},define:function(t){Object.assign(this.spaces,t)},_getMatrix:function(t,e,s){return t.copy(this.spaces[e].toXYZ).multiply(this.spaces[s].fromXYZ)},_getDrawingBufferColorSpace:function(t){return this.spaces[t].outputColorSpaceConfig.drawingBufferColorSpace},_getUnpackColorSpace:function(t=this.workingColorSpace){return this.spaces[t].workingColorSpaceConfig.unpackColorSpace},fromWorkingColorSpace:function(e,s){return ci("THREE.ColorManagement: .fromWorkingColorSpace() has been renamed to .workingToColorSpace()."),t.workingToColorSpace(e,s)},toWorkingColorSpace:function(e,s){return ci("THREE.ColorManagement: .toWorkingColorSpace() has been renamed to .colorSpaceToWorking()."),t.colorSpaceToWorking(e,s)}},e=[.64,.33,.3,.6,.15,.06],s=[.2126,.7152,.0722],i=[.3127,.329];return t.define({[Ge]:{primaries:e,whitePoint:i,transfer:$e,toXYZ:di,fromXYZ:pi,luminanceCoefficients:s,workingColorSpaceConfig:{unpackColorSpace:Ze},outputColorSpaceConfig:{drawingBufferColorSpace:Ze}},[Ze]:{primaries:e,whitePoint:i,transfer:Qe,toXYZ:di,fromXYZ:pi,luminanceCoefficients:s,outputColorSpaceConfig:{drawingBufferColorSpace:Ze}}}),t}const yi=mi();function gi(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}function fi(t){return t<.0031308?12.92*t:1.055*Math.pow(t,.41666)-.055}let xi;class bi{static getDataURL(t,e="image/png"){if(/^data:/i.test(t.src))return t.src;if("undefined"==typeof HTMLCanvasElement)return t.src;let s;if(t instanceof HTMLCanvasElement)s=t;else{void 0===xi&&(xi=oi("canvas")),xi.width=t.width,xi.height=t.height;const e=xi.getContext("2d");t instanceof ImageData?e.putImageData(t,0,0):e.drawImage(t,0,0,t.width,t.height),s=xi}return s.toDataURL(e)}static sRGBToLinear(t){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const e=oi("canvas");e.width=t.width,e.height=t.height;const s=e.getContext("2d");s.drawImage(t,0,0,t.width,t.height);const i=s.getImageData(0,0,t.width,t.height),r=i.data;for(let t=0;t1),this.pmremVersion=0}get width(){return this.source.getSize(_i).x}get height(){return this.source.getSize(_i).y}get depth(){return this.source.getSize(_i).z}get image(){return this.source.data}set image(t=null){this.source.data=t}updateMatrix(){this.matrix.setUvTransform(this.offset.x,this.offset.y,this.repeat.x,this.repeat.y,this.rotation,this.center.x,this.center.y)}addUpdateRange(t,e){this.updateRanges.push({start:t,count:e})}clearUpdateRanges(){this.updateRanges.length=0}clone(){return(new this.constructor).copy(this)}copy(t){return this.name=t.name,this.source=t.source,this.mipmaps=t.mipmaps.slice(0),this.mapping=t.mapping,this.channel=t.channel,this.wrapS=t.wrapS,this.wrapT=t.wrapT,this.magFilter=t.magFilter,this.minFilter=t.minFilter,this.anisotropy=t.anisotropy,this.format=t.format,this.internalFormat=t.internalFormat,this.type=t.type,this.offset.copy(t.offset),this.repeat.copy(t.repeat),this.center.copy(t.center),this.rotation=t.rotation,this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrix.copy(t.matrix),this.generateMipmaps=t.generateMipmaps,this.premultiplyAlpha=t.premultiplyAlpha,this.flipY=t.flipY,this.unpackAlignment=t.unpackAlignment,this.colorSpace=t.colorSpace,this.renderTarget=t.renderTarget,this.isRenderTargetTexture=t.isRenderTargetTexture,this.isArrayTexture=t.isArrayTexture,this.userData=JSON.parse(JSON.stringify(t.userData)),this.needsUpdate=!0,this}setValues(t){for(const e in t){const s=t[e];if(void 0===s){console.warn(`THREE.Texture.setValues(): parameter '${e}' has value of undefined.`);continue}const i=this[e];void 0!==i?i&&s&&i.isVector2&&s.isVector2||i&&s&&i.isVector3&&s.isVector3||i&&s&&i.isMatrix3&&s.isMatrix3?i.copy(s):this[e]=s:console.warn(`THREE.Texture.setValues(): property '${e}' does not exist.`)}}toJSON(t){const e=void 0===t||"string"==typeof t;if(!e&&void 0!==t.textures[this.uuid])return t.textures[this.uuid];const s={metadata:{version:4.7,type:"Texture",generator:"Texture.toJSON"},uuid:this.uuid,name:this.name,image:this.source.toJSON(t).uuid,mapping:this.mapping,channel:this.channel,repeat:[this.repeat.x,this.repeat.y],offset:[this.offset.x,this.offset.y],center:[this.center.x,this.center.y],rotation:this.rotation,wrap:[this.wrapS,this.wrapT],format:this.format,internalFormat:this.internalFormat,type:this.type,colorSpace:this.colorSpace,minFilter:this.minFilter,magFilter:this.magFilter,anisotropy:this.anisotropy,flipY:this.flipY,generateMipmaps:this.generateMipmaps,premultiplyAlpha:this.premultiplyAlpha,unpackAlignment:this.unpackAlignment};return Object.keys(this.userData).length>0&&(s.userData=this.userData),e||(t.textures[this.uuid]=s),s}dispose(){this.dispatchEvent({type:"dispose"})}transformUv(t){if(this.mapping!==ot)return t;if(t.applyMatrix3(this.matrix),t.x<0||t.x>1)switch(this.wrapS){case pt:t.x=t.x-Math.floor(t.x);break;case mt:t.x=t.x<0?0:1;break;case yt:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case pt:t.y=t.y-Math.floor(t.y);break;case mt:t.y=t.y<0?0:1;break;case yt:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flipY&&(t.y=1-t.y),t}set needsUpdate(t){!0===t&&(this.version++,this.source.needsUpdate=!0)}set needsPMREMUpdate(t){!0===t&&this.pmremVersion++}}Ai.DEFAULT_IMAGE=null,Ai.DEFAULT_MAPPING=ot,Ai.DEFAULT_ANISOTROPY=1;class Ti{constructor(t=0,e=0,s=0,i=1){Ti.prototype.isVector4=!0,this.x=t,this.y=e,this.z=s,this.w=i}get width(){return this.z}set width(t){this.z=t}get height(){return this.w}set height(t){this.w=t}set(t,e,s,i){return this.x=t,this.y=e,this.z=s,this.w=i,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this.w=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setW(t){return this.w=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;case 3:this.w=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this.w=void 0!==t.w?t.w:1,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this.w+=t.w,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this.w+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this.w=t.w+e.w,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this.w+=t.w*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this.w-=t.w,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this.w-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this.w=t.w-e.w,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this.w*=t.w,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this.w*=t,this}applyMatrix4(t){const e=this.x,s=this.y,i=this.z,r=this.w,n=t.elements;return this.x=n[0]*e+n[4]*s+n[8]*i+n[12]*r,this.y=n[1]*e+n[5]*s+n[9]*i+n[13]*r,this.z=n[2]*e+n[6]*s+n[10]*i+n[14]*r,this.w=n[3]*e+n[7]*s+n[11]*i+n[15]*r,this}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this.w/=t.w,this}divideScalar(t){return this.multiplyScalar(1/t)}setAxisAngleFromQuaternion(t){this.w=2*Math.acos(t.w);const e=Math.sqrt(1-t.w*t.w);return e<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=t.x/e,this.y=t.y/e,this.z=t.z/e),this}setAxisAngleFromRotationMatrix(t){let e,s,i,r;const n=.01,a=.1,o=t.elements,h=o[0],l=o[4],c=o[8],u=o[1],d=o[5],p=o[9],m=o[2],y=o[6],g=o[10];if(Math.abs(l-u)o&&t>f?tf?o1;this.dispose()}this.viewport.set(0,0,t,e),this.scissor.set(0,0,t,e)}clone(){return(new this.constructor).copy(this)}copy(t){this.width=t.width,this.height=t.height,this.depth=t.depth,this.scissor.copy(t.scissor),this.scissorTest=t.scissorTest,this.viewport.copy(t.viewport),this.textures.length=0;for(let e=0,s=t.textures.length;e=this.min.x&&t.x<=this.max.x&&t.y>=this.min.y&&t.y<=this.max.y&&t.z>=this.min.z&&t.z<=this.max.z}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y&&this.min.z<=t.min.z&&t.max.z<=this.max.z}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y),(t.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(t){return t.max.x>=this.min.x&&t.min.x<=this.max.x&&t.max.y>=this.min.y&&t.min.y<=this.max.y&&t.max.z>=this.min.z&&t.min.z<=this.max.z}intersectsSphere(t){return this.clampPoint(t.center,Oi),Oi.distanceToSquared(t.center)<=t.radius*t.radius}intersectsPlane(t){let e,s;return t.normal.x>0?(e=t.normal.x*this.min.x,s=t.normal.x*this.max.x):(e=t.normal.x*this.max.x,s=t.normal.x*this.min.x),t.normal.y>0?(e+=t.normal.y*this.min.y,s+=t.normal.y*this.max.y):(e+=t.normal.y*this.max.y,s+=t.normal.y*this.min.y),t.normal.z>0?(e+=t.normal.z*this.min.z,s+=t.normal.z*this.max.z):(e+=t.normal.z*this.max.z,s+=t.normal.z*this.min.z),e<=-t.constant&&s>=-t.constant}intersectsTriangle(t){if(this.isEmpty())return!1;this.getCenter(Ui),Hi.subVectors(this.max,Ui),Vi.subVectors(t.a,Ui),Fi.subVectors(t.b,Ui),Li.subVectors(t.c,Ui),ji.subVectors(Fi,Vi),Di.subVectors(Li,Fi),Wi.subVectors(Vi,Li);let e=[0,-ji.z,ji.y,0,-Di.z,Di.y,0,-Wi.z,Wi.y,ji.z,0,-ji.x,Di.z,0,-Di.x,Wi.z,0,-Wi.x,-ji.y,ji.x,0,-Di.y,Di.x,0,-Wi.y,Wi.x,0];return!!Xi(e,Vi,Fi,Li,Hi)&&(e=[1,0,0,0,1,0,0,0,1],!!Xi(e,Vi,Fi,Li,Hi)&&(qi.crossVectors(ji,Di),e=[qi.x,qi.y,qi.z],Xi(e,Vi,Fi,Li,Hi)))}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,Oi).distanceTo(t)}getBoundingSphere(t){return this.isEmpty()?t.makeEmpty():(this.getCenter(t.center),t.radius=.5*this.getSize(Oi).length()),t}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}applyMatrix4(t){return this.isEmpty()||(Pi[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(t),Pi[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(t),Pi[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(t),Pi[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(t),Pi[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(t),Pi[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(t),Pi[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(t),Pi[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(t),this.setFromPoints(Pi)),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}toJSON(){return{min:this.min.toArray(),max:this.max.toArray()}}fromJSON(t){return this.min.fromArray(t.min),this.max.fromArray(t.max),this}}const Pi=[new Ks,new Ks,new Ks,new Ks,new Ks,new Ks,new Ks,new Ks],Oi=new Ks,Ni=new Ri,Vi=new Ks,Fi=new Ks,Li=new Ks,ji=new Ks,Di=new Ks,Wi=new Ks,Ui=new Ks,Hi=new Ks,qi=new Ks,Ji=new Ks;function Xi(t,e,s,i,r){for(let n=0,a=t.length-3;n<=a;n+=3){Ji.fromArray(t,n);const a=r.x*Math.abs(Ji.x)+r.y*Math.abs(Ji.y)+r.z*Math.abs(Ji.z),o=e.dot(Ji),h=s.dot(Ji),l=i.dot(Ji);if(Math.max(-Math.max(o,h,l),Math.min(o,h,l))>a)return!1}return!0}const Yi=new Ri,Zi=new Ks,Gi=new Ks;class $i{constructor(t=new Ks,e=-1){this.isSphere=!0,this.center=t,this.radius=e}set(t,e){return this.center.copy(t),this.radius=e,this}setFromPoints(t,e){const s=this.center;void 0!==e?s.copy(e):Yi.setFromPoints(t).getCenter(s);let i=0;for(let e=0,r=t.length;ethis.radius*this.radius&&(e.sub(this.center).normalize(),e.multiplyScalar(this.radius).add(this.center)),e}getBoundingBox(t){return this.isEmpty()?(t.makeEmpty(),t):(t.set(this.center,this.center),t.expandByScalar(this.radius),t)}applyMatrix4(t){return this.center.applyMatrix4(t),this.radius=this.radius*t.getMaxScaleOnAxis(),this}translate(t){return this.center.add(t),this}expandByPoint(t){if(this.isEmpty())return this.center.copy(t),this.radius=0,this;Zi.subVectors(t,this.center);const e=Zi.lengthSq();if(e>this.radius*this.radius){const t=Math.sqrt(e),s=.5*(t-this.radius);this.center.addScaledVector(Zi,s/t),this.radius+=s}return this}union(t){return t.isEmpty()?this:this.isEmpty()?(this.copy(t),this):(!0===this.center.equals(t.center)?this.radius=Math.max(this.radius,t.radius):(Gi.subVectors(t.center,this.center).setLength(t.radius),this.expandByPoint(Zi.copy(t.center).add(Gi)),this.expandByPoint(Zi.copy(t.center).sub(Gi))),this)}equals(t){return t.center.equals(this.center)&&t.radius===this.radius}clone(){return(new this.constructor).copy(this)}toJSON(){return{radius:this.radius,center:this.center.toArray()}}fromJSON(t){return this.radius=t.radius,this.center.fromArray(t.center),this}}const Qi=new Ks,Ki=new Ks,tr=new Ks,er=new Ks,sr=new Ks,ir=new Ks,rr=new Ks;class nr{constructor(t=new Ks,e=new Ks(0,0,-1)){this.origin=t,this.direction=e}set(t,e){return this.origin.copy(t),this.direction.copy(e),this}copy(t){return this.origin.copy(t.origin),this.direction.copy(t.direction),this}at(t,e){return e.copy(this.origin).addScaledVector(this.direction,t)}lookAt(t){return this.direction.copy(t).sub(this.origin).normalize(),this}recast(t){return this.origin.copy(this.at(t,Qi)),this}closestPointToPoint(t,e){e.subVectors(t,this.origin);const s=e.dot(this.direction);return s<0?e.copy(this.origin):e.copy(this.origin).addScaledVector(this.direction,s)}distanceToPoint(t){return Math.sqrt(this.distanceSqToPoint(t))}distanceSqToPoint(t){const e=Qi.subVectors(t,this.origin).dot(this.direction);return e<0?this.origin.distanceToSquared(t):(Qi.copy(this.origin).addScaledVector(this.direction,e),Qi.distanceToSquared(t))}distanceSqToSegment(t,e,s,i){Ki.copy(t).add(e).multiplyScalar(.5),tr.copy(e).sub(t).normalize(),er.copy(this.origin).sub(Ki);const r=.5*t.distanceTo(e),n=-this.direction.dot(tr),a=er.dot(this.direction),o=-er.dot(tr),h=er.lengthSq(),l=Math.abs(1-n*n);let c,u,d,p;if(l>0)if(c=n*o-a,u=n*a-o,p=r*l,c>=0)if(u>=-p)if(u<=p){const t=1/l;c*=t,u*=t,d=c*(c+n*u+2*a)+u*(n*c+u+2*o)+h}else u=r,c=Math.max(0,-(n*u+a)),d=-c*c+u*(u+2*o)+h;else u=-r,c=Math.max(0,-(n*u+a)),d=-c*c+u*(u+2*o)+h;else u<=-p?(c=Math.max(0,-(-n*r+a)),u=c>0?-r:Math.min(Math.max(-r,-o),r),d=-c*c+u*(u+2*o)+h):u<=p?(c=0,u=Math.min(Math.max(-r,-o),r),d=u*(u+2*o)+h):(c=Math.max(0,-(n*r+a)),u=c>0?r:Math.min(Math.max(-r,-o),r),d=-c*c+u*(u+2*o)+h);else u=n>0?-r:r,c=Math.max(0,-(n*u+a)),d=-c*c+u*(u+2*o)+h;return s&&s.copy(this.origin).addScaledVector(this.direction,c),i&&i.copy(Ki).addScaledVector(tr,u),d}intersectSphere(t,e){Qi.subVectors(t.center,this.origin);const s=Qi.dot(this.direction),i=Qi.dot(Qi)-s*s,r=t.radius*t.radius;if(i>r)return null;const n=Math.sqrt(r-i),a=s-n,o=s+n;return o<0?null:a<0?this.at(o,e):this.at(a,e)}intersectsSphere(t){return!(t.radius<0)&&this.distanceSqToPoint(t.center)<=t.radius*t.radius}distanceToPlane(t){const e=t.normal.dot(this.direction);if(0===e)return 0===t.distanceToPoint(this.origin)?0:null;const s=-(this.origin.dot(t.normal)+t.constant)/e;return s>=0?s:null}intersectPlane(t,e){const s=this.distanceToPlane(t);return null===s?null:this.at(s,e)}intersectsPlane(t){const e=t.distanceToPoint(this.origin);if(0===e)return!0;return t.normal.dot(this.direction)*e<0}intersectBox(t,e){let s,i,r,n,a,o;const h=1/this.direction.x,l=1/this.direction.y,c=1/this.direction.z,u=this.origin;return h>=0?(s=(t.min.x-u.x)*h,i=(t.max.x-u.x)*h):(s=(t.max.x-u.x)*h,i=(t.min.x-u.x)*h),l>=0?(r=(t.min.y-u.y)*l,n=(t.max.y-u.y)*l):(r=(t.max.y-u.y)*l,n=(t.min.y-u.y)*l),s>n||r>i?null:((r>s||isNaN(s))&&(s=r),(n=0?(a=(t.min.z-u.z)*c,o=(t.max.z-u.z)*c):(a=(t.max.z-u.z)*c,o=(t.min.z-u.z)*c),s>o||a>i?null:((a>s||s!=s)&&(s=a),(o=0?s:i,e)))}intersectsBox(t){return null!==this.intersectBox(t,Qi)}intersectTriangle(t,e,s,i,r){sr.subVectors(e,t),ir.subVectors(s,t),rr.crossVectors(sr,ir);let n,a=this.direction.dot(rr);if(a>0){if(i)return null;n=1}else{if(!(a<0))return null;n=-1,a=-a}er.subVectors(this.origin,t);const o=n*this.direction.dot(ir.crossVectors(er,ir));if(o<0)return null;const h=n*this.direction.dot(sr.cross(er));if(h<0)return null;if(o+h>a)return null;const l=-n*er.dot(rr);return l<0?null:this.at(l/a,r)}applyMatrix4(t){return this.origin.applyMatrix4(t),this.direction.transformDirection(t),this}equals(t){return t.origin.equals(this.origin)&&t.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}class ar{constructor(t,e,s,i,r,n,a,o,h,l,c,u,d,p,m,y){ar.prototype.isMatrix4=!0,this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],void 0!==t&&this.set(t,e,s,i,r,n,a,o,h,l,c,u,d,p,m,y)}set(t,e,s,i,r,n,a,o,h,l,c,u,d,p,m,y){const g=this.elements;return g[0]=t,g[4]=e,g[8]=s,g[12]=i,g[1]=r,g[5]=n,g[9]=a,g[13]=o,g[2]=h,g[6]=l,g[10]=c,g[14]=u,g[3]=d,g[7]=p,g[11]=m,g[15]=y,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return(new ar).fromArray(this.elements)}copy(t){const e=this.elements,s=t.elements;return e[0]=s[0],e[1]=s[1],e[2]=s[2],e[3]=s[3],e[4]=s[4],e[5]=s[5],e[6]=s[6],e[7]=s[7],e[8]=s[8],e[9]=s[9],e[10]=s[10],e[11]=s[11],e[12]=s[12],e[13]=s[13],e[14]=s[14],e[15]=s[15],this}copyPosition(t){const e=this.elements,s=t.elements;return e[12]=s[12],e[13]=s[13],e[14]=s[14],this}setFromMatrix3(t){const e=t.elements;return this.set(e[0],e[3],e[6],0,e[1],e[4],e[7],0,e[2],e[5],e[8],0,0,0,0,1),this}extractBasis(t,e,s){return t.setFromMatrixColumn(this,0),e.setFromMatrixColumn(this,1),s.setFromMatrixColumn(this,2),this}makeBasis(t,e,s){return this.set(t.x,e.x,s.x,0,t.y,e.y,s.y,0,t.z,e.z,s.z,0,0,0,0,1),this}extractRotation(t){const e=this.elements,s=t.elements,i=1/or.setFromMatrixColumn(t,0).length(),r=1/or.setFromMatrixColumn(t,1).length(),n=1/or.setFromMatrixColumn(t,2).length();return e[0]=s[0]*i,e[1]=s[1]*i,e[2]=s[2]*i,e[3]=0,e[4]=s[4]*r,e[5]=s[5]*r,e[6]=s[6]*r,e[7]=0,e[8]=s[8]*n,e[9]=s[9]*n,e[10]=s[10]*n,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromEuler(t){const e=this.elements,s=t.x,i=t.y,r=t.z,n=Math.cos(s),a=Math.sin(s),o=Math.cos(i),h=Math.sin(i),l=Math.cos(r),c=Math.sin(r);if("XYZ"===t.order){const t=n*l,s=n*c,i=a*l,r=a*c;e[0]=o*l,e[4]=-o*c,e[8]=h,e[1]=s+i*h,e[5]=t-r*h,e[9]=-a*o,e[2]=r-t*h,e[6]=i+s*h,e[10]=n*o}else if("YXZ"===t.order){const t=o*l,s=o*c,i=h*l,r=h*c;e[0]=t+r*a,e[4]=i*a-s,e[8]=n*h,e[1]=n*c,e[5]=n*l,e[9]=-a,e[2]=s*a-i,e[6]=r+t*a,e[10]=n*o}else if("ZXY"===t.order){const t=o*l,s=o*c,i=h*l,r=h*c;e[0]=t-r*a,e[4]=-n*c,e[8]=i+s*a,e[1]=s+i*a,e[5]=n*l,e[9]=r-t*a,e[2]=-n*h,e[6]=a,e[10]=n*o}else if("ZYX"===t.order){const t=n*l,s=n*c,i=a*l,r=a*c;e[0]=o*l,e[4]=i*h-s,e[8]=t*h+r,e[1]=o*c,e[5]=r*h+t,e[9]=s*h-i,e[2]=-h,e[6]=a*o,e[10]=n*o}else if("YZX"===t.order){const t=n*o,s=n*h,i=a*o,r=a*h;e[0]=o*l,e[4]=r-t*c,e[8]=i*c+s,e[1]=c,e[5]=n*l,e[9]=-a*l,e[2]=-h*l,e[6]=s*c+i,e[10]=t-r*c}else if("XZY"===t.order){const t=n*o,s=n*h,i=a*o,r=a*h;e[0]=o*l,e[4]=-c,e[8]=h*l,e[1]=t*c+r,e[5]=n*l,e[9]=s*c-i,e[2]=i*c-s,e[6]=a*l,e[10]=r*c+t}return e[3]=0,e[7]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromQuaternion(t){return this.compose(lr,t,cr)}lookAt(t,e,s){const i=this.elements;return pr.subVectors(t,e),0===pr.lengthSq()&&(pr.z=1),pr.normalize(),ur.crossVectors(s,pr),0===ur.lengthSq()&&(1===Math.abs(s.z)?pr.x+=1e-4:pr.z+=1e-4,pr.normalize(),ur.crossVectors(s,pr)),ur.normalize(),dr.crossVectors(pr,ur),i[0]=ur.x,i[4]=dr.x,i[8]=pr.x,i[1]=ur.y,i[5]=dr.y,i[9]=pr.y,i[2]=ur.z,i[6]=dr.z,i[10]=pr.z,this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const s=t.elements,i=e.elements,r=this.elements,n=s[0],a=s[4],o=s[8],h=s[12],l=s[1],c=s[5],u=s[9],d=s[13],p=s[2],m=s[6],y=s[10],g=s[14],f=s[3],x=s[7],b=s[11],v=s[15],w=i[0],M=i[4],S=i[8],_=i[12],A=i[1],T=i[5],z=i[9],C=i[13],I=i[2],B=i[6],k=i[10],E=i[14],R=i[3],P=i[7],O=i[11],N=i[15];return r[0]=n*w+a*A+o*I+h*R,r[4]=n*M+a*T+o*B+h*P,r[8]=n*S+a*z+o*k+h*O,r[12]=n*_+a*C+o*E+h*N,r[1]=l*w+c*A+u*I+d*R,r[5]=l*M+c*T+u*B+d*P,r[9]=l*S+c*z+u*k+d*O,r[13]=l*_+c*C+u*E+d*N,r[2]=p*w+m*A+y*I+g*R,r[6]=p*M+m*T+y*B+g*P,r[10]=p*S+m*z+y*k+g*O,r[14]=p*_+m*C+y*E+g*N,r[3]=f*w+x*A+b*I+v*R,r[7]=f*M+x*T+b*B+v*P,r[11]=f*S+x*z+b*k+v*O,r[15]=f*_+x*C+b*E+v*N,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[4]*=t,e[8]*=t,e[12]*=t,e[1]*=t,e[5]*=t,e[9]*=t,e[13]*=t,e[2]*=t,e[6]*=t,e[10]*=t,e[14]*=t,e[3]*=t,e[7]*=t,e[11]*=t,e[15]*=t,this}determinant(){const t=this.elements,e=t[0],s=t[4],i=t[8],r=t[12],n=t[1],a=t[5],o=t[9],h=t[13],l=t[2],c=t[6],u=t[10],d=t[14];return t[3]*(+r*o*c-i*h*c-r*a*u+s*h*u+i*a*d-s*o*d)+t[7]*(+e*o*d-e*h*u+r*n*u-i*n*d+i*h*l-r*o*l)+t[11]*(+e*h*c-e*a*d-r*n*c+s*n*d+r*a*l-s*h*l)+t[15]*(-i*a*l-e*o*c+e*a*u+i*n*c-s*n*u+s*o*l)}transpose(){const t=this.elements;let e;return e=t[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this}setPosition(t,e,s){const i=this.elements;return t.isVector3?(i[12]=t.x,i[13]=t.y,i[14]=t.z):(i[12]=t,i[13]=e,i[14]=s),this}invert(){const t=this.elements,e=t[0],s=t[1],i=t[2],r=t[3],n=t[4],a=t[5],o=t[6],h=t[7],l=t[8],c=t[9],u=t[10],d=t[11],p=t[12],m=t[13],y=t[14],g=t[15],f=c*y*h-m*u*h+m*o*d-a*y*d-c*o*g+a*u*g,x=p*u*h-l*y*h-p*o*d+n*y*d+l*o*g-n*u*g,b=l*m*h-p*c*h+p*a*d-n*m*d-l*a*g+n*c*g,v=p*c*o-l*m*o-p*a*u+n*m*u+l*a*y-n*c*y,w=e*f+s*x+i*b+r*v;if(0===w)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const M=1/w;return t[0]=f*M,t[1]=(m*u*r-c*y*r-m*i*d+s*y*d+c*i*g-s*u*g)*M,t[2]=(a*y*r-m*o*r+m*i*h-s*y*h-a*i*g+s*o*g)*M,t[3]=(c*o*r-a*u*r-c*i*h+s*u*h+a*i*d-s*o*d)*M,t[4]=x*M,t[5]=(l*y*r-p*u*r+p*i*d-e*y*d-l*i*g+e*u*g)*M,t[6]=(p*o*r-n*y*r-p*i*h+e*y*h+n*i*g-e*o*g)*M,t[7]=(n*u*r-l*o*r+l*i*h-e*u*h-n*i*d+e*o*d)*M,t[8]=b*M,t[9]=(p*c*r-l*m*r-p*s*d+e*m*d+l*s*g-e*c*g)*M,t[10]=(n*m*r-p*a*r+p*s*h-e*m*h-n*s*g+e*a*g)*M,t[11]=(l*a*r-n*c*r-l*s*h+e*c*h+n*s*d-e*a*d)*M,t[12]=v*M,t[13]=(l*m*i-p*c*i+p*s*u-e*m*u-l*s*y+e*c*y)*M,t[14]=(p*a*i-n*m*i-p*s*o+e*m*o+n*s*y-e*a*y)*M,t[15]=(n*c*i-l*a*i+l*s*o-e*c*o-n*s*u+e*a*u)*M,this}scale(t){const e=this.elements,s=t.x,i=t.y,r=t.z;return e[0]*=s,e[4]*=i,e[8]*=r,e[1]*=s,e[5]*=i,e[9]*=r,e[2]*=s,e[6]*=i,e[10]*=r,e[3]*=s,e[7]*=i,e[11]*=r,this}getMaxScaleOnAxis(){const t=this.elements,e=t[0]*t[0]+t[1]*t[1]+t[2]*t[2],s=t[4]*t[4]+t[5]*t[5]+t[6]*t[6],i=t[8]*t[8]+t[9]*t[9]+t[10]*t[10];return Math.sqrt(Math.max(e,s,i))}makeTranslation(t,e,s){return t.isVector3?this.set(1,0,0,t.x,0,1,0,t.y,0,0,1,t.z,0,0,0,1):this.set(1,0,0,t,0,1,0,e,0,0,1,s,0,0,0,1),this}makeRotationX(t){const e=Math.cos(t),s=Math.sin(t);return this.set(1,0,0,0,0,e,-s,0,0,s,e,0,0,0,0,1),this}makeRotationY(t){const e=Math.cos(t),s=Math.sin(t);return this.set(e,0,s,0,0,1,0,0,-s,0,e,0,0,0,0,1),this}makeRotationZ(t){const e=Math.cos(t),s=Math.sin(t);return this.set(e,-s,0,0,s,e,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(t,e){const s=Math.cos(e),i=Math.sin(e),r=1-s,n=t.x,a=t.y,o=t.z,h=r*n,l=r*a;return this.set(h*n+s,h*a-i*o,h*o+i*a,0,h*a+i*o,l*a+s,l*o-i*n,0,h*o-i*a,l*o+i*n,r*o*o+s,0,0,0,0,1),this}makeScale(t,e,s){return this.set(t,0,0,0,0,e,0,0,0,0,s,0,0,0,0,1),this}makeShear(t,e,s,i,r,n){return this.set(1,s,r,0,t,1,n,0,e,i,1,0,0,0,0,1),this}compose(t,e,s){const i=this.elements,r=e._x,n=e._y,a=e._z,o=e._w,h=r+r,l=n+n,c=a+a,u=r*h,d=r*l,p=r*c,m=n*l,y=n*c,g=a*c,f=o*h,x=o*l,b=o*c,v=s.x,w=s.y,M=s.z;return i[0]=(1-(m+g))*v,i[1]=(d+b)*v,i[2]=(p-x)*v,i[3]=0,i[4]=(d-b)*w,i[5]=(1-(u+g))*w,i[6]=(y+f)*w,i[7]=0,i[8]=(p+x)*M,i[9]=(y-f)*M,i[10]=(1-(u+m))*M,i[11]=0,i[12]=t.x,i[13]=t.y,i[14]=t.z,i[15]=1,this}decompose(t,e,s){const i=this.elements;let r=or.set(i[0],i[1],i[2]).length();const n=or.set(i[4],i[5],i[6]).length(),a=or.set(i[8],i[9],i[10]).length();this.determinant()<0&&(r=-r),t.x=i[12],t.y=i[13],t.z=i[14],hr.copy(this);const o=1/r,h=1/n,l=1/a;return hr.elements[0]*=o,hr.elements[1]*=o,hr.elements[2]*=o,hr.elements[4]*=h,hr.elements[5]*=h,hr.elements[6]*=h,hr.elements[8]*=l,hr.elements[9]*=l,hr.elements[10]*=l,e.setFromRotationMatrix(hr),s.x=r,s.y=n,s.z=a,this}makePerspective(t,e,s,i,r,n,a=2e3,o=!1){const h=this.elements,l=2*r/(e-t),c=2*r/(s-i),u=(e+t)/(e-t),d=(s+i)/(s-i);let p,m;if(o)p=r/(n-r),m=n*r/(n-r);else if(a===Ps)p=-(n+r)/(n-r),m=-2*n*r/(n-r);else{if(a!==Os)throw new Error("THREE.Matrix4.makePerspective(): Invalid coordinate system: "+a);p=-n/(n-r),m=-n*r/(n-r)}return h[0]=l,h[4]=0,h[8]=u,h[12]=0,h[1]=0,h[5]=c,h[9]=d,h[13]=0,h[2]=0,h[6]=0,h[10]=p,h[14]=m,h[3]=0,h[7]=0,h[11]=-1,h[15]=0,this}makeOrthographic(t,e,s,i,r,n,a=2e3,o=!1){const h=this.elements,l=2/(e-t),c=2/(s-i),u=-(e+t)/(e-t),d=-(s+i)/(s-i);let p,m;if(o)p=1/(n-r),m=n/(n-r);else if(a===Ps)p=-2/(n-r),m=-(n+r)/(n-r);else{if(a!==Os)throw new Error("THREE.Matrix4.makeOrthographic(): Invalid coordinate system: "+a);p=-1/(n-r),m=-r/(n-r)}return h[0]=l,h[4]=0,h[8]=0,h[12]=u,h[1]=0,h[5]=c,h[9]=0,h[13]=d,h[2]=0,h[6]=0,h[10]=p,h[14]=m,h[3]=0,h[7]=0,h[11]=0,h[15]=1,this}equals(t){const e=this.elements,s=t.elements;for(let t=0;t<16;t++)if(e[t]!==s[t])return!1;return!0}fromArray(t,e=0){for(let s=0;s<16;s++)this.elements[s]=t[s+e];return this}toArray(t=[],e=0){const s=this.elements;return t[e]=s[0],t[e+1]=s[1],t[e+2]=s[2],t[e+3]=s[3],t[e+4]=s[4],t[e+5]=s[5],t[e+6]=s[6],t[e+7]=s[7],t[e+8]=s[8],t[e+9]=s[9],t[e+10]=s[10],t[e+11]=s[11],t[e+12]=s[12],t[e+13]=s[13],t[e+14]=s[14],t[e+15]=s[15],t}}const or=new Ks,hr=new ar,lr=new Ks(0,0,0),cr=new Ks(1,1,1),ur=new Ks,dr=new Ks,pr=new Ks,mr=new ar,yr=new Qs;class gr{constructor(t=0,e=0,s=0,i=gr.DEFAULT_ORDER){this.isEuler=!0,this._x=t,this._y=e,this._z=s,this._order=i}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get order(){return this._order}set order(t){this._order=t,this._onChangeCallback()}set(t,e,s,i=this._order){return this._x=t,this._y=e,this._z=s,this._order=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._order)}copy(t){return this._x=t._x,this._y=t._y,this._z=t._z,this._order=t._order,this._onChangeCallback(),this}setFromRotationMatrix(t,e=this._order,s=!0){const i=t.elements,r=i[0],n=i[4],a=i[8],o=i[1],h=i[5],l=i[9],c=i[2],u=i[6],d=i[10];switch(e){case"XYZ":this._y=Math.asin(qs(a,-1,1)),Math.abs(a)<.9999999?(this._x=Math.atan2(-l,d),this._z=Math.atan2(-n,r)):(this._x=Math.atan2(u,h),this._z=0);break;case"YXZ":this._x=Math.asin(-qs(l,-1,1)),Math.abs(l)<.9999999?(this._y=Math.atan2(a,d),this._z=Math.atan2(o,h)):(this._y=Math.atan2(-c,r),this._z=0);break;case"ZXY":this._x=Math.asin(qs(u,-1,1)),Math.abs(u)<.9999999?(this._y=Math.atan2(-c,d),this._z=Math.atan2(-n,h)):(this._y=0,this._z=Math.atan2(o,r));break;case"ZYX":this._y=Math.asin(-qs(c,-1,1)),Math.abs(c)<.9999999?(this._x=Math.atan2(u,d),this._z=Math.atan2(o,r)):(this._x=0,this._z=Math.atan2(-n,h));break;case"YZX":this._z=Math.asin(qs(o,-1,1)),Math.abs(o)<.9999999?(this._x=Math.atan2(-l,h),this._y=Math.atan2(-c,r)):(this._x=0,this._y=Math.atan2(a,d));break;case"XZY":this._z=Math.asin(-qs(n,-1,1)),Math.abs(n)<.9999999?(this._x=Math.atan2(u,h),this._y=Math.atan2(a,r)):(this._x=Math.atan2(-l,d),this._y=0);break;default:console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: "+e)}return this._order=e,!0===s&&this._onChangeCallback(),this}setFromQuaternion(t,e,s){return mr.makeRotationFromQuaternion(t),this.setFromRotationMatrix(mr,e,s)}setFromVector3(t,e=this._order){return this.set(t.x,t.y,t.z,e)}reorder(t){return yr.setFromEuler(this),this.setFromQuaternion(yr,t)}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._order===this._order}fromArray(t){return this._x=t[0],this._y=t[1],this._z=t[2],void 0!==t[3]&&(this._order=t[3]),this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._order,t}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._order}}gr.DEFAULT_ORDER="XYZ";class fr{constructor(){this.mask=1}set(t){this.mask=1<>>0}enable(t){this.mask|=1<1){for(let t=0;t1){for(let t=0;t0&&(i.userData=this.userData),i.layers=this.layers.mask,i.matrix=this.matrix.toArray(),i.up=this.up.toArray(),!1===this.matrixAutoUpdate&&(i.matrixAutoUpdate=!1),this.isInstancedMesh&&(i.type="InstancedMesh",i.count=this.count,i.instanceMatrix=this.instanceMatrix.toJSON(),null!==this.instanceColor&&(i.instanceColor=this.instanceColor.toJSON())),this.isBatchedMesh&&(i.type="BatchedMesh",i.perObjectFrustumCulled=this.perObjectFrustumCulled,i.sortObjects=this.sortObjects,i.drawRanges=this._drawRanges,i.reservedRanges=this._reservedRanges,i.geometryInfo=this._geometryInfo.map(t=>({...t,boundingBox:t.boundingBox?t.boundingBox.toJSON():void 0,boundingSphere:t.boundingSphere?t.boundingSphere.toJSON():void 0})),i.instanceInfo=this._instanceInfo.map(t=>({...t})),i.availableInstanceIds=this._availableInstanceIds.slice(),i.availableGeometryIds=this._availableGeometryIds.slice(),i.nextIndexStart=this._nextIndexStart,i.nextVertexStart=this._nextVertexStart,i.geometryCount=this._geometryCount,i.maxInstanceCount=this._maxInstanceCount,i.maxVertexCount=this._maxVertexCount,i.maxIndexCount=this._maxIndexCount,i.geometryInitialized=this._geometryInitialized,i.matricesTexture=this._matricesTexture.toJSON(t),i.indirectTexture=this._indirectTexture.toJSON(t),null!==this._colorsTexture&&(i.colorsTexture=this._colorsTexture.toJSON(t)),null!==this.boundingSphere&&(i.boundingSphere=this.boundingSphere.toJSON()),null!==this.boundingBox&&(i.boundingBox=this.boundingBox.toJSON())),this.isScene)this.background&&(this.background.isColor?i.background=this.background.toJSON():this.background.isTexture&&(i.background=this.background.toJSON(t).uuid)),this.environment&&this.environment.isTexture&&!0!==this.environment.isRenderTargetTexture&&(i.environment=this.environment.toJSON(t).uuid);else if(this.isMesh||this.isLine||this.isPoints){i.geometry=r(t.geometries,this.geometry);const e=this.geometry.parameters;if(void 0!==e&&void 0!==e.shapes){const s=e.shapes;if(Array.isArray(s))for(let e=0,i=s.length;e0){i.children=[];for(let e=0;e0){i.animations=[];for(let e=0;e0&&(s.geometries=e),i.length>0&&(s.materials=i),r.length>0&&(s.textures=r),a.length>0&&(s.images=a),o.length>0&&(s.shapes=o),h.length>0&&(s.skeletons=h),l.length>0&&(s.animations=l),c.length>0&&(s.nodes=c)}return s.object=i,s;function n(t){const e=[];for(const s in t){const i=t[s];delete i.metadata,e.push(i)}return e}}clone(t){return(new this.constructor).copy(this,t)}copy(t,e=!0){if(this.name=t.name,this.up.copy(t.up),this.position.copy(t.position),this.rotation.order=t.rotation.order,this.quaternion.copy(t.quaternion),this.scale.copy(t.scale),this.matrix.copy(t.matrix),this.matrixWorld.copy(t.matrixWorld),this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrixWorldAutoUpdate=t.matrixWorldAutoUpdate,this.matrixWorldNeedsUpdate=t.matrixWorldNeedsUpdate,this.layers.mask=t.layers.mask,this.visible=t.visible,this.castShadow=t.castShadow,this.receiveShadow=t.receiveShadow,this.frustumCulled=t.frustumCulled,this.renderOrder=t.renderOrder,this.animations=t.animations.slice(),this.userData=JSON.parse(JSON.stringify(t.userData)),!0===e)for(let e=0;e0?i.multiplyScalar(1/Math.sqrt(r)):i.set(0,0,0)}static getBarycoord(t,e,s,i,r){Pr.subVectors(i,e),Or.subVectors(s,e),Nr.subVectors(t,e);const n=Pr.dot(Pr),a=Pr.dot(Or),o=Pr.dot(Nr),h=Or.dot(Or),l=Or.dot(Nr),c=n*h-a*a;if(0===c)return r.set(0,0,0),null;const u=1/c,d=(h*o-a*l)*u,p=(n*l-a*o)*u;return r.set(1-d-p,p,d)}static containsPoint(t,e,s,i){return null!==this.getBarycoord(t,e,s,i,Vr)&&(Vr.x>=0&&Vr.y>=0&&Vr.x+Vr.y<=1)}static getInterpolation(t,e,s,i,r,n,a,o){return null===this.getBarycoord(t,e,s,i,Vr)?(o.x=0,o.y=0,"z"in o&&(o.z=0),"w"in o&&(o.w=0),null):(o.setScalar(0),o.addScaledVector(r,Vr.x),o.addScaledVector(n,Vr.y),o.addScaledVector(a,Vr.z),o)}static getInterpolatedAttribute(t,e,s,i,r,n){return Hr.setScalar(0),qr.setScalar(0),Jr.setScalar(0),Hr.fromBufferAttribute(t,e),qr.fromBufferAttribute(t,s),Jr.fromBufferAttribute(t,i),n.setScalar(0),n.addScaledVector(Hr,r.x),n.addScaledVector(qr,r.y),n.addScaledVector(Jr,r.z),n}static isFrontFacing(t,e,s,i){return Pr.subVectors(s,e),Or.subVectors(t,e),Pr.cross(Or).dot(i)<0}set(t,e,s){return this.a.copy(t),this.b.copy(e),this.c.copy(s),this}setFromPointsAndIndices(t,e,s,i){return this.a.copy(t[e]),this.b.copy(t[s]),this.c.copy(t[i]),this}setFromAttributeAndIndices(t,e,s,i){return this.a.fromBufferAttribute(t,e),this.b.fromBufferAttribute(t,s),this.c.fromBufferAttribute(t,i),this}clone(){return(new this.constructor).copy(this)}copy(t){return this.a.copy(t.a),this.b.copy(t.b),this.c.copy(t.c),this}getArea(){return Pr.subVectors(this.c,this.b),Or.subVectors(this.a,this.b),.5*Pr.cross(Or).length()}getMidpoint(t){return t.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(t){return Xr.getNormal(this.a,this.b,this.c,t)}getPlane(t){return t.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(t,e){return Xr.getBarycoord(t,this.a,this.b,this.c,e)}getInterpolation(t,e,s,i,r){return Xr.getInterpolation(t,this.a,this.b,this.c,e,s,i,r)}containsPoint(t){return Xr.containsPoint(t,this.a,this.b,this.c)}isFrontFacing(t){return Xr.isFrontFacing(this.a,this.b,this.c,t)}intersectsBox(t){return t.intersectsTriangle(this)}closestPointToPoint(t,e){const s=this.a,i=this.b,r=this.c;let n,a;Fr.subVectors(i,s),Lr.subVectors(r,s),Dr.subVectors(t,s);const o=Fr.dot(Dr),h=Lr.dot(Dr);if(o<=0&&h<=0)return e.copy(s);Wr.subVectors(t,i);const l=Fr.dot(Wr),c=Lr.dot(Wr);if(l>=0&&c<=l)return e.copy(i);const u=o*c-l*h;if(u<=0&&o>=0&&l<=0)return n=o/(o-l),e.copy(s).addScaledVector(Fr,n);Ur.subVectors(t,r);const d=Fr.dot(Ur),p=Lr.dot(Ur);if(p>=0&&d<=p)return e.copy(r);const m=d*h-o*p;if(m<=0&&h>=0&&p<=0)return a=h/(h-p),e.copy(s).addScaledVector(Lr,a);const y=l*p-d*c;if(y<=0&&c-l>=0&&d-p>=0)return jr.subVectors(r,i),a=(c-l)/(c-l+(d-p)),e.copy(i).addScaledVector(jr,a);const g=1/(y+m+u);return n=m*g,a=u*g,e.copy(s).addScaledVector(Fr,n).addScaledVector(Lr,a)}equals(t){return t.a.equals(this.a)&&t.b.equals(this.b)&&t.c.equals(this.c)}}const Yr={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},Zr={h:0,s:0,l:0},Gr={h:0,s:0,l:0};function $r(t,e,s){return s<0&&(s+=1),s>1&&(s-=1),s<1/6?t+6*(e-t)*s:s<.5?e:s<2/3?t+6*(e-t)*(2/3-s):t}class Qr{constructor(t,e,s){return this.isColor=!0,this.r=1,this.g=1,this.b=1,this.set(t,e,s)}set(t,e,s){if(void 0===e&&void 0===s){const e=t;e&&e.isColor?this.copy(e):"number"==typeof e?this.setHex(e):"string"==typeof e&&this.setStyle(e)}else this.setRGB(t,e,s);return this}setScalar(t){return this.r=t,this.g=t,this.b=t,this}setHex(t,e=Ze){return t=Math.floor(t),this.r=(t>>16&255)/255,this.g=(t>>8&255)/255,this.b=(255&t)/255,yi.colorSpaceToWorking(this,e),this}setRGB(t,e,s,i=yi.workingColorSpace){return this.r=t,this.g=e,this.b=s,yi.colorSpaceToWorking(this,i),this}setHSL(t,e,s,i=yi.workingColorSpace){if(t=Js(t,1),e=qs(e,0,1),s=qs(s,0,1),0===e)this.r=this.g=this.b=s;else{const i=s<=.5?s*(1+e):s+e-s*e,r=2*s-i;this.r=$r(r,i,t+1/3),this.g=$r(r,i,t),this.b=$r(r,i,t-1/3)}return yi.colorSpaceToWorking(this,i),this}setStyle(t,e=Ze){function s(e){void 0!==e&&parseFloat(e)<1&&console.warn("THREE.Color: Alpha component of "+t+" will be ignored.")}let i;if(i=/^(\w+)\(([^\)]*)\)/.exec(t)){let r;const n=i[1],a=i[2];switch(n){case"rgb":case"rgba":if(r=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return s(r[4]),this.setRGB(Math.min(255,parseInt(r[1],10))/255,Math.min(255,parseInt(r[2],10))/255,Math.min(255,parseInt(r[3],10))/255,e);if(r=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return s(r[4]),this.setRGB(Math.min(100,parseInt(r[1],10))/100,Math.min(100,parseInt(r[2],10))/100,Math.min(100,parseInt(r[3],10))/100,e);break;case"hsl":case"hsla":if(r=/^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return s(r[4]),this.setHSL(parseFloat(r[1])/360,parseFloat(r[2])/100,parseFloat(r[3])/100,e);break;default:console.warn("THREE.Color: Unknown color model "+t)}}else if(i=/^\#([A-Fa-f\d]+)$/.exec(t)){const s=i[1],r=s.length;if(3===r)return this.setRGB(parseInt(s.charAt(0),16)/15,parseInt(s.charAt(1),16)/15,parseInt(s.charAt(2),16)/15,e);if(6===r)return this.setHex(parseInt(s,16),e);console.warn("THREE.Color: Invalid hex color "+t)}else if(t&&t.length>0)return this.setColorName(t,e);return this}setColorName(t,e=Ze){const s=Yr[t.toLowerCase()];return void 0!==s?this.setHex(s,e):console.warn("THREE.Color: Unknown color "+t),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(t){return this.r=t.r,this.g=t.g,this.b=t.b,this}copySRGBToLinear(t){return this.r=gi(t.r),this.g=gi(t.g),this.b=gi(t.b),this}copyLinearToSRGB(t){return this.r=fi(t.r),this.g=fi(t.g),this.b=fi(t.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(t=Ze){return yi.workingToColorSpace(Kr.copy(this),t),65536*Math.round(qs(255*Kr.r,0,255))+256*Math.round(qs(255*Kr.g,0,255))+Math.round(qs(255*Kr.b,0,255))}getHexString(t=Ze){return("000000"+this.getHex(t).toString(16)).slice(-6)}getHSL(t,e=yi.workingColorSpace){yi.workingToColorSpace(Kr.copy(this),e);const s=Kr.r,i=Kr.g,r=Kr.b,n=Math.max(s,i,r),a=Math.min(s,i,r);let o,h;const l=(a+n)/2;if(a===n)o=0,h=0;else{const t=n-a;switch(h=l<=.5?t/(n+a):t/(2-n-a),n){case s:o=(i-r)/t+(i0!=t>0&&this.version++,this._alphaTest=t}onBeforeRender(){}onBeforeCompile(){}customProgramCacheKey(){return this.onBeforeCompile.toString()}setValues(t){if(void 0!==t)for(const e in t){const s=t[e];if(void 0===s){console.warn(`THREE.Material: parameter '${e}' has value of undefined.`);continue}const i=this[e];void 0!==i?i&&i.isColor?i.set(s):i&&i.isVector3&&s&&s.isVector3?i.copy(s):this[e]=s:console.warn(`THREE.Material: '${e}' is not a property of THREE.${this.type}.`)}}toJSON(t){const e=void 0===t||"string"==typeof t;e&&(t={textures:{},images:{}});const s={metadata:{version:4.7,type:"Material",generator:"Material.toJSON"}};function i(t){const e=[];for(const s in t){const i=t[s];delete i.metadata,e.push(i)}return e}if(s.uuid=this.uuid,s.type=this.type,""!==this.name&&(s.name=this.name),this.color&&this.color.isColor&&(s.color=this.color.getHex()),void 0!==this.roughness&&(s.roughness=this.roughness),void 0!==this.metalness&&(s.metalness=this.metalness),void 0!==this.sheen&&(s.sheen=this.sheen),this.sheenColor&&this.sheenColor.isColor&&(s.sheenColor=this.sheenColor.getHex()),void 0!==this.sheenRoughness&&(s.sheenRoughness=this.sheenRoughness),this.emissive&&this.emissive.isColor&&(s.emissive=this.emissive.getHex()),void 0!==this.emissiveIntensity&&1!==this.emissiveIntensity&&(s.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(s.specular=this.specular.getHex()),void 0!==this.specularIntensity&&(s.specularIntensity=this.specularIntensity),this.specularColor&&this.specularColor.isColor&&(s.specularColor=this.specularColor.getHex()),void 0!==this.shininess&&(s.shininess=this.shininess),void 0!==this.clearcoat&&(s.clearcoat=this.clearcoat),void 0!==this.clearcoatRoughness&&(s.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(s.clearcoatMap=this.clearcoatMap.toJSON(t).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(s.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(t).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(s.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(t).uuid,s.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),this.sheenColorMap&&this.sheenColorMap.isTexture&&(s.sheenColorMap=this.sheenColorMap.toJSON(t).uuid),this.sheenRoughnessMap&&this.sheenRoughnessMap.isTexture&&(s.sheenRoughnessMap=this.sheenRoughnessMap.toJSON(t).uuid),void 0!==this.dispersion&&(s.dispersion=this.dispersion),void 0!==this.iridescence&&(s.iridescence=this.iridescence),void 0!==this.iridescenceIOR&&(s.iridescenceIOR=this.iridescenceIOR),void 0!==this.iridescenceThicknessRange&&(s.iridescenceThicknessRange=this.iridescenceThicknessRange),this.iridescenceMap&&this.iridescenceMap.isTexture&&(s.iridescenceMap=this.iridescenceMap.toJSON(t).uuid),this.iridescenceThicknessMap&&this.iridescenceThicknessMap.isTexture&&(s.iridescenceThicknessMap=this.iridescenceThicknessMap.toJSON(t).uuid),void 0!==this.anisotropy&&(s.anisotropy=this.anisotropy),void 0!==this.anisotropyRotation&&(s.anisotropyRotation=this.anisotropyRotation),this.anisotropyMap&&this.anisotropyMap.isTexture&&(s.anisotropyMap=this.anisotropyMap.toJSON(t).uuid),this.map&&this.map.isTexture&&(s.map=this.map.toJSON(t).uuid),this.matcap&&this.matcap.isTexture&&(s.matcap=this.matcap.toJSON(t).uuid),this.alphaMap&&this.alphaMap.isTexture&&(s.alphaMap=this.alphaMap.toJSON(t).uuid),this.lightMap&&this.lightMap.isTexture&&(s.lightMap=this.lightMap.toJSON(t).uuid,s.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(s.aoMap=this.aoMap.toJSON(t).uuid,s.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(s.bumpMap=this.bumpMap.toJSON(t).uuid,s.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(s.normalMap=this.normalMap.toJSON(t).uuid,s.normalMapType=this.normalMapType,s.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(s.displacementMap=this.displacementMap.toJSON(t).uuid,s.displacementScale=this.displacementScale,s.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(s.roughnessMap=this.roughnessMap.toJSON(t).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(s.metalnessMap=this.metalnessMap.toJSON(t).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(s.emissiveMap=this.emissiveMap.toJSON(t).uuid),this.specularMap&&this.specularMap.isTexture&&(s.specularMap=this.specularMap.toJSON(t).uuid),this.specularIntensityMap&&this.specularIntensityMap.isTexture&&(s.specularIntensityMap=this.specularIntensityMap.toJSON(t).uuid),this.specularColorMap&&this.specularColorMap.isTexture&&(s.specularColorMap=this.specularColorMap.toJSON(t).uuid),this.envMap&&this.envMap.isTexture&&(s.envMap=this.envMap.toJSON(t).uuid,void 0!==this.combine&&(s.combine=this.combine)),void 0!==this.envMapRotation&&(s.envMapRotation=this.envMapRotation.toArray()),void 0!==this.envMapIntensity&&(s.envMapIntensity=this.envMapIntensity),void 0!==this.reflectivity&&(s.reflectivity=this.reflectivity),void 0!==this.refractionRatio&&(s.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(s.gradientMap=this.gradientMap.toJSON(t).uuid),void 0!==this.transmission&&(s.transmission=this.transmission),this.transmissionMap&&this.transmissionMap.isTexture&&(s.transmissionMap=this.transmissionMap.toJSON(t).uuid),void 0!==this.thickness&&(s.thickness=this.thickness),this.thicknessMap&&this.thicknessMap.isTexture&&(s.thicknessMap=this.thicknessMap.toJSON(t).uuid),void 0!==this.attenuationDistance&&this.attenuationDistance!==1/0&&(s.attenuationDistance=this.attenuationDistance),void 0!==this.attenuationColor&&(s.attenuationColor=this.attenuationColor.getHex()),void 0!==this.size&&(s.size=this.size),null!==this.shadowSide&&(s.shadowSide=this.shadowSide),void 0!==this.sizeAttenuation&&(s.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(s.blending=this.blending),0!==this.side&&(s.side=this.side),!0===this.vertexColors&&(s.vertexColors=!0),this.opacity<1&&(s.opacity=this.opacity),!0===this.transparent&&(s.transparent=!0),204!==this.blendSrc&&(s.blendSrc=this.blendSrc),205!==this.blendDst&&(s.blendDst=this.blendDst),100!==this.blendEquation&&(s.blendEquation=this.blendEquation),null!==this.blendSrcAlpha&&(s.blendSrcAlpha=this.blendSrcAlpha),null!==this.blendDstAlpha&&(s.blendDstAlpha=this.blendDstAlpha),null!==this.blendEquationAlpha&&(s.blendEquationAlpha=this.blendEquationAlpha),this.blendColor&&this.blendColor.isColor&&(s.blendColor=this.blendColor.getHex()),0!==this.blendAlpha&&(s.blendAlpha=this.blendAlpha),3!==this.depthFunc&&(s.depthFunc=this.depthFunc),!1===this.depthTest&&(s.depthTest=this.depthTest),!1===this.depthWrite&&(s.depthWrite=this.depthWrite),!1===this.colorWrite&&(s.colorWrite=this.colorWrite),255!==this.stencilWriteMask&&(s.stencilWriteMask=this.stencilWriteMask),519!==this.stencilFunc&&(s.stencilFunc=this.stencilFunc),0!==this.stencilRef&&(s.stencilRef=this.stencilRef),255!==this.stencilFuncMask&&(s.stencilFuncMask=this.stencilFuncMask),this.stencilFail!==ts&&(s.stencilFail=this.stencilFail),this.stencilZFail!==ts&&(s.stencilZFail=this.stencilZFail),this.stencilZPass!==ts&&(s.stencilZPass=this.stencilZPass),!0===this.stencilWrite&&(s.stencilWrite=this.stencilWrite),void 0!==this.rotation&&0!==this.rotation&&(s.rotation=this.rotation),!0===this.polygonOffset&&(s.polygonOffset=!0),0!==this.polygonOffsetFactor&&(s.polygonOffsetFactor=this.polygonOffsetFactor),0!==this.polygonOffsetUnits&&(s.polygonOffsetUnits=this.polygonOffsetUnits),void 0!==this.linewidth&&1!==this.linewidth&&(s.linewidth=this.linewidth),void 0!==this.dashSize&&(s.dashSize=this.dashSize),void 0!==this.gapSize&&(s.gapSize=this.gapSize),void 0!==this.scale&&(s.scale=this.scale),!0===this.dithering&&(s.dithering=!0),this.alphaTest>0&&(s.alphaTest=this.alphaTest),!0===this.alphaHash&&(s.alphaHash=!0),!0===this.alphaToCoverage&&(s.alphaToCoverage=!0),!0===this.premultipliedAlpha&&(s.premultipliedAlpha=!0),!0===this.forceSinglePass&&(s.forceSinglePass=!0),!0===this.wireframe&&(s.wireframe=!0),this.wireframeLinewidth>1&&(s.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(s.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(s.wireframeLinejoin=this.wireframeLinejoin),!0===this.flatShading&&(s.flatShading=!0),!1===this.visible&&(s.visible=!1),!1===this.toneMapped&&(s.toneMapped=!1),!1===this.fog&&(s.fog=!1),Object.keys(this.userData).length>0&&(s.userData=this.userData),e){const e=i(t.textures),r=i(t.images);e.length>0&&(s.textures=e),r.length>0&&(s.images=r)}return s}clone(){return(new this.constructor).copy(this)}copy(t){this.name=t.name,this.blending=t.blending,this.side=t.side,this.vertexColors=t.vertexColors,this.opacity=t.opacity,this.transparent=t.transparent,this.blendSrc=t.blendSrc,this.blendDst=t.blendDst,this.blendEquation=t.blendEquation,this.blendSrcAlpha=t.blendSrcAlpha,this.blendDstAlpha=t.blendDstAlpha,this.blendEquationAlpha=t.blendEquationAlpha,this.blendColor.copy(t.blendColor),this.blendAlpha=t.blendAlpha,this.depthFunc=t.depthFunc,this.depthTest=t.depthTest,this.depthWrite=t.depthWrite,this.stencilWriteMask=t.stencilWriteMask,this.stencilFunc=t.stencilFunc,this.stencilRef=t.stencilRef,this.stencilFuncMask=t.stencilFuncMask,this.stencilFail=t.stencilFail,this.stencilZFail=t.stencilZFail,this.stencilZPass=t.stencilZPass,this.stencilWrite=t.stencilWrite;const e=t.clippingPlanes;let s=null;if(null!==e){const t=e.length;s=new Array(t);for(let i=0;i!==t;++i)s[i]=e[i].clone()}return this.clippingPlanes=s,this.clipIntersection=t.clipIntersection,this.clipShadows=t.clipShadows,this.shadowSide=t.shadowSide,this.colorWrite=t.colorWrite,this.precision=t.precision,this.polygonOffset=t.polygonOffset,this.polygonOffsetFactor=t.polygonOffsetFactor,this.polygonOffsetUnits=t.polygonOffsetUnits,this.dithering=t.dithering,this.alphaTest=t.alphaTest,this.alphaHash=t.alphaHash,this.alphaToCoverage=t.alphaToCoverage,this.premultipliedAlpha=t.premultipliedAlpha,this.forceSinglePass=t.forceSinglePass,this.visible=t.visible,this.toneMapped=t.toneMapped,this.userData=JSON.parse(JSON.stringify(t.userData)),this}dispose(){this.dispatchEvent({type:"dispose"})}set needsUpdate(t){!0===t&&this.version++}}class sn extends en{constructor(t){super(),this.isMeshBasicMaterial=!0,this.type="MeshBasicMaterial",this.color=new Qr(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new gr,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}const rn=nn();function nn(){const t=new ArrayBuffer(4),e=new Float32Array(t),s=new Uint32Array(t),i=new Uint32Array(512),r=new Uint32Array(512);for(let t=0;t<256;++t){const e=t-127;e<-27?(i[t]=0,i[256|t]=32768,r[t]=24,r[256|t]=24):e<-14?(i[t]=1024>>-e-14,i[256|t]=1024>>-e-14|32768,r[t]=-e-1,r[256|t]=-e-1):e<=15?(i[t]=e+15<<10,i[256|t]=e+15<<10|32768,r[t]=13,r[256|t]=13):e<128?(i[t]=31744,i[256|t]=64512,r[t]=24,r[256|t]=24):(i[t]=31744,i[256|t]=64512,r[t]=13,r[256|t]=13)}const n=new Uint32Array(2048),a=new Uint32Array(64),o=new Uint32Array(64);for(let t=1;t<1024;++t){let e=t<<13,s=0;for(;!(8388608&e);)e<<=1,s-=8388608;e&=-8388609,s+=947912704,n[t]=e|s}for(let t=1024;t<2048;++t)n[t]=939524096+(t-1024<<13);for(let t=1;t<31;++t)a[t]=t<<23;a[31]=1199570944,a[32]=2147483648;for(let t=33;t<63;++t)a[t]=2147483648+(t-32<<23);a[63]=3347054592;for(let t=1;t<64;++t)32!==t&&(o[t]=1024);return{floatView:e,uint32View:s,baseTable:i,shiftTable:r,mantissaTable:n,exponentTable:a,offsetTable:o}}function an(t){Math.abs(t)>65504&&console.warn("THREE.DataUtils.toHalfFloat(): Value out of range."),t=qs(t,-65504,65504),rn.floatView[0]=t;const e=rn.uint32View[0],s=e>>23&511;return rn.baseTable[s]+((8388607&e)>>rn.shiftTable[s])}function on(t){const e=t>>10;return rn.uint32View[0]=rn.mantissaTable[rn.offsetTable[e]+(1023&t)]+rn.exponentTable[e],rn.floatView[0]}class hn{static toHalfFloat(t){return an(t)}static fromHalfFloat(t){return on(t)}}const ln=new Ks,cn=new $s;let un=0;class dn{constructor(t,e,s=!1){if(Array.isArray(t))throw new TypeError("THREE.BufferAttribute: array should be a Typed Array.");this.isBufferAttribute=!0,Object.defineProperty(this,"id",{value:un++}),this.name="",this.array=t,this.itemSize=e,this.count=void 0!==t?t.length/e:0,this.normalized=s,this.usage=Ss,this.updateRanges=[],this.gpuType=Et,this.version=0}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}addUpdateRange(t,e){this.updateRanges.push({start:t,count:e})}clearUpdateRanges(){this.updateRanges.length=0}copy(t){return this.name=t.name,this.array=new t.array.constructor(t.array),this.itemSize=t.itemSize,this.count=t.count,this.normalized=t.normalized,this.usage=t.usage,this.gpuType=t.gpuType,this}copyAt(t,e,s){t*=this.itemSize,s*=e.itemSize;for(let i=0,r=this.itemSize;ie.count&&console.warn("THREE.BufferGeometry: Buffer size too small for points data. Use .dispose() and create a new geometry."),e.needsUpdate=!0}return this}computeBoundingBox(){null===this.boundingBox&&(this.boundingBox=new Ri);const t=this.attributes.position,e=this.morphAttributes.position;if(t&&t.isGLBufferAttribute)return console.error("THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box.",this),void this.boundingBox.set(new Ks(-1/0,-1/0,-1/0),new Ks(1/0,1/0,1/0));if(void 0!==t){if(this.boundingBox.setFromBufferAttribute(t),e)for(let t=0,s=e.length;t0&&(t.userData=this.userData),void 0!==this.parameters){const e=this.parameters;for(const s in e)void 0!==e[s]&&(t[s]=e[s]);return t}t.data={attributes:{}};const e=this.index;null!==e&&(t.data.index={type:e.array.constructor.name,array:Array.prototype.slice.call(e.array)});const s=this.attributes;for(const e in s){const i=s[e];t.data.attributes[e]=i.toJSON(t.data)}const i={};let r=!1;for(const e in this.morphAttributes){const s=this.morphAttributes[e],n=[];for(let e=0,i=s.length;e0&&(i[e]=n,r=!0)}r&&(t.data.morphAttributes=i,t.data.morphTargetsRelative=this.morphTargetsRelative);const n=this.groups;n.length>0&&(t.data.groups=JSON.parse(JSON.stringify(n)));const a=this.boundingSphere;return null!==a&&(t.data.boundingSphere=a.toJSON()),t}clone(){return(new this.constructor).copy(this)}copy(t){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const e={};this.name=t.name;const s=t.index;null!==s&&this.setIndex(s.clone());const i=t.attributes;for(const t in i){const s=i[t];this.setAttribute(t,s.clone(e))}const r=t.morphAttributes;for(const t in r){const s=[],i=r[t];for(let t=0,r=i.length;t0){const s=t[e[0]];if(void 0!==s){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=s.length;t(t.far-t.near)**2)return}Bn.copy(r).invert(),kn.copy(t.ray).applyMatrix4(Bn),null!==s.boundingBox&&!1===kn.intersectsBox(s.boundingBox)||this._computeIntersections(t,e,kn)}}_computeIntersections(t,e,s){let i;const r=this.geometry,n=this.material,a=r.index,o=r.attributes.position,h=r.attributes.uv,l=r.attributes.uv1,c=r.attributes.normal,u=r.groups,d=r.drawRange;if(null!==a)if(Array.isArray(n))for(let r=0,o=u.length;rs.far?null:{distance:l,point:jn.clone(),object:t}}(t,e,s,i,Pn,On,Nn,Ln);if(c){const t=new Ks;Xr.getBarycoord(Ln,Pn,On,Nn,t),r&&(c.uv=Xr.getInterpolatedAttribute(r,o,h,l,t,new $s)),n&&(c.uv1=Xr.getInterpolatedAttribute(n,o,h,l,t,new $s)),a&&(c.normal=Xr.getInterpolatedAttribute(a,o,h,l,t,new Ks),c.normal.dot(i.direction)>0&&c.normal.multiplyScalar(-1));const e={a:o,b:h,c:l,normal:new Ks,materialIndex:0};Xr.getNormal(Pn,On,Nn,e.normal),c.face=e,c.barycoord=t}return c}class Un extends In{constructor(t=1,e=1,s=1,i=1,r=1,n=1){super(),this.type="BoxGeometry",this.parameters={width:t,height:e,depth:s,widthSegments:i,heightSegments:r,depthSegments:n};const a=this;i=Math.floor(i),r=Math.floor(r),n=Math.floor(n);const o=[],h=[],l=[],c=[];let u=0,d=0;function p(t,e,s,i,r,n,p,m,y,g,f){const x=n/y,b=p/g,v=n/2,w=p/2,M=m/2,S=y+1,_=g+1;let A=0,T=0;const z=new Ks;for(let n=0;n<_;n++){const a=n*b-w;for(let o=0;o0?1:-1,l.push(z.x,z.y,z.z),c.push(o/y),c.push(1-n/g),A+=1}}for(let t=0;t0&&(e.defines=this.defines),e.vertexShader=this.vertexShader,e.fragmentShader=this.fragmentShader,e.lights=this.lights,e.clipping=this.clipping;const s={};for(const t in this.extensions)!0===this.extensions[t]&&(s[t]=!0);return Object.keys(s).length>0&&(e.extensions=s),e}}class Zn extends Rr{constructor(){super(),this.isCamera=!0,this.type="Camera",this.matrixWorldInverse=new ar,this.projectionMatrix=new ar,this.projectionMatrixInverse=new ar,this.coordinateSystem=Ps,this._reversedDepth=!1}get reversedDepth(){return this._reversedDepth}copy(t,e){return super.copy(t,e),this.matrixWorldInverse.copy(t.matrixWorldInverse),this.projectionMatrix.copy(t.projectionMatrix),this.projectionMatrixInverse.copy(t.projectionMatrixInverse),this.coordinateSystem=t.coordinateSystem,this}getWorldDirection(t){return super.getWorldDirection(t).negate()}updateMatrixWorld(t){super.updateMatrixWorld(t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(t,e){super.updateWorldMatrix(t,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return(new this.constructor).copy(this)}}const Gn=new Ks,$n=new $s,Qn=new $s;class Kn extends Zn{constructor(t=50,e=1,s=.1,i=2e3){super(),this.isPerspectiveCamera=!0,this.type="PerspectiveCamera",this.fov=t,this.zoom=1,this.near=s,this.far=i,this.focus=10,this.aspect=e,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.fov=t.fov,this.zoom=t.zoom,this.near=t.near,this.far=t.far,this.focus=t.focus,this.aspect=t.aspect,this.view=null===t.view?null:Object.assign({},t.view),this.filmGauge=t.filmGauge,this.filmOffset=t.filmOffset,this}setFocalLength(t){const e=.5*this.getFilmHeight()/t;this.fov=2*Us*Math.atan(e),this.updateProjectionMatrix()}getFocalLength(){const t=Math.tan(.5*Ws*this.fov);return.5*this.getFilmHeight()/t}getEffectiveFOV(){return 2*Us*Math.atan(Math.tan(.5*Ws*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}getViewBounds(t,e,s){Gn.set(-1,-1,.5).applyMatrix4(this.projectionMatrixInverse),e.set(Gn.x,Gn.y).multiplyScalar(-t/Gn.z),Gn.set(1,1,.5).applyMatrix4(this.projectionMatrixInverse),s.set(Gn.x,Gn.y).multiplyScalar(-t/Gn.z)}getViewSize(t,e){return this.getViewBounds(t,$n,Qn),e.subVectors(Qn,$n)}setViewOffset(t,e,s,i,r,n){this.aspect=t/e,null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=s,this.view.offsetY=i,this.view.width=r,this.view.height=n,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=this.near;let e=t*Math.tan(.5*Ws*this.fov)/this.zoom,s=2*e,i=this.aspect*s,r=-.5*i;const n=this.view;if(null!==this.view&&this.view.enabled){const t=n.fullWidth,a=n.fullHeight;r+=n.offsetX*i/t,e-=n.offsetY*s/a,i*=n.width/t,s*=n.height/a}const a=this.filmOffset;0!==a&&(r+=t*a/this.getFilmWidth()),this.projectionMatrix.makePerspective(r,r+i,e,e-s,t,this.far,this.coordinateSystem,this.reversedDepth),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.fov=this.fov,e.object.zoom=this.zoom,e.object.near=this.near,e.object.far=this.far,e.object.focus=this.focus,e.object.aspect=this.aspect,null!==this.view&&(e.object.view=Object.assign({},this.view)),e.object.filmGauge=this.filmGauge,e.object.filmOffset=this.filmOffset,e}}const ta=-90;class ea extends Rr{constructor(t,e,s){super(),this.type="CubeCamera",this.renderTarget=s,this.coordinateSystem=null,this.activeMipmapLevel=0;const i=new Kn(ta,1,t,e);i.layers=this.layers,this.add(i);const r=new Kn(ta,1,t,e);r.layers=this.layers,this.add(r);const n=new Kn(ta,1,t,e);n.layers=this.layers,this.add(n);const a=new Kn(ta,1,t,e);a.layers=this.layers,this.add(a);const o=new Kn(ta,1,t,e);o.layers=this.layers,this.add(o);const h=new Kn(ta,1,t,e);h.layers=this.layers,this.add(h)}updateCoordinateSystem(){const t=this.coordinateSystem,e=this.children.concat(),[s,i,r,n,a,o]=e;for(const t of e)this.remove(t);if(t===Ps)s.up.set(0,1,0),s.lookAt(1,0,0),i.up.set(0,1,0),i.lookAt(-1,0,0),r.up.set(0,0,-1),r.lookAt(0,1,0),n.up.set(0,0,1),n.lookAt(0,-1,0),a.up.set(0,1,0),a.lookAt(0,0,1),o.up.set(0,1,0),o.lookAt(0,0,-1);else{if(t!==Os)throw new Error("THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinate system: "+t);s.up.set(0,-1,0),s.lookAt(-1,0,0),i.up.set(0,-1,0),i.lookAt(1,0,0),r.up.set(0,0,1),r.lookAt(0,1,0),n.up.set(0,0,-1),n.lookAt(0,-1,0),a.up.set(0,-1,0),a.lookAt(0,0,1),o.up.set(0,-1,0),o.lookAt(0,0,-1)}for(const t of e)this.add(t),t.updateMatrixWorld()}update(t,e){null===this.parent&&this.updateMatrixWorld();const{renderTarget:s,activeMipmapLevel:i}=this;this.coordinateSystem!==t.coordinateSystem&&(this.coordinateSystem=t.coordinateSystem,this.updateCoordinateSystem());const[r,n,a,o,h,l]=this.children,c=t.getRenderTarget(),u=t.getActiveCubeFace(),d=t.getActiveMipmapLevel(),p=t.xr.enabled;t.xr.enabled=!1;const m=s.texture.generateMipmaps;s.texture.generateMipmaps=!1,t.setRenderTarget(s,0,i),t.render(e,r),t.setRenderTarget(s,1,i),t.render(e,n),t.setRenderTarget(s,2,i),t.render(e,a),t.setRenderTarget(s,3,i),t.render(e,o),t.setRenderTarget(s,4,i),t.render(e,h),s.texture.generateMipmaps=m,t.setRenderTarget(s,5,i),t.render(e,l),t.setRenderTarget(c,u,d),t.xr.enabled=p,s.texture.needsPMREMUpdate=!0}}class sa extends Ai{constructor(t=[],e=301,s,i,r,n,a,o,h,l){super(t,e,s,i,r,n,a,o,h,l),this.isCubeTexture=!0,this.flipY=!1}get images(){return this.image}set images(t){this.image=t}}class ia extends Ci{constructor(t=1,e={}){super(t,t,e),this.isWebGLCubeRenderTarget=!0;const s={width:t,height:t,depth:1},i=[s,s,s,s,s,s];this.texture=new sa(i),this._setTextureOptions(e),this.texture.isRenderTargetTexture=!0}fromEquirectangularTexture(t,e){this.texture.type=e.type,this.texture.colorSpace=e.colorSpace,this.texture.generateMipmaps=e.generateMipmaps,this.texture.minFilter=e.minFilter,this.texture.magFilter=e.magFilter;const s={uniforms:{tEquirect:{value:null}},vertexShader:"\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\tvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\n\t\t\t\t\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvWorldDirection = transformDirection( position, modelMatrix );\n\n\t\t\t\t\t#include \n\t\t\t\t\t#include \n\n\t\t\t\t}\n\t\t\t",fragmentShader:"\n\n\t\t\t\tuniform sampler2D tEquirect;\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\t#include \n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvec3 direction = normalize( vWorldDirection );\n\n\t\t\t\t\tvec2 sampleUV = equirectUv( direction );\n\n\t\t\t\t\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\n\t\t\t\t}\n\t\t\t"},i=new Un(5,5,5),r=new Yn({name:"CubemapFromEquirect",uniforms:Hn(s.uniforms),vertexShader:s.vertexShader,fragmentShader:s.fragmentShader,side:1,blending:0});r.uniforms.tEquirect.value=e;const n=new Dn(i,r),a=e.minFilter;e.minFilter===_t&&(e.minFilter=wt);return new ea(1,10,this).update(t,n),e.minFilter=a,n.geometry.dispose(),n.material.dispose(),this}clear(t,e=!0,s=!0,i=!0){const r=t.getRenderTarget();for(let r=0;r<6;r++)t.setRenderTarget(this,r),t.clear(e,s,i);t.setRenderTarget(r)}}class ra extends Rr{constructor(){super(),this.isGroup=!0,this.type="Group"}}const na={type:"move"};class aa{constructor(){this._targetRay=null,this._grip=null,this._hand=null}getHandSpace(){return null===this._hand&&(this._hand=new ra,this._hand.matrixAutoUpdate=!1,this._hand.visible=!1,this._hand.joints={},this._hand.inputState={pinching:!1}),this._hand}getTargetRaySpace(){return null===this._targetRay&&(this._targetRay=new ra,this._targetRay.matrixAutoUpdate=!1,this._targetRay.visible=!1,this._targetRay.hasLinearVelocity=!1,this._targetRay.linearVelocity=new Ks,this._targetRay.hasAngularVelocity=!1,this._targetRay.angularVelocity=new Ks),this._targetRay}getGripSpace(){return null===this._grip&&(this._grip=new ra,this._grip.matrixAutoUpdate=!1,this._grip.visible=!1,this._grip.hasLinearVelocity=!1,this._grip.linearVelocity=new Ks,this._grip.hasAngularVelocity=!1,this._grip.angularVelocity=new Ks),this._grip}dispatchEvent(t){return null!==this._targetRay&&this._targetRay.dispatchEvent(t),null!==this._grip&&this._grip.dispatchEvent(t),null!==this._hand&&this._hand.dispatchEvent(t),this}connect(t){if(t&&t.hand){const e=this._hand;if(e)for(const s of t.hand.values())this._getHandJoint(e,s)}return this.dispatchEvent({type:"connected",data:t}),this}disconnect(t){return this.dispatchEvent({type:"disconnected",data:t}),null!==this._targetRay&&(this._targetRay.visible=!1),null!==this._grip&&(this._grip.visible=!1),null!==this._hand&&(this._hand.visible=!1),this}update(t,e,s){let i=null,r=null,n=null;const a=this._targetRay,o=this._grip,h=this._hand;if(t&&"visible-blurred"!==e.session.visibilityState){if(h&&t.hand){n=!0;for(const i of t.hand.values()){const t=e.getJointPose(i,s),r=this._getHandJoint(h,i);null!==t&&(r.matrix.fromArray(t.transform.matrix),r.matrix.decompose(r.position,r.rotation,r.scale),r.matrixWorldNeedsUpdate=!0,r.jointRadius=t.radius),r.visible=null!==t}const i=h.joints["index-finger-tip"],r=h.joints["thumb-tip"],a=i.position.distanceTo(r.position),o=.02,l=.005;h.inputState.pinching&&a>o+l?(h.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:t.handedness,target:this})):!h.inputState.pinching&&a<=o-l&&(h.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:t.handedness,target:this}))}else null!==o&&t.gripSpace&&(r=e.getPose(t.gripSpace,s),null!==r&&(o.matrix.fromArray(r.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale),o.matrixWorldNeedsUpdate=!0,r.linearVelocity?(o.hasLinearVelocity=!0,o.linearVelocity.copy(r.linearVelocity)):o.hasLinearVelocity=!1,r.angularVelocity?(o.hasAngularVelocity=!0,o.angularVelocity.copy(r.angularVelocity)):o.hasAngularVelocity=!1));null!==a&&(i=e.getPose(t.targetRaySpace,s),null===i&&null!==r&&(i=r),null!==i&&(a.matrix.fromArray(i.transform.matrix),a.matrix.decompose(a.position,a.rotation,a.scale),a.matrixWorldNeedsUpdate=!0,i.linearVelocity?(a.hasLinearVelocity=!0,a.linearVelocity.copy(i.linearVelocity)):a.hasLinearVelocity=!1,i.angularVelocity?(a.hasAngularVelocity=!0,a.angularVelocity.copy(i.angularVelocity)):a.hasAngularVelocity=!1,this.dispatchEvent(na)))}return null!==a&&(a.visible=null!==i),null!==o&&(o.visible=null!==r),null!==h&&(h.visible=null!==n),this}_getHandJoint(t,e){if(void 0===t.joints[e.jointName]){const s=new ra;s.matrixAutoUpdate=!1,s.visible=!1,t.joints[e.jointName]=s,t.add(s)}return t.joints[e.jointName]}}class oa{constructor(t,e=25e-5){this.isFogExp2=!0,this.name="",this.color=new Qr(t),this.density=e}clone(){return new oa(this.color,this.density)}toJSON(){return{type:"FogExp2",name:this.name,color:this.color.getHex(),density:this.density}}}class ha{constructor(t,e=1,s=1e3){this.isFog=!0,this.name="",this.color=new Qr(t),this.near=e,this.far=s}clone(){return new ha(this.color,this.near,this.far)}toJSON(){return{type:"Fog",name:this.name,color:this.color.getHex(),near:this.near,far:this.far}}}class la extends Rr{constructor(){super(),this.isScene=!0,this.type="Scene",this.background=null,this.environment=null,this.fog=null,this.backgroundBlurriness=0,this.backgroundIntensity=1,this.backgroundRotation=new gr,this.environmentIntensity=1,this.environmentRotation=new gr,this.overrideMaterial=null,"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}copy(t,e){return super.copy(t,e),null!==t.background&&(this.background=t.background.clone()),null!==t.environment&&(this.environment=t.environment.clone()),null!==t.fog&&(this.fog=t.fog.clone()),this.backgroundBlurriness=t.backgroundBlurriness,this.backgroundIntensity=t.backgroundIntensity,this.backgroundRotation.copy(t.backgroundRotation),this.environmentIntensity=t.environmentIntensity,this.environmentRotation.copy(t.environmentRotation),null!==t.overrideMaterial&&(this.overrideMaterial=t.overrideMaterial.clone()),this.matrixAutoUpdate=t.matrixAutoUpdate,this}toJSON(t){const e=super.toJSON(t);return null!==this.fog&&(e.object.fog=this.fog.toJSON()),this.backgroundBlurriness>0&&(e.object.backgroundBlurriness=this.backgroundBlurriness),1!==this.backgroundIntensity&&(e.object.backgroundIntensity=this.backgroundIntensity),e.object.backgroundRotation=this.backgroundRotation.toArray(),1!==this.environmentIntensity&&(e.object.environmentIntensity=this.environmentIntensity),e.object.environmentRotation=this.environmentRotation.toArray(),e}}class ca{constructor(t,e){this.isInterleavedBuffer=!0,this.array=t,this.stride=e,this.count=void 0!==t?t.length/e:0,this.usage=Ss,this.updateRanges=[],this.version=0,this.uuid=Hs()}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}addUpdateRange(t,e){this.updateRanges.push({start:t,count:e})}clearUpdateRanges(){this.updateRanges.length=0}copy(t){return this.array=new t.array.constructor(t.array),this.count=t.count,this.stride=t.stride,this.usage=t.usage,this}copyAt(t,e,s){t*=this.stride,s*=e.stride;for(let i=0,r=this.stride;it.far||e.push({distance:o,point:ya.clone(),uv:Xr.getInterpolation(ya,wa,Ma,Sa,_a,Aa,Ta,new $s),face:null,object:this})}copy(t,e){return super.copy(t,e),void 0!==t.center&&this.center.copy(t.center),this.material=t.material,this}}function Ca(t,e,s,i,r,n){xa.subVectors(t,s).addScalar(.5).multiply(i),void 0!==r?(ba.x=n*xa.x-r*xa.y,ba.y=r*xa.x+n*xa.y):ba.copy(xa),t.copy(e),t.x+=ba.x,t.y+=ba.y,t.applyMatrix4(va)}const Ia=new Ks,Ba=new Ks;class ka extends Rr{constructor(){super(),this.isLOD=!0,this._currentLevel=0,this.type="LOD",Object.defineProperties(this,{levels:{enumerable:!0,value:[]}}),this.autoUpdate=!0}copy(t){super.copy(t,!1);const e=t.levels;for(let t=0,s=e.length;t0){let s,i;for(s=1,i=e.length;s0){Ia.setFromMatrixPosition(this.matrixWorld);const s=t.ray.origin.distanceTo(Ia);this.getObjectForDistance(s).raycast(t,e)}}update(t){const e=this.levels;if(e.length>1){Ia.setFromMatrixPosition(t.matrixWorld),Ba.setFromMatrixPosition(this.matrixWorld);const s=Ia.distanceTo(Ba)/t.zoom;let i,r;for(e[0].object.visible=!0,i=1,r=e.length;i=t))break;e[i-1].object.visible=!1,e[i].object.visible=!0}for(this._currentLevel=i-1;i1?null:e.copy(t.start).addScaledVector(s,r)}intersectsLine(t){const e=this.distanceToPoint(t.start),s=this.distanceToPoint(t.end);return e<0&&s>0||s<0&&e>0}intersectsBox(t){return t.intersectsPlane(this)}intersectsSphere(t){return t.intersectsPlane(this)}coplanarPoint(t){return t.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(t,e){const s=e||ro.getNormalMatrix(t),i=this.coplanarPoint(so).applyMatrix4(t),r=this.normal.applyMatrix3(s).normalize();return this.constant=-i.dot(r),this}translate(t){return this.constant-=t.dot(this.normal),this}equals(t){return t.normal.equals(this.normal)&&t.constant===this.constant}clone(){return(new this.constructor).copy(this)}}const ao=new $i,oo=new $s(.5,.5),ho=new Ks;class lo{constructor(t=new no,e=new no,s=new no,i=new no,r=new no,n=new no){this.planes=[t,e,s,i,r,n]}set(t,e,s,i,r,n){const a=this.planes;return a[0].copy(t),a[1].copy(e),a[2].copy(s),a[3].copy(i),a[4].copy(r),a[5].copy(n),this}copy(t){const e=this.planes;for(let s=0;s<6;s++)e[s].copy(t.planes[s]);return this}setFromProjectionMatrix(t,e=2e3,s=!1){const i=this.planes,r=t.elements,n=r[0],a=r[1],o=r[2],h=r[3],l=r[4],c=r[5],u=r[6],d=r[7],p=r[8],m=r[9],y=r[10],g=r[11],f=r[12],x=r[13],b=r[14],v=r[15];if(i[0].setComponents(h-n,d-l,g-p,v-f).normalize(),i[1].setComponents(h+n,d+l,g+p,v+f).normalize(),i[2].setComponents(h+a,d+c,g+m,v+x).normalize(),i[3].setComponents(h-a,d-c,g-m,v-x).normalize(),s)i[4].setComponents(o,u,y,b).normalize(),i[5].setComponents(h-o,d-u,g-y,v-b).normalize();else if(i[4].setComponents(h-o,d-u,g-y,v-b).normalize(),e===Ps)i[5].setComponents(h+o,d+u,g+y,v+b).normalize();else{if(e!==Os)throw new Error("THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: "+e);i[5].setComponents(o,u,y,b).normalize()}return this}intersectsObject(t){if(void 0!==t.boundingSphere)null===t.boundingSphere&&t.computeBoundingSphere(),ao.copy(t.boundingSphere).applyMatrix4(t.matrixWorld);else{const e=t.geometry;null===e.boundingSphere&&e.computeBoundingSphere(),ao.copy(e.boundingSphere).applyMatrix4(t.matrixWorld)}return this.intersectsSphere(ao)}intersectsSprite(t){ao.center.set(0,0,0);const e=oo.distanceTo(t.center);return ao.radius=.7071067811865476+e,ao.applyMatrix4(t.matrixWorld),this.intersectsSphere(ao)}intersectsSphere(t){const e=this.planes,s=t.center,i=-t.radius;for(let t=0;t<6;t++){if(e[t].distanceToPoint(s)0?t.max.x:t.min.x,ho.y=i.normal.y>0?t.max.y:t.min.y,ho.z=i.normal.z>0?t.max.z:t.min.z,i.distanceToPoint(ho)<0)return!1}return!0}containsPoint(t){const e=this.planes;for(let s=0;s<6;s++)if(e[s].distanceToPoint(t)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}const co=new ar,uo=new lo;class po{constructor(){this.coordinateSystem=Ps}intersectsObject(t,e){if(!e.isArrayCamera||0===e.cameras.length)return!1;for(let s=0;s=r.length&&r.push({start:-1,count:-1,z:-1,index:-1});const a=r[this.index];n.push(a),this.index++,a.start=t,a.count=e,a.z=s,a.index=i}reset(){this.list.length=0,this.index=0}}const xo=new ar,bo=new Qr(1,1,1),vo=new lo,wo=new po,Mo=new Ri,So=new $i,_o=new Ks,Ao=new Ks,To=new Ks,zo=new fo,Co=new Dn,Io=[];function Bo(t,e,s=0){const i=e.itemSize;if(t.isInterleavedBufferAttribute||t.array.constructor!==e.array.constructor){const r=t.count;for(let n=0;n65535?new Uint32Array(i):new Uint16Array(i);e.setIndex(new dn(t,1))}this._geometryInitialized=!0}}_validateGeometry(t){const e=this.geometry;if(Boolean(t.getIndex())!==Boolean(e.getIndex()))throw new Error('THREE.BatchedMesh: All geometries must consistently have "index".');for(const s in e.attributes){if(!t.hasAttribute(s))throw new Error(`THREE.BatchedMesh: Added geometry missing "${s}". All geometries must have consistent attributes.`);const i=t.getAttribute(s),r=e.getAttribute(s);if(i.itemSize!==r.itemSize||i.normalized!==r.normalized)throw new Error("THREE.BatchedMesh: All attributes must have a consistent itemSize and normalized value.")}}validateInstanceId(t){const e=this._instanceInfo;if(t<0||t>=e.length||!1===e[t].active)throw new Error(`THREE.BatchedMesh: Invalid instanceId ${t}. Instance is either out of range or has been deleted.`)}validateGeometryId(t){const e=this._geometryInfo;if(t<0||t>=e.length||!1===e[t].active)throw new Error(`THREE.BatchedMesh: Invalid geometryId ${t}. Geometry is either out of range or has been deleted.`)}setCustomSort(t){return this.customSort=t,this}computeBoundingBox(){null===this.boundingBox&&(this.boundingBox=new Ri);const t=this.boundingBox,e=this._instanceInfo;t.makeEmpty();for(let s=0,i=e.length;s=this.maxInstanceCount&&0===this._availableInstanceIds.length)throw new Error("THREE.BatchedMesh: Maximum item count reached.");const e={visible:!0,active:!0,geometryIndex:t};let s=null;this._availableInstanceIds.length>0?(this._availableInstanceIds.sort(mo),s=this._availableInstanceIds.shift(),this._instanceInfo[s]=e):(s=this._instanceInfo.length,this._instanceInfo.push(e));const i=this._matricesTexture;xo.identity().toArray(i.image.data,16*s),i.needsUpdate=!0;const r=this._colorsTexture;return r&&(bo.toArray(r.image.data,4*s),r.needsUpdate=!0),this._visibilityChanged=!0,s}addGeometry(t,e=-1,s=-1){this._initializeGeometry(t),this._validateGeometry(t);const i={vertexStart:-1,vertexCount:-1,reservedVertexCount:-1,indexStart:-1,indexCount:-1,reservedIndexCount:-1,start:-1,count:-1,boundingBox:null,boundingSphere:null,active:!0},r=this._geometryInfo;i.vertexStart=this._nextVertexStart,i.reservedVertexCount=-1===e?t.getAttribute("position").count:e;const n=t.getIndex();if(null!==n&&(i.indexStart=this._nextIndexStart,i.reservedIndexCount=-1===s?n.count:s),-1!==i.indexStart&&i.indexStart+i.reservedIndexCount>this._maxIndexCount||i.vertexStart+i.reservedVertexCount>this._maxVertexCount)throw new Error("THREE.BatchedMesh: Reserved space request exceeds the maximum buffer size.");let a;return this._availableGeometryIds.length>0?(this._availableGeometryIds.sort(mo),a=this._availableGeometryIds.shift(),r[a]=i):(a=this._geometryCount,this._geometryCount++,r.push(i)),this.setGeometryAt(a,t),this._nextIndexStart=i.indexStart+i.reservedIndexCount,this._nextVertexStart=i.vertexStart+i.reservedVertexCount,a}setGeometryAt(t,e){if(t>=this._geometryCount)throw new Error("THREE.BatchedMesh: Maximum geometry count reached.");this._validateGeometry(e);const s=this.geometry,i=null!==s.getIndex(),r=s.getIndex(),n=e.getIndex(),a=this._geometryInfo[t];if(i&&n.count>a.reservedIndexCount||e.attributes.position.count>a.reservedVertexCount)throw new Error("THREE.BatchedMesh: Reserved space not large enough for provided geometry.");const o=a.vertexStart,h=a.reservedVertexCount;a.vertexCount=e.getAttribute("position").count;for(const t in s.attributes){const i=e.getAttribute(t),r=s.getAttribute(t);Bo(i,r,o);const n=i.itemSize;for(let t=i.count,e=h;t=e.length||!1===e[t].active)return this;const s=this._instanceInfo;for(let e=0,i=s.length;ee).sort((t,e)=>s[t].vertexStart-s[e].vertexStart),r=this.geometry;for(let n=0,a=s.length;n=this._geometryCount)return null;const s=this.geometry,i=this._geometryInfo[t];if(null===i.boundingBox){const t=new Ri,e=s.index,r=s.attributes.position;for(let s=i.start,n=i.start+i.count;s=this._geometryCount)return null;const s=this.geometry,i=this._geometryInfo[t];if(null===i.boundingSphere){const e=new $i;this.getBoundingBoxAt(t,Mo),Mo.getCenter(e.center);const r=s.index,n=s.attributes.position;let a=0;for(let t=i.start,s=i.start+i.count;tt.active);if(Math.max(...s.map(t=>t.vertexStart+t.reservedVertexCount))>t)throw new Error(`BatchedMesh: Geometry vertex values are being used outside the range ${e}. Cannot shrink further.`);if(this.geometry.index){if(Math.max(...s.map(t=>t.indexStart+t.reservedIndexCount))>e)throw new Error(`BatchedMesh: Geometry index values are being used outside the range ${e}. Cannot shrink further.`)}const i=this.geometry;i.dispose(),this._maxVertexCount=t,this._maxIndexCount=e,this._geometryInitialized&&(this._geometryInitialized=!1,this.geometry=new In,this._initializeGeometry(i));const r=this.geometry;i.index&&ko(i.index.array,r.index.array);for(const t in i.attributes)ko(i.attributes[t].array,r.attributes[t].array)}raycast(t,e){const s=this._instanceInfo,i=this._geometryInfo,r=this.matrixWorld,n=this.geometry;Co.material=this.material,Co.geometry.index=n.index,Co.geometry.attributes=n.attributes,null===Co.geometry.boundingBox&&(Co.geometry.boundingBox=new Ri),null===Co.geometry.boundingSphere&&(Co.geometry.boundingSphere=new $i);for(let n=0,a=s.length;n({...t,boundingBox:null!==t.boundingBox?t.boundingBox.clone():null,boundingSphere:null!==t.boundingSphere?t.boundingSphere.clone():null})),this._instanceInfo=t._instanceInfo.map(t=>({...t})),this._availableInstanceIds=t._availableInstanceIds.slice(),this._availableGeometryIds=t._availableGeometryIds.slice(),this._nextIndexStart=t._nextIndexStart,this._nextVertexStart=t._nextVertexStart,this._geometryCount=t._geometryCount,this._maxInstanceCount=t._maxInstanceCount,this._maxVertexCount=t._maxVertexCount,this._maxIndexCount=t._maxIndexCount,this._geometryInitialized=t._geometryInitialized,this._multiDrawCounts=t._multiDrawCounts.slice(),this._multiDrawStarts=t._multiDrawStarts.slice(),this._indirectTexture=t._indirectTexture.clone(),this._indirectTexture.image.data=this._indirectTexture.image.data.slice(),this._matricesTexture=t._matricesTexture.clone(),this._matricesTexture.image.data=this._matricesTexture.image.data.slice(),null!==this._colorsTexture&&(this._colorsTexture=t._colorsTexture.clone(),this._colorsTexture.image.data=this._colorsTexture.image.data.slice()),this}dispose(){this.geometry.dispose(),this._matricesTexture.dispose(),this._matricesTexture=null,this._indirectTexture.dispose(),this._indirectTexture=null,null!==this._colorsTexture&&(this._colorsTexture.dispose(),this._colorsTexture=null)}onBeforeRender(t,e,s,i,r){if(!this._visibilityChanged&&!this.perObjectFrustumCulled&&!this.sortObjects)return;const n=i.getIndex(),a=null===n?1:n.array.BYTES_PER_ELEMENT,o=this._instanceInfo,h=this._multiDrawStarts,l=this._multiDrawCounts,c=this._geometryInfo,u=this.perObjectFrustumCulled,d=this._indirectTexture,p=d.image.data,m=s.isArrayCamera?wo:vo;u&&!s.isArrayCamera&&(xo.multiplyMatrices(s.projectionMatrix,s.matrixWorldInverse).multiply(this.matrixWorld),vo.setFromProjectionMatrix(xo,s.coordinateSystem,s.reversedDepth));let y=0;if(this.sortObjects){xo.copy(this.matrixWorld).invert(),_o.setFromMatrixPosition(s.matrixWorld).applyMatrix4(xo),Ao.set(0,0,-1).transformDirection(s.matrixWorld).transformDirection(xo);for(let t=0,e=o.length;t0){const s=t[e[0]];if(void 0!==s){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=s.length;ti)return;Lo.applyMatrix4(t.matrixWorld);const h=e.ray.origin.distanceTo(Lo);return he.far?void 0:{distance:h,point:jo.clone().applyMatrix4(t.matrixWorld),index:a,face:null,faceIndex:null,barycoord:null,object:t}}const Uo=new Ks,Ho=new Ks;class qo extends Do{constructor(t,e){super(t,e),this.isLineSegments=!0,this.type="LineSegments"}computeLineDistances(){const t=this.geometry;if(null===t.index){const e=t.attributes.position,s=[];for(let t=0,i=e.count;t0){const s=t[e[0]];if(void 0!==s){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=s.length;tr.far)return;n.push({distance:h,distanceToRay:Math.sqrt(o),point:s,index:e,face:null,faceIndex:null,barycoord:null,object:a})}}class th extends Ai{constructor(t,e,s,i,r=1006,n=1006,a,o,h){super(t,e,s,i,r,n,a,o,h),this.isVideoTexture=!0,this.generateMipmaps=!1,this._requestVideoFrameCallbackId=0;const l=this;"requestVideoFrameCallback"in t&&(this._requestVideoFrameCallbackId=t.requestVideoFrameCallback(function e(){l.needsUpdate=!0,l._requestVideoFrameCallbackId=t.requestVideoFrameCallback(e)}))}clone(){return new this.constructor(this.image).copy(this)}update(){const t=this.image;!1==="requestVideoFrameCallback"in t&&t.readyState>=t.HAVE_CURRENT_DATA&&(this.needsUpdate=!0)}dispose(){0!==this._requestVideoFrameCallbackId&&this.source.data.cancelVideoFrameCallback(this._requestVideoFrameCallbackId),super.dispose()}}class eh extends th{constructor(t,e,s,i,r,n,a,o){super({},t,e,s,i,r,n,a,o),this.isVideoFrameTexture=!0}update(){}clone(){return(new this.constructor).copy(this)}setFrame(t){this.image=t,this.needsUpdate=!0}}class sh extends Ai{constructor(t,e){super({width:t,height:e}),this.isFramebufferTexture=!0,this.magFilter=gt,this.minFilter=gt,this.generateMipmaps=!1,this.needsUpdate=!0}}class ih extends Ai{constructor(t,e,s,i,r,n,a,o,h,l,c,u){super(null,n,a,o,h,l,i,r,c,u),this.isCompressedTexture=!0,this.image={width:e,height:s},this.mipmaps=t,this.flipY=!1,this.generateMipmaps=!1}}class rh extends ih{constructor(t,e,s,i,r,n){super(t,e,s,r,n),this.isCompressedArrayTexture=!0,this.image.depth=i,this.wrapR=mt,this.layerUpdates=new Set}addLayerUpdate(t){this.layerUpdates.add(t)}clearLayerUpdates(){this.layerUpdates.clear()}}class nh extends ih{constructor(t,e,s){super(void 0,t[0].width,t[0].height,e,s,ht),this.isCompressedCubeTexture=!0,this.isCubeTexture=!0,this.image=t}}class ah extends Ai{constructor(t,e,s,i,r,n,a,o,h){super(t,e,s,i,r,n,a,o,h),this.isCanvasTexture=!0,this.needsUpdate=!0}}class oh extends Ai{constructor(t,e,s=1014,i,r,n,a=1003,o=1003,h,l=1026,c=1){if(l!==Wt&&1027!==l)throw new Error("DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat");super({width:t,height:e,depth:c},i,r,n,a,o,l,s,h),this.isDepthTexture=!0,this.flipY=!1,this.generateMipmaps=!1,this.compareFunction=null}copy(t){return super.copy(t),this.source=new wi(Object.assign({},t.image)),this.compareFunction=t.compareFunction,this}toJSON(t){const e=super.toJSON(t);return null!==this.compareFunction&&(e.compareFunction=this.compareFunction),e}}class hh extends Ai{constructor(t=null){super(),this.sourceTexture=t,this.isExternalTexture=!0}copy(t){return super.copy(t),this.sourceTexture=t.sourceTexture,this}}class lh extends In{constructor(t=1,e=1,s=4,i=8,r=1){super(),this.type="CapsuleGeometry",this.parameters={radius:t,height:e,capSegments:s,radialSegments:i,heightSegments:r},e=Math.max(0,e),s=Math.max(1,Math.floor(s)),i=Math.max(3,Math.floor(i)),r=Math.max(1,Math.floor(r));const n=[],a=[],o=[],h=[],l=e/2,c=Math.PI/2*t,u=e,d=2*c+u,p=2*s+r,m=i+1,y=new Ks,g=new Ks;for(let f=0;f<=p;f++){let x=0,b=0,v=0,w=0;if(f<=s){const e=f/s,i=e*Math.PI/2;b=-l-t*Math.cos(i),v=t*Math.sin(i),w=-t*Math.cos(i),x=e*c}else if(f<=s+r){const i=(f-s)/r;b=i*e-l,v=t,w=0,x=c+i*u}else{const e=(f-s-r)/s,i=e*Math.PI/2;b=l+t*Math.sin(i),v=t*Math.cos(i),w=t*Math.sin(i),x=c+u+e*c}const M=Math.max(0,Math.min(1,x/d));let S=0;0===f?S=.5/i:f===p&&(S=-.5/i);for(let t=0;t<=i;t++){const e=t/i,s=e*Math.PI*2,r=Math.sin(s),n=Math.cos(s);g.x=-v*n,g.y=b,g.z=v*r,a.push(g.x,g.y,g.z),y.set(-v*n,w,v*r),y.normalize(),o.push(y.x,y.y,y.z),h.push(e+S,M)}if(f>0){const t=(f-1)*m;for(let e=0;e0||0!==i)&&(l.push(n,a,h),x+=3),(e>0||i!==r-1)&&(l.push(a,o,h),x+=3)}h.addGroup(g,x,0),g+=x}(),!1===n&&(t>0&&f(!0),e>0&&f(!1)),this.setIndex(l),this.setAttribute("position",new wn(c,3)),this.setAttribute("normal",new wn(u,3)),this.setAttribute("uv",new wn(d,2))}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new uh(t.radiusTop,t.radiusBottom,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class dh extends uh{constructor(t=1,e=1,s=32,i=1,r=!1,n=0,a=2*Math.PI){super(0,t,e,s,i,r,n,a),this.type="ConeGeometry",this.parameters={radius:t,height:e,radialSegments:s,heightSegments:i,openEnded:r,thetaStart:n,thetaLength:a}}static fromJSON(t){return new dh(t.radius,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class ph extends In{constructor(t=[],e=[],s=1,i=0){super(),this.type="PolyhedronGeometry",this.parameters={vertices:t,indices:e,radius:s,detail:i};const r=[],n=[];function a(t,e,s,i){const r=i+1,n=[];for(let i=0;i<=r;i++){n[i]=[];const a=t.clone().lerp(s,i/r),o=e.clone().lerp(s,i/r),h=r-i;for(let t=0;t<=h;t++)n[i][t]=0===t&&i===r?a:a.clone().lerp(o,t/h)}for(let t=0;t.9&&a<.1&&(e<.2&&(n[t+0]+=1),s<.2&&(n[t+2]+=1),i<.2&&(n[t+4]+=1))}}()}(),this.setAttribute("position",new wn(r,3)),this.setAttribute("normal",new wn(r.slice(),3)),this.setAttribute("uv",new wn(n,2)),0===i?this.computeVertexNormals():this.normalizeNormals()}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new ph(t.vertices,t.indices,t.radius,t.details)}}class mh extends ph{constructor(t=1,e=0){const s=(1+Math.sqrt(5))/2,i=1/s;super([-1,-1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,1,0,-i,-s,0,-i,s,0,i,-s,0,i,s,-i,-s,0,-i,s,0,i,-s,0,i,s,0,-s,0,-i,s,0,-i,-s,0,i,s,0,i],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],t,e),this.type="DodecahedronGeometry",this.parameters={radius:t,detail:e}}static fromJSON(t){return new mh(t.radius,t.detail)}}const yh=new Ks,gh=new Ks,fh=new Ks,xh=new Xr;class bh extends In{constructor(t=null,e=1){if(super(),this.type="EdgesGeometry",this.parameters={geometry:t,thresholdAngle:e},null!==t){const s=4,i=Math.pow(10,s),r=Math.cos(Ws*e),n=t.getIndex(),a=t.getAttribute("position"),o=n?n.count:a.count,h=[0,0,0],l=["a","b","c"],c=new Array(3),u={},d=[];for(let t=0;t0)){h=i;break}h=i-1}if(i=h,s[i]===n)return i/(r-1);const l=s[i];return(i+(n-l)/(s[i+1]-l))/(r-1)}getTangent(t,e){const s=1e-4;let i=t-s,r=t+s;i<0&&(i=0),r>1&&(r=1);const n=this.getPoint(i),a=this.getPoint(r),o=e||(n.isVector2?new $s:new Ks);return o.copy(a).sub(n).normalize(),o}getTangentAt(t,e){const s=this.getUtoTmapping(t);return this.getTangent(s,e)}computeFrenetFrames(t,e=!1){const s=new Ks,i=[],r=[],n=[],a=new Ks,o=new ar;for(let e=0;e<=t;e++){const s=e/t;i[e]=this.getTangentAt(s,new Ks)}r[0]=new Ks,n[0]=new Ks;let h=Number.MAX_VALUE;const l=Math.abs(i[0].x),c=Math.abs(i[0].y),u=Math.abs(i[0].z);l<=h&&(h=l,s.set(1,0,0)),c<=h&&(h=c,s.set(0,1,0)),u<=h&&s.set(0,0,1),a.crossVectors(i[0],s).normalize(),r[0].crossVectors(i[0],a),n[0].crossVectors(i[0],r[0]);for(let e=1;e<=t;e++){if(r[e]=r[e-1].clone(),n[e]=n[e-1].clone(),a.crossVectors(i[e-1],i[e]),a.length()>Number.EPSILON){a.normalize();const t=Math.acos(qs(i[e-1].dot(i[e]),-1,1));r[e].applyMatrix4(o.makeRotationAxis(a,t))}n[e].crossVectors(i[e],r[e])}if(!0===e){let e=Math.acos(qs(r[0].dot(r[t]),-1,1));e/=t,i[0].dot(a.crossVectors(r[0],r[t]))>0&&(e=-e);for(let s=1;s<=t;s++)r[s].applyMatrix4(o.makeRotationAxis(i[s],e*s)),n[s].crossVectors(i[s],r[s])}return{tangents:i,normals:r,binormals:n}}clone(){return(new this.constructor).copy(this)}copy(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}toJSON(){const t={metadata:{version:4.7,type:"Curve",generator:"Curve.toJSON"}};return t.arcLengthDivisions=this.arcLengthDivisions,t.type=this.type,t}fromJSON(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}}class wh extends vh{constructor(t=0,e=0,s=1,i=1,r=0,n=2*Math.PI,a=!1,o=0){super(),this.isEllipseCurve=!0,this.type="EllipseCurve",this.aX=t,this.aY=e,this.xRadius=s,this.yRadius=i,this.aStartAngle=r,this.aEndAngle=n,this.aClockwise=a,this.aRotation=o}getPoint(t,e=new $s){const s=e,i=2*Math.PI;let r=this.aEndAngle-this.aStartAngle;const n=Math.abs(r)i;)r-=i;r0?0:(Math.floor(Math.abs(h)/r)+1)*r:0===l&&h===r-1&&(h=r-2,l=1),this.closed||h>0?a=i[(h-1)%r]:(_h.subVectors(i[0],i[1]).add(i[0]),a=_h);const c=i[h%r],u=i[(h+1)%r];if(this.closed||h+2i.length-2?i.length-1:n+1],c=i[n>i.length-3?i.length-1:n+2];return s.set(Ih(a,o.x,h.x,l.x,c.x),Ih(a,o.y,h.y,l.y,c.y)),s}copy(t){super.copy(t),this.points=[];for(let e=0,s=t.points.length;e=s){const t=i[r]-s,n=this.curves[r],a=n.getLength(),o=0===a?0:1-t/a;return n.getPointAt(o,e)}r++}return null}getLength(){const t=this.getCurveLengths();return t[t.length-1]}updateArcLengths(){this.needsUpdate=!0,this.cacheLengths=null,this.getCurveLengths()}getCurveLengths(){if(this.cacheLengths&&this.cacheLengths.length===this.curves.length)return this.cacheLengths;const t=[];let e=0;for(let s=0,i=this.curves.length;s1&&!e[e.length-1].equals(e[0])&&e.push(e[0]),e}copy(t){super.copy(t),this.curves=[];for(let e=0,s=t.curves.length;e0){const t=h.getPoint(0);t.equals(this.currentPoint)||this.lineTo(t.x,t.y)}this.curves.push(h);const l=h.getPoint(1);return this.currentPoint.copy(l),this}copy(t){return super.copy(t),this.currentPoint.copy(t.currentPoint),this}toJSON(){const t=super.toJSON();return t.currentPoint=this.currentPoint.toArray(),t}fromJSON(t){return super.fromJSON(t),this.currentPoint.fromArray(t.currentPoint),this}}class Wh extends Dh{constructor(t){super(t),this.uuid=Hs(),this.type="Shape",this.holes=[]}getPointsHoles(t){const e=[];for(let s=0,i=this.holes.length;s80*s){o=1/0,h=1/0;let e=-1/0,i=-1/0;for(let n=s;ne&&(e=s),r>i&&(i=r)}l=Math.max(e-o,i-h),l=0!==l?32767/l:0}return Jh(n,a,s,o,h,l,0),a}function Hh(t,e,s,i,r){let n;if(r===function(t,e,s,i){let r=0;for(let n=e,a=s-i;n0)for(let r=e;r=e;r-=i)n=dl(r/i|0,t[r],t[r+1],n);return n&&al(n,n.next)&&(pl(n),n=n.next),n}function qh(t,e){if(!t)return t;e||(e=t);let s,i=t;do{if(s=!1,i.steiner||!al(i,i.next)&&0!==nl(i.prev,i,i.next))i=i.next;else{if(pl(i),i=e=i.prev,i===i.next)break;s=!0}}while(s||i!==e);return e}function Jh(t,e,s,i,r,n,a){if(!t)return;!a&&n&&function(t,e,s,i){let r=t;do{0===r.z&&(r.z=tl(r.x,r.y,e,s,i)),r.prevZ=r.prev,r.nextZ=r.next,r=r.next}while(r!==t);r.prevZ.nextZ=null,r.prevZ=null,function(t){let e,s=1;do{let i,r=t;t=null;let n=null;for(e=0;r;){e++;let a=r,o=0;for(let t=0;t0||h>0&&a;)0!==o&&(0===h||!a||r.z<=a.z)?(i=r,r=r.nextZ,o--):(i=a,a=a.nextZ,h--),n?n.nextZ=i:t=i,i.prevZ=n,n=i;r=a}n.nextZ=null,s*=2}while(e>1)}(r)}(t,i,r,n);let o=t;for(;t.prev!==t.next;){const h=t.prev,l=t.next;if(n?Yh(t,i,r,n):Xh(t))e.push(h.i,t.i,l.i),pl(t),t=l.next,o=l.next;else if((t=l)===o){a?1===a?Jh(t=Zh(qh(t),e),e,s,i,r,n,2):2===a&&Gh(t,e,s,i,r,n):Jh(qh(t),e,s,i,r,n,1);break}}}function Xh(t){const e=t.prev,s=t,i=t.next;if(nl(e,s,i)>=0)return!1;const r=e.x,n=s.x,a=i.x,o=e.y,h=s.y,l=i.y,c=Math.min(r,n,a),u=Math.min(o,h,l),d=Math.max(r,n,a),p=Math.max(o,h,l);let m=i.next;for(;m!==e;){if(m.x>=c&&m.x<=d&&m.y>=u&&m.y<=p&&il(r,o,n,h,a,l,m.x,m.y)&&nl(m.prev,m,m.next)>=0)return!1;m=m.next}return!0}function Yh(t,e,s,i){const r=t.prev,n=t,a=t.next;if(nl(r,n,a)>=0)return!1;const o=r.x,h=n.x,l=a.x,c=r.y,u=n.y,d=a.y,p=Math.min(o,h,l),m=Math.min(c,u,d),y=Math.max(o,h,l),g=Math.max(c,u,d),f=tl(p,m,e,s,i),x=tl(y,g,e,s,i);let b=t.prevZ,v=t.nextZ;for(;b&&b.z>=f&&v&&v.z<=x;){if(b.x>=p&&b.x<=y&&b.y>=m&&b.y<=g&&b!==r&&b!==a&&il(o,c,h,u,l,d,b.x,b.y)&&nl(b.prev,b,b.next)>=0)return!1;if(b=b.prevZ,v.x>=p&&v.x<=y&&v.y>=m&&v.y<=g&&v!==r&&v!==a&&il(o,c,h,u,l,d,v.x,v.y)&&nl(v.prev,v,v.next)>=0)return!1;v=v.nextZ}for(;b&&b.z>=f;){if(b.x>=p&&b.x<=y&&b.y>=m&&b.y<=g&&b!==r&&b!==a&&il(o,c,h,u,l,d,b.x,b.y)&&nl(b.prev,b,b.next)>=0)return!1;b=b.prevZ}for(;v&&v.z<=x;){if(v.x>=p&&v.x<=y&&v.y>=m&&v.y<=g&&v!==r&&v!==a&&il(o,c,h,u,l,d,v.x,v.y)&&nl(v.prev,v,v.next)>=0)return!1;v=v.nextZ}return!0}function Zh(t,e){let s=t;do{const i=s.prev,r=s.next.next;!al(i,r)&&ol(i,s,s.next,r)&&cl(i,r)&&cl(r,i)&&(e.push(i.i,s.i,r.i),pl(s),pl(s.next),s=t=r),s=s.next}while(s!==t);return qh(s)}function Gh(t,e,s,i,r,n){let a=t;do{let t=a.next.next;for(;t!==a.prev;){if(a.i!==t.i&&rl(a,t)){let o=ul(a,t);return a=qh(a,a.next),o=qh(o,o.next),Jh(a,e,s,i,r,n,0),void Jh(o,e,s,i,r,n,0)}t=t.next}a=a.next}while(a!==t)}function $h(t,e){let s=t.x-e.x;if(0===s&&(s=t.y-e.y,0===s)){s=(t.next.y-t.y)/(t.next.x-t.x)-(e.next.y-e.y)/(e.next.x-e.x)}return s}function Qh(t,e){const s=function(t,e){let s=e;const i=t.x,r=t.y;let n,a=-1/0;if(al(t,s))return s;do{if(al(t,s.next))return s.next;if(r<=s.y&&r>=s.next.y&&s.next.y!==s.y){const t=s.x+(r-s.y)*(s.next.x-s.x)/(s.next.y-s.y);if(t<=i&&t>a&&(a=t,n=s.x=s.x&&s.x>=h&&i!==s.x&&sl(rn.x||s.x===n.x&&Kh(n,s)))&&(n=s,c=e)}s=s.next}while(s!==o);return n}(t,e);if(!s)return e;const i=ul(s,t);return qh(i,i.next),qh(s,s.next)}function Kh(t,e){return nl(t.prev,t,e.prev)<0&&nl(e.next,t,t.next)<0}function tl(t,e,s,i,r){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-s)*r|0)|t<<8))|t<<4))|t<<2))|t<<1))|(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=(e-i)*r|0)|e<<8))|e<<4))|e<<2))|e<<1))<<1}function el(t){let e=t,s=t;do{(e.x=(t-a)*(n-o)&&(t-a)*(i-o)>=(s-a)*(e-o)&&(s-a)*(n-o)>=(r-a)*(i-o)}function il(t,e,s,i,r,n,a,o){return!(t===a&&e===o)&&sl(t,e,s,i,r,n,a,o)}function rl(t,e){return t.next.i!==e.i&&t.prev.i!==e.i&&!function(t,e){let s=t;do{if(s.i!==t.i&&s.next.i!==t.i&&s.i!==e.i&&s.next.i!==e.i&&ol(s,s.next,t,e))return!0;s=s.next}while(s!==t);return!1}(t,e)&&(cl(t,e)&&cl(e,t)&&function(t,e){let s=t,i=!1;const r=(t.x+e.x)/2,n=(t.y+e.y)/2;do{s.y>n!=s.next.y>n&&s.next.y!==s.y&&r<(s.next.x-s.x)*(n-s.y)/(s.next.y-s.y)+s.x&&(i=!i),s=s.next}while(s!==t);return i}(t,e)&&(nl(t.prev,t,e.prev)||nl(t,e.prev,e))||al(t,e)&&nl(t.prev,t,t.next)>0&&nl(e.prev,e,e.next)>0)}function nl(t,e,s){return(e.y-t.y)*(s.x-e.x)-(e.x-t.x)*(s.y-e.y)}function al(t,e){return t.x===e.x&&t.y===e.y}function ol(t,e,s,i){const r=ll(nl(t,e,s)),n=ll(nl(t,e,i)),a=ll(nl(s,i,t)),o=ll(nl(s,i,e));return r!==n&&a!==o||(!(0!==r||!hl(t,s,e))||(!(0!==n||!hl(t,i,e))||(!(0!==a||!hl(s,t,i))||!(0!==o||!hl(s,e,i)))))}function hl(t,e,s){return e.x<=Math.max(t.x,s.x)&&e.x>=Math.min(t.x,s.x)&&e.y<=Math.max(t.y,s.y)&&e.y>=Math.min(t.y,s.y)}function ll(t){return t>0?1:t<0?-1:0}function cl(t,e){return nl(t.prev,t,t.next)<0?nl(t,e,t.next)>=0&&nl(t,t.prev,e)>=0:nl(t,e,t.prev)<0||nl(t,t.next,e)<0}function ul(t,e){const s=ml(t.i,t.x,t.y),i=ml(e.i,e.x,e.y),r=t.next,n=e.prev;return t.next=e,e.prev=t,s.next=r,r.prev=s,i.next=s,s.prev=i,n.next=i,i.prev=n,i}function dl(t,e,s,i){const r=ml(t,e,s);return i?(r.next=i.next,r.prev=i,i.next.prev=r,i.next=r):(r.prev=r,r.next=r),r}function pl(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ)}function ml(t,e,s){return{i:t,x:e,y:s,prev:null,next:null,z:0,prevZ:null,nextZ:null,steiner:!1}}class yl{static triangulate(t,e,s=2){return Uh(t,e,s)}}class gl{static area(t){const e=t.length;let s=0;for(let i=e-1,r=0;r2&&t[e-1].equals(t[0])&&t.pop()}function xl(t,e){for(let s=0;sNumber.EPSILON){const u=Math.sqrt(c),d=Math.sqrt(h*h+l*l),p=e.x-o/u,m=e.y+a/u,y=((s.x-l/d-p)*l-(s.y+h/d-m)*h)/(a*l-o*h);i=p+a*y-t.x,r=m+o*y-t.y;const g=i*i+r*r;if(g<=2)return new $s(i,r);n=Math.sqrt(g/2)}else{let t=!1;a>Number.EPSILON?h>Number.EPSILON&&(t=!0):a<-Number.EPSILON?h<-Number.EPSILON&&(t=!0):Math.sign(o)===Math.sign(l)&&(t=!0),t?(i=-o,r=a,n=Math.sqrt(c)):(i=a,r=o,n=Math.sqrt(c/2))}return new $s(i/n,r/n)}const k=[];for(let t=0,e=z.length,s=e-1,i=t+1;t=0;t--){const e=t/p,s=c*Math.cos(e*Math.PI/2),i=u*Math.sin(e*Math.PI/2)+d;for(let t=0,e=z.length;t=0;){const i=s;let r=s-1;r<0&&(r=t.length-1);for(let t=0,s=o+2*p;t0)&&d.push(e,r,h),(t!==s-1||o0!=t>0&&this.version++,this._anisotropy=t}get clearcoat(){return this._clearcoat}set clearcoat(t){this._clearcoat>0!=t>0&&this.version++,this._clearcoat=t}get iridescence(){return this._iridescence}set iridescence(t){this._iridescence>0!=t>0&&this.version++,this._iridescence=t}get dispersion(){return this._dispersion}set dispersion(t){this._dispersion>0!=t>0&&this.version++,this._dispersion=t}get sheen(){return this._sheen}set sheen(t){this._sheen>0!=t>0&&this.version++,this._sheen=t}get transmission(){return this._transmission}set transmission(t){this._transmission>0!=t>0&&this.version++,this._transmission=t}copy(t){return super.copy(t),this.defines={STANDARD:"",PHYSICAL:""},this.anisotropy=t.anisotropy,this.anisotropyRotation=t.anisotropyRotation,this.anisotropyMap=t.anisotropyMap,this.clearcoat=t.clearcoat,this.clearcoatMap=t.clearcoatMap,this.clearcoatRoughness=t.clearcoatRoughness,this.clearcoatRoughnessMap=t.clearcoatRoughnessMap,this.clearcoatNormalMap=t.clearcoatNormalMap,this.clearcoatNormalScale.copy(t.clearcoatNormalScale),this.dispersion=t.dispersion,this.ior=t.ior,this.iridescence=t.iridescence,this.iridescenceMap=t.iridescenceMap,this.iridescenceIOR=t.iridescenceIOR,this.iridescenceThicknessRange=[...t.iridescenceThicknessRange],this.iridescenceThicknessMap=t.iridescenceThicknessMap,this.sheen=t.sheen,this.sheenColor.copy(t.sheenColor),this.sheenColorMap=t.sheenColorMap,this.sheenRoughness=t.sheenRoughness,this.sheenRoughnessMap=t.sheenRoughnessMap,this.transmission=t.transmission,this.transmissionMap=t.transmissionMap,this.thickness=t.thickness,this.thicknessMap=t.thicknessMap,this.attenuationDistance=t.attenuationDistance,this.attenuationColor.copy(t.attenuationColor),this.specularIntensity=t.specularIntensity,this.specularIntensityMap=t.specularIntensityMap,this.specularColor.copy(t.specularColor),this.specularColorMap=t.specularColorMap,this}}class Ll extends en{constructor(t){super(),this.isMeshPhongMaterial=!0,this.type="MeshPhongMaterial",this.color=new Qr(16777215),this.specular=new Qr(1118481),this.shininess=30,this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Qr(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new $s(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new gr,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.specular.copy(t.specular),this.shininess=t.shininess,this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class jl extends en{constructor(t){super(),this.isMeshToonMaterial=!0,this.defines={TOON:""},this.type="MeshToonMaterial",this.color=new Qr(16777215),this.map=null,this.gradientMap=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Qr(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new $s(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.gradientMap=t.gradientMap,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}class Dl extends en{constructor(t){super(),this.isMeshNormalMaterial=!0,this.type="MeshNormalMaterial",this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new $s(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.flatShading=!1,this.setValues(t)}copy(t){return super.copy(t),this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.flatShading=t.flatShading,this}}class Wl extends en{constructor(t){super(),this.isMeshLambertMaterial=!0,this.type="MeshLambertMaterial",this.color=new Qr(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Qr(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new $s(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new gr,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class Ul extends en{constructor(t){super(),this.isMeshDepthMaterial=!0,this.type="MeshDepthMaterial",this.depthPacking=3200,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.setValues(t)}copy(t){return super.copy(t),this.depthPacking=t.depthPacking,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this}}class Hl extends en{constructor(t){super(),this.isMeshDistanceMaterial=!0,this.type="MeshDistanceMaterial",this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.setValues(t)}copy(t){return super.copy(t),this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this}}class ql extends en{constructor(t){super(),this.isMeshMatcapMaterial=!0,this.defines={MATCAP:""},this.type="MeshMatcapMaterial",this.color=new Qr(16777215),this.matcap=null,this.map=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new $s(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.defines={MATCAP:""},this.color.copy(t.color),this.matcap=t.matcap,this.map=t.map,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.flatShading=t.flatShading,this.fog=t.fog,this}}class Jl extends Ro{constructor(t){super(),this.isLineDashedMaterial=!0,this.type="LineDashedMaterial",this.scale=1,this.dashSize=3,this.gapSize=1,this.setValues(t)}copy(t){return super.copy(t),this.scale=t.scale,this.dashSize=t.dashSize,this.gapSize=t.gapSize,this}}function Xl(t,e){return t&&t.constructor!==e?"number"==typeof e.BYTES_PER_ELEMENT?new e(t):Array.prototype.slice.call(t):t}function Yl(t){return ArrayBuffer.isView(t)&&!(t instanceof DataView)}function Zl(t){const e=t.length,s=new Array(e);for(let t=0;t!==e;++t)s[t]=t;return s.sort(function(e,s){return t[e]-t[s]}),s}function Gl(t,e,s){const i=t.length,r=new t.constructor(i);for(let n=0,a=0;a!==i;++n){const i=s[n]*e;for(let s=0;s!==e;++s)r[a++]=t[i+s]}return r}function $l(t,e,s,i){let r=1,n=t[0];for(;void 0!==n&&void 0===n[i];)n=t[r++];if(void 0===n)return;let a=n[i];if(void 0!==a)if(Array.isArray(a))do{a=n[i],void 0!==a&&(e.push(n.time),s.push(...a)),n=t[r++]}while(void 0!==n);else if(void 0!==a.toArray)do{a=n[i],void 0!==a&&(e.push(n.time),a.toArray(s,s.length)),n=t[r++]}while(void 0!==n);else do{a=n[i],void 0!==a&&(e.push(n.time),s.push(a)),n=t[r++]}while(void 0!==n)}class Ql{static convertArray(t,e){return Xl(t,e)}static isTypedArray(t){return Yl(t)}static getKeyframeOrder(t){return Zl(t)}static sortedArray(t,e,s){return Gl(t,e,s)}static flattenJSON(t,e,s,i){$l(t,e,s,i)}static subclip(t,e,s,i,r=30){return function(t,e,s,i,r=30){const n=t.clone();n.name=e;const a=[];for(let t=0;t=i)){h.push(e.times[t]);for(let s=0;sn.tracks[t].times[0]&&(o=n.tracks[t].times[0]);for(let t=0;t=i.times[u]){const t=u*h+o,e=t+h-o;d=i.values.slice(t,e)}else{const t=i.createInterpolant(),e=o,s=h-o;t.evaluate(n),d=t.resultBuffer.slice(e,s)}"quaternion"===r&&(new Qs).fromArray(d).normalize().conjugate().toArray(d);const p=a.times.length;for(let t=0;t=r)){const a=e[1];t=r)break e}n=s,s=0;break s}break t}for(;s>>1;te;)--n;if(++n,0!==r||n!==i){r>=n&&(n=Math.max(n,1),r=n-1);const t=this.getValueSize();this.times=s.slice(r,n),this.values=this.values.slice(r*t,n*t)}return this}validate(){let t=!0;const e=this.getValueSize();e-Math.floor(e)!==0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),t=!1);const s=this.times,i=this.values,r=s.length;0===r&&(console.error("THREE.KeyframeTrack: Track is empty.",this),t=!1);let n=null;for(let e=0;e!==r;e++){const i=s[e];if("number"==typeof i&&isNaN(i)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,e,i),t=!1;break}if(null!==n&&n>i){console.error("THREE.KeyframeTrack: Out of order keys.",this,e,i,n),t=!1;break}n=i}if(void 0!==i&&Yl(i))for(let e=0,s=i.length;e!==s;++e){const s=i[e];if(isNaN(s)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,e,s),t=!1;break}}return t}optimize(){const t=this.times.slice(),e=this.values.slice(),s=this.getValueSize(),i=this.getInterpolation()===Re,r=t.length-1;let n=1;for(let a=1;a0){t[n]=t[r];for(let t=r*s,i=n*s,a=0;a!==s;++a)e[i+a]=e[t+a];++n}return n!==t.length?(this.times=t.slice(0,n),this.values=e.slice(0,n*s)):(this.times=t,this.values=e),this}clone(){const t=this.times.slice(),e=this.values.slice(),s=new(0,this.constructor)(this.name,t,e);return s.createInterpolant=this.createInterpolant,s}}ic.prototype.ValueTypeName="",ic.prototype.TimeBufferType=Float32Array,ic.prototype.ValueBufferType=Float32Array,ic.prototype.DefaultInterpolation=Ee;class rc extends ic{constructor(t,e,s){super(t,e,s)}}rc.prototype.ValueTypeName="bool",rc.prototype.ValueBufferType=Array,rc.prototype.DefaultInterpolation=ke,rc.prototype.InterpolantFactoryMethodLinear=void 0,rc.prototype.InterpolantFactoryMethodSmooth=void 0;class nc extends ic{constructor(t,e,s,i){super(t,e,s,i)}}nc.prototype.ValueTypeName="color";class ac extends ic{constructor(t,e,s,i){super(t,e,s,i)}}ac.prototype.ValueTypeName="number";class oc extends Kl{constructor(t,e,s,i){super(t,e,s,i)}interpolate_(t,e,s,i){const r=this.resultBuffer,n=this.sampleValues,a=this.valueSize,o=(s-e)/(i-e);let h=t*a;for(let t=h+a;h!==t;h+=4)Qs.slerpFlat(r,0,n,h-a,n,h,o);return r}}class hc extends ic{constructor(t,e,s,i){super(t,e,s,i)}InterpolantFactoryMethodLinear(t){return new oc(this.times,this.values,this.getValueSize(),t)}}hc.prototype.ValueTypeName="quaternion",hc.prototype.InterpolantFactoryMethodSmooth=void 0;class lc extends ic{constructor(t,e,s){super(t,e,s)}}lc.prototype.ValueTypeName="string",lc.prototype.ValueBufferType=Array,lc.prototype.DefaultInterpolation=ke,lc.prototype.InterpolantFactoryMethodLinear=void 0,lc.prototype.InterpolantFactoryMethodSmooth=void 0;class cc extends ic{constructor(t,e,s,i){super(t,e,s,i)}}cc.prototype.ValueTypeName="vector";class uc{constructor(t="",e=-1,s=[],i=2500){this.name=t,this.tracks=s,this.duration=e,this.blendMode=i,this.uuid=Hs(),this.userData={},this.duration<0&&this.resetDuration()}static parse(t){const e=[],s=t.tracks,i=1/(t.fps||1);for(let t=0,r=s.length;t!==r;++t)e.push(dc(s[t]).scale(i));const r=new this(t.name,t.duration,e,t.blendMode);return r.uuid=t.uuid,r.userData=JSON.parse(t.userData||"{}"),r}static toJSON(t){const e=[],s=t.tracks,i={name:t.name,duration:t.duration,tracks:e,uuid:t.uuid,blendMode:t.blendMode,userData:JSON.stringify(t.userData)};for(let t=0,i=s.length;t!==i;++t)e.push(ic.toJSON(s[t]));return i}static CreateFromMorphTargetSequence(t,e,s,i){const r=e.length,n=[];for(let t=0;t1){const t=n[1];let e=i[t];e||(i[t]=e=[]),e.push(s)}}const n=[];for(const t in i)n.push(this.CreateFromMorphTargetSequence(t,i[t],e,s));return n}static parseAnimation(t,e){if(console.warn("THREE.AnimationClip: parseAnimation() is deprecated and will be removed with r185"),!t)return console.error("THREE.AnimationClip: No animation in JSONLoader data."),null;const s=function(t,e,s,i,r){if(0!==s.length){const n=[],a=[];$l(s,n,a,i),0!==n.length&&r.push(new t(e,n,a))}},i=[],r=t.name||"default",n=t.fps||30,a=t.blendMode;let o=t.length||-1;const h=t.hierarchy||[];for(let t=0;t{e&&e(r),this.manager.itemEnd(t)},0),r;if(void 0!==fc[t])return void fc[t].push({onLoad:e,onProgress:s,onError:i});fc[t]=[],fc[t].push({onLoad:e,onProgress:s,onError:i});const n=new Request(t,{headers:new Headers(this.requestHeader),credentials:this.withCredentials?"include":"same-origin",signal:"function"==typeof AbortSignal.any?AbortSignal.any([this._abortController.signal,this.manager.abortController.signal]):this._abortController.signal}),a=this.mimeType,o=this.responseType;fetch(n).then(e=>{if(200===e.status||0===e.status){if(0===e.status&&console.warn("THREE.FileLoader: HTTP Status 0 received."),"undefined"==typeof ReadableStream||void 0===e.body||void 0===e.body.getReader)return e;const s=fc[t],i=e.body.getReader(),r=e.headers.get("X-File-Size")||e.headers.get("Content-Length"),n=r?parseInt(r):0,a=0!==n;let o=0;const h=new ReadableStream({start(t){!function e(){i.read().then(({done:i,value:r})=>{if(i)t.close();else{o+=r.byteLength;const i=new ProgressEvent("progress",{lengthComputable:a,loaded:o,total:n});for(let t=0,e=s.length;t{t.error(e)})}()}});return new Response(h)}throw new xc(`fetch for "${e.url}" responded with ${e.status}: ${e.statusText}`,e)}).then(t=>{switch(o){case"arraybuffer":return t.arrayBuffer();case"blob":return t.blob();case"document":return t.text().then(t=>(new DOMParser).parseFromString(t,a));case"json":return t.json();default:if(""===a)return t.text();{const e=/charset="?([^;"\s]*)"?/i.exec(a),s=e&&e[1]?e[1].toLowerCase():void 0,i=new TextDecoder(s);return t.arrayBuffer().then(t=>i.decode(t))}}}).then(e=>{pc.add(`file:${t}`,e);const s=fc[t];delete fc[t];for(let t=0,i=s.length;t{const s=fc[t];if(void 0===s)throw this.manager.itemError(t),e;delete fc[t];for(let t=0,i=s.length;t{this.manager.itemEnd(t)}),this.manager.itemStart(t)}setResponseType(t){return this.responseType=t,this}setMimeType(t){return this.mimeType=t,this}abort(){return this._abortController.abort(),this._abortController=new AbortController,this}}class vc extends gc{constructor(t){super(t)}load(t,e,s,i){const r=this,n=new bc(this.manager);n.setPath(this.path),n.setRequestHeader(this.requestHeader),n.setWithCredentials(this.withCredentials),n.load(t,function(s){try{e(r.parse(JSON.parse(s)))}catch(e){i?i(e):console.error(e),r.manager.itemError(t)}},s,i)}parse(t){const e=[];for(let s=0;s0:i.vertexColors=t.vertexColors),void 0!==t.uniforms)for(const e in t.uniforms){const r=t.uniforms[e];switch(i.uniforms[e]={},r.type){case"t":i.uniforms[e].value=s(r.value);break;case"c":i.uniforms[e].value=(new Qr).setHex(r.value);break;case"v2":i.uniforms[e].value=(new $s).fromArray(r.value);break;case"v3":i.uniforms[e].value=(new Ks).fromArray(r.value);break;case"v4":i.uniforms[e].value=(new Ti).fromArray(r.value);break;case"m3":i.uniforms[e].value=(new si).fromArray(r.value);break;case"m4":i.uniforms[e].value=(new ar).fromArray(r.value);break;default:i.uniforms[e].value=r.value}}if(void 0!==t.defines&&(i.defines=t.defines),void 0!==t.vertexShader&&(i.vertexShader=t.vertexShader),void 0!==t.fragmentShader&&(i.fragmentShader=t.fragmentShader),void 0!==t.glslVersion&&(i.glslVersion=t.glslVersion),void 0!==t.extensions)for(const e in t.extensions)i.extensions[e]=t.extensions[e];if(void 0!==t.lights&&(i.lights=t.lights),void 0!==t.clipping&&(i.clipping=t.clipping),void 0!==t.size&&(i.size=t.size),void 0!==t.sizeAttenuation&&(i.sizeAttenuation=t.sizeAttenuation),void 0!==t.map&&(i.map=s(t.map)),void 0!==t.matcap&&(i.matcap=s(t.matcap)),void 0!==t.alphaMap&&(i.alphaMap=s(t.alphaMap)),void 0!==t.bumpMap&&(i.bumpMap=s(t.bumpMap)),void 0!==t.bumpScale&&(i.bumpScale=t.bumpScale),void 0!==t.normalMap&&(i.normalMap=s(t.normalMap)),void 0!==t.normalMapType&&(i.normalMapType=t.normalMapType),void 0!==t.normalScale){let e=t.normalScale;!1===Array.isArray(e)&&(e=[e,e]),i.normalScale=(new $s).fromArray(e)}return void 0!==t.displacementMap&&(i.displacementMap=s(t.displacementMap)),void 0!==t.displacementScale&&(i.displacementScale=t.displacementScale),void 0!==t.displacementBias&&(i.displacementBias=t.displacementBias),void 0!==t.roughnessMap&&(i.roughnessMap=s(t.roughnessMap)),void 0!==t.metalnessMap&&(i.metalnessMap=s(t.metalnessMap)),void 0!==t.emissiveMap&&(i.emissiveMap=s(t.emissiveMap)),void 0!==t.emissiveIntensity&&(i.emissiveIntensity=t.emissiveIntensity),void 0!==t.specularMap&&(i.specularMap=s(t.specularMap)),void 0!==t.specularIntensityMap&&(i.specularIntensityMap=s(t.specularIntensityMap)),void 0!==t.specularColorMap&&(i.specularColorMap=s(t.specularColorMap)),void 0!==t.envMap&&(i.envMap=s(t.envMap)),void 0!==t.envMapRotation&&i.envMapRotation.fromArray(t.envMapRotation),void 0!==t.envMapIntensity&&(i.envMapIntensity=t.envMapIntensity),void 0!==t.reflectivity&&(i.reflectivity=t.reflectivity),void 0!==t.refractionRatio&&(i.refractionRatio=t.refractionRatio),void 0!==t.lightMap&&(i.lightMap=s(t.lightMap)),void 0!==t.lightMapIntensity&&(i.lightMapIntensity=t.lightMapIntensity),void 0!==t.aoMap&&(i.aoMap=s(t.aoMap)),void 0!==t.aoMapIntensity&&(i.aoMapIntensity=t.aoMapIntensity),void 0!==t.gradientMap&&(i.gradientMap=s(t.gradientMap)),void 0!==t.clearcoatMap&&(i.clearcoatMap=s(t.clearcoatMap)),void 0!==t.clearcoatRoughnessMap&&(i.clearcoatRoughnessMap=s(t.clearcoatRoughnessMap)),void 0!==t.clearcoatNormalMap&&(i.clearcoatNormalMap=s(t.clearcoatNormalMap)),void 0!==t.clearcoatNormalScale&&(i.clearcoatNormalScale=(new $s).fromArray(t.clearcoatNormalScale)),void 0!==t.iridescenceMap&&(i.iridescenceMap=s(t.iridescenceMap)),void 0!==t.iridescenceThicknessMap&&(i.iridescenceThicknessMap=s(t.iridescenceThicknessMap)),void 0!==t.transmissionMap&&(i.transmissionMap=s(t.transmissionMap)),void 0!==t.thicknessMap&&(i.thicknessMap=s(t.thicknessMap)),void 0!==t.anisotropyMap&&(i.anisotropyMap=s(t.anisotropyMap)),void 0!==t.sheenColorMap&&(i.sheenColorMap=s(t.sheenColorMap)),void 0!==t.sheenRoughnessMap&&(i.sheenRoughnessMap=s(t.sheenRoughnessMap)),i}setTextures(t){return this.textures=t,this}createMaterialFromType(t){return Xc.createMaterialFromType(t)}static createMaterialFromType(t){return new{ShadowMaterial:Ol,SpriteMaterial:pa,RawShaderMaterial:Nl,ShaderMaterial:Yn,PointsMaterial:Xo,MeshPhysicalMaterial:Fl,MeshStandardMaterial:Vl,MeshPhongMaterial:Ll,MeshToonMaterial:jl,MeshNormalMaterial:Dl,MeshLambertMaterial:Wl,MeshDepthMaterial:Ul,MeshDistanceMaterial:Hl,MeshBasicMaterial:sn,MeshMatcapMaterial:ql,LineDashedMaterial:Jl,LineBasicMaterial:Ro,Material:en}[t]}}class Yc{static extractUrlBase(t){const e=t.lastIndexOf("/");return-1===e?"./":t.slice(0,e+1)}static resolveURL(t,e){return"string"!=typeof t||""===t?"":(/^https?:\/\//i.test(e)&&/^\//.test(t)&&(e=e.replace(/(^https?:\/\/[^\/]+).*/i,"$1")),/^(https?:)?\/\//i.test(t)||/^data:.*,.*$/i.test(t)||/^blob:.*$/i.test(t)?t:e+t)}}class Zc extends In{constructor(){super(),this.isInstancedBufferGeometry=!0,this.type="InstancedBufferGeometry",this.instanceCount=1/0}copy(t){return super.copy(t),this.instanceCount=t.instanceCount,this}toJSON(){const t=super.toJSON();return t.instanceCount=this.instanceCount,t.isInstancedBufferGeometry=!0,t}}class Gc extends gc{constructor(t){super(t)}load(t,e,s,i){const r=this,n=new bc(r.manager);n.setPath(r.path),n.setRequestHeader(r.requestHeader),n.setWithCredentials(r.withCredentials),n.load(t,function(s){try{e(r.parse(JSON.parse(s)))}catch(e){i?i(e):console.error(e),r.manager.itemError(t)}},s,i)}parse(t){const e={},s={};function i(t,i){if(void 0!==e[i])return e[i];const r=t.interleavedBuffers[i],n=function(t,e){if(void 0!==s[e])return s[e];const i=t.arrayBuffers,r=i[e],n=new Uint32Array(r).buffer;return s[e]=n,n}(t,r.buffer),a=ai(r.type,n),o=new ca(a,r.stride);return o.uuid=r.uuid,e[i]=o,o}const r=t.isInstancedBufferGeometry?new Zc:new In,n=t.data.index;if(void 0!==n){const t=ai(n.type,n.array);r.setIndex(new dn(t,1))}const a=t.data.attributes;for(const e in a){const s=a[e];let n;if(s.isInterleavedBufferAttribute){const e=i(t.data,s.data);n=new da(e,s.itemSize,s.offset,s.normalized)}else{const t=ai(s.type,s.array);n=new(s.isInstancedBufferAttribute?Xa:dn)(t,s.itemSize,s.normalized)}void 0!==s.name&&(n.name=s.name),void 0!==s.usage&&n.setUsage(s.usage),r.setAttribute(e,n)}const o=t.data.morphAttributes;if(o)for(const e in o){const s=o[e],n=[];for(let e=0,r=s.length;e0){const s=new mc(e);r=new Sc(s),r.setCrossOrigin(this.crossOrigin);for(let e=0,s=t.length;e0){i=new Sc(this.manager),i.setCrossOrigin(this.crossOrigin);for(let e=0,i=t.length;e{let e=null,s=null;return void 0!==t.boundingBox&&(e=(new Ri).fromJSON(t.boundingBox)),void 0!==t.boundingSphere&&(s=(new $i).fromJSON(t.boundingSphere)),{...t,boundingBox:e,boundingSphere:s}}),n._instanceInfo=t.instanceInfo,n._availableInstanceIds=t._availableInstanceIds,n._availableGeometryIds=t._availableGeometryIds,n._nextIndexStart=t.nextIndexStart,n._nextVertexStart=t.nextVertexStart,n._geometryCount=t.geometryCount,n._maxInstanceCount=t.maxInstanceCount,n._maxVertexCount=t.maxVertexCount,n._maxIndexCount=t.maxIndexCount,n._geometryInitialized=t.geometryInitialized,n._matricesTexture=c(t.matricesTexture.uuid),n._indirectTexture=c(t.indirectTexture.uuid),void 0!==t.colorsTexture&&(n._colorsTexture=c(t.colorsTexture.uuid)),void 0!==t.boundingSphere&&(n.boundingSphere=(new $i).fromJSON(t.boundingSphere)),void 0!==t.boundingBox&&(n.boundingBox=(new Ri).fromJSON(t.boundingBox));break;case"LOD":n=new ka;break;case"Line":n=new Do(h(t.geometry),l(t.material));break;case"LineLoop":n=new Jo(h(t.geometry),l(t.material));break;case"LineSegments":n=new qo(h(t.geometry),l(t.material));break;case"PointCloud":case"Points":n=new Qo(h(t.geometry),l(t.material));break;case"Sprite":n=new za(l(t.material));break;case"Group":n=new ra;break;case"Bone":n=new Wa;break;default:n=new Rr}if(n.uuid=t.uuid,void 0!==t.name&&(n.name=t.name),void 0!==t.matrix?(n.matrix.fromArray(t.matrix),void 0!==t.matrixAutoUpdate&&(n.matrixAutoUpdate=t.matrixAutoUpdate),n.matrixAutoUpdate&&n.matrix.decompose(n.position,n.quaternion,n.scale)):(void 0!==t.position&&n.position.fromArray(t.position),void 0!==t.rotation&&n.rotation.fromArray(t.rotation),void 0!==t.quaternion&&n.quaternion.fromArray(t.quaternion),void 0!==t.scale&&n.scale.fromArray(t.scale)),void 0!==t.up&&n.up.fromArray(t.up),void 0!==t.castShadow&&(n.castShadow=t.castShadow),void 0!==t.receiveShadow&&(n.receiveShadow=t.receiveShadow),t.shadow&&(void 0!==t.shadow.intensity&&(n.shadow.intensity=t.shadow.intensity),void 0!==t.shadow.bias&&(n.shadow.bias=t.shadow.bias),void 0!==t.shadow.normalBias&&(n.shadow.normalBias=t.shadow.normalBias),void 0!==t.shadow.radius&&(n.shadow.radius=t.shadow.radius),void 0!==t.shadow.mapSize&&n.shadow.mapSize.fromArray(t.shadow.mapSize),void 0!==t.shadow.camera&&(n.shadow.camera=this.parseObject(t.shadow.camera))),void 0!==t.visible&&(n.visible=t.visible),void 0!==t.frustumCulled&&(n.frustumCulled=t.frustumCulled),void 0!==t.renderOrder&&(n.renderOrder=t.renderOrder),void 0!==t.userData&&(n.userData=t.userData),void 0!==t.layers&&(n.layers.mask=t.layers),void 0!==t.children){const a=t.children;for(let t=0;t{if(!0!==eu.has(n))return e&&e(s),r.manager.itemEnd(t),s;i&&i(eu.get(n)),r.manager.itemError(t),r.manager.itemEnd(t)}):(setTimeout(function(){e&&e(n),r.manager.itemEnd(t)},0),n);const a={};a.credentials="anonymous"===this.crossOrigin?"same-origin":"include",a.headers=this.requestHeader,a.signal="function"==typeof AbortSignal.any?AbortSignal.any([this._abortController.signal,this.manager.abortController.signal]):this._abortController.signal;const o=fetch(t,a).then(function(t){return t.blob()}).then(function(t){return createImageBitmap(t,Object.assign(r.options,{colorSpaceConversion:"none"}))}).then(function(s){return pc.add(`image-bitmap:${t}`,s),e&&e(s),r.manager.itemEnd(t),s}).catch(function(e){i&&i(e),eu.set(o,e),pc.remove(`image-bitmap:${t}`),r.manager.itemError(t),r.manager.itemEnd(t)});pc.add(`image-bitmap:${t}`,o),r.manager.itemStart(t)}abort(){return this._abortController.abort(),this._abortController=new AbortController,this}}let iu;class ru{static getContext(){return void 0===iu&&(iu=new(window.AudioContext||window.webkitAudioContext)),iu}static setContext(t){iu=t}}class nu extends gc{constructor(t){super(t)}load(t,e,s,i){const r=this,n=new bc(this.manager);function a(e){i?i(e):console.error(e),r.manager.itemError(t)}n.setResponseType("arraybuffer"),n.setPath(this.path),n.setRequestHeader(this.requestHeader),n.setWithCredentials(this.withCredentials),n.load(t,function(t){try{const s=t.slice(0);ru.getContext().decodeAudioData(s,function(t){e(t)}).catch(a)}catch(t){a(t)}},s,i)}}const au=new ar,ou=new ar,hu=new ar;class lu{constructor(){this.type="StereoCamera",this.aspect=1,this.eyeSep=.064,this.cameraL=new Kn,this.cameraL.layers.enable(1),this.cameraL.matrixAutoUpdate=!1,this.cameraR=new Kn,this.cameraR.layers.enable(2),this.cameraR.matrixAutoUpdate=!1,this._cache={focus:null,fov:null,aspect:null,near:null,far:null,zoom:null,eyeSep:null}}update(t){const e=this._cache;if(e.focus!==t.focus||e.fov!==t.fov||e.aspect!==t.aspect*this.aspect||e.near!==t.near||e.far!==t.far||e.zoom!==t.zoom||e.eyeSep!==this.eyeSep){e.focus=t.focus,e.fov=t.fov,e.aspect=t.aspect*this.aspect,e.near=t.near,e.far=t.far,e.zoom=t.zoom,e.eyeSep=this.eyeSep,hu.copy(t.projectionMatrix);const s=e.eyeSep/2,i=s*e.near/e.focus,r=e.near*Math.tan(Ws*e.fov*.5)/e.zoom;let n,a;ou.elements[12]=-s,au.elements[12]=s,n=-r*e.aspect+i,a=r*e.aspect+i,hu.elements[0]=2*e.near/(a-n),hu.elements[8]=(a+n)/(a-n),this.cameraL.projectionMatrix.copy(hu),n=-r*e.aspect-i,a=r*e.aspect-i,hu.elements[0]=2*e.near/(a-n),hu.elements[8]=(a+n)/(a-n),this.cameraR.projectionMatrix.copy(hu)}this.cameraL.matrixWorld.copy(t.matrixWorld).multiply(ou),this.cameraR.matrixWorld.copy(t.matrixWorld).multiply(au)}}class cu extends Kn{constructor(t=[]){super(),this.isArrayCamera=!0,this.isMultiViewCamera=!1,this.cameras=t}}class uu{constructor(t=!0){this.autoStart=t,this.startTime=0,this.oldTime=0,this.elapsedTime=0,this.running=!1}start(){this.startTime=performance.now(),this.oldTime=this.startTime,this.elapsedTime=0,this.running=!0}stop(){this.getElapsedTime(),this.running=!1,this.autoStart=!1}getElapsedTime(){return this.getDelta(),this.elapsedTime}getDelta(){let t=0;if(this.autoStart&&!this.running)return this.start(),0;if(this.running){const e=performance.now();t=(e-this.oldTime)/1e3,this.oldTime=e,this.elapsedTime+=t}return t}}const du=new Ks,pu=new Qs,mu=new Ks,yu=new Ks,gu=new Ks;class fu extends Rr{constructor(){super(),this.type="AudioListener",this.context=ru.getContext(),this.gain=this.context.createGain(),this.gain.connect(this.context.destination),this.filter=null,this.timeDelta=0,this._clock=new uu}getInput(){return this.gain}removeFilter(){return null!==this.filter&&(this.gain.disconnect(this.filter),this.filter.disconnect(this.context.destination),this.gain.connect(this.context.destination),this.filter=null),this}getFilter(){return this.filter}setFilter(t){return null!==this.filter?(this.gain.disconnect(this.filter),this.filter.disconnect(this.context.destination)):this.gain.disconnect(this.context.destination),this.filter=t,this.gain.connect(this.filter),this.filter.connect(this.context.destination),this}getMasterVolume(){return this.gain.gain.value}setMasterVolume(t){return this.gain.gain.setTargetAtTime(t,this.context.currentTime,.01),this}updateMatrixWorld(t){super.updateMatrixWorld(t);const e=this.context.listener;if(this.timeDelta=this._clock.getDelta(),this.matrixWorld.decompose(du,pu,mu),yu.set(0,0,-1).applyQuaternion(pu),gu.set(0,1,0).applyQuaternion(pu),e.positionX){const t=this.context.currentTime+this.timeDelta;e.positionX.linearRampToValueAtTime(du.x,t),e.positionY.linearRampToValueAtTime(du.y,t),e.positionZ.linearRampToValueAtTime(du.z,t),e.forwardX.linearRampToValueAtTime(yu.x,t),e.forwardY.linearRampToValueAtTime(yu.y,t),e.forwardZ.linearRampToValueAtTime(yu.z,t),e.upX.linearRampToValueAtTime(gu.x,t),e.upY.linearRampToValueAtTime(gu.y,t),e.upZ.linearRampToValueAtTime(gu.z,t)}else e.setPosition(du.x,du.y,du.z),e.setOrientation(yu.x,yu.y,yu.z,gu.x,gu.y,gu.z)}}class xu extends Rr{constructor(t){super(),this.type="Audio",this.listener=t,this.context=t.context,this.gain=this.context.createGain(),this.gain.connect(t.getInput()),this.autoplay=!1,this.buffer=null,this.detune=0,this.loop=!1,this.loopStart=0,this.loopEnd=0,this.offset=0,this.duration=void 0,this.playbackRate=1,this.isPlaying=!1,this.hasPlaybackControl=!0,this.source=null,this.sourceType="empty",this._startedAt=0,this._progress=0,this._connected=!1,this.filters=[]}getOutput(){return this.gain}setNodeSource(t){return this.hasPlaybackControl=!1,this.sourceType="audioNode",this.source=t,this.connect(),this}setMediaElementSource(t){return this.hasPlaybackControl=!1,this.sourceType="mediaNode",this.source=this.context.createMediaElementSource(t),this.connect(),this}setMediaStreamSource(t){return this.hasPlaybackControl=!1,this.sourceType="mediaStreamNode",this.source=this.context.createMediaStreamSource(t),this.connect(),this}setBuffer(t){return this.buffer=t,this.sourceType="buffer",this.autoplay&&this.play(),this}play(t=0){if(!0===this.isPlaying)return void console.warn("THREE.Audio: Audio is already playing.");if(!1===this.hasPlaybackControl)return void console.warn("THREE.Audio: this Audio has no playback control.");this._startedAt=this.context.currentTime+t;const e=this.context.createBufferSource();return e.buffer=this.buffer,e.loop=this.loop,e.loopStart=this.loopStart,e.loopEnd=this.loopEnd,e.onended=this.onEnded.bind(this),e.start(this._startedAt,this._progress+this.offset,this.duration),this.isPlaying=!0,this.source=e,this.setDetune(this.detune),this.setPlaybackRate(this.playbackRate),this.connect()}pause(){if(!1!==this.hasPlaybackControl)return!0===this.isPlaying&&(this._progress+=Math.max(this.context.currentTime-this._startedAt,0)*this.playbackRate,!0===this.loop&&(this._progress=this._progress%(this.duration||this.buffer.duration)),this.source.stop(),this.source.onended=null,this.isPlaying=!1),this;console.warn("THREE.Audio: this Audio has no playback control.")}stop(t=0){if(!1!==this.hasPlaybackControl)return this._progress=0,null!==this.source&&(this.source.stop(this.context.currentTime+t),this.source.onended=null),this.isPlaying=!1,this;console.warn("THREE.Audio: this Audio has no playback control.")}connect(){if(this.filters.length>0){this.source.connect(this.filters[0]);for(let t=1,e=this.filters.length;t0){this.source.disconnect(this.filters[0]);for(let t=1,e=this.filters.length;t0&&this._mixBufferRegionAdditive(s,i,this._addIndex*e,1,e);for(let t=e,r=e+e;t!==r;++t)if(s[t]!==s[t+e]){a.setValue(s,i);break}}saveOriginalState(){const t=this.binding,e=this.buffer,s=this.valueSize,i=s*this._origIndex;t.getValue(e,i);for(let t=s,r=i;t!==r;++t)e[t]=e[i+t%s];this._setIdentity(),this.cumulativeWeight=0,this.cumulativeWeightAdditive=0}restoreOriginalState(){const t=3*this.valueSize;this.binding.setValue(this.buffer,t)}_setAdditiveIdentityNumeric(){const t=this._addIndex*this.valueSize,e=t+this.valueSize;for(let s=t;s=.5)for(let i=0;i!==r;++i)t[e+i]=t[s+i]}_slerp(t,e,s,i){Qs.slerpFlat(t,e,t,e,t,s,i)}_slerpAdditive(t,e,s,i,r){const n=this._workIndex*r;Qs.multiplyQuaternionsFlat(t,n,t,e,t,s),Qs.slerpFlat(t,e,t,e,t,n,i)}_lerp(t,e,s,i,r){const n=1-i;for(let a=0;a!==r;++a){const r=e+a;t[r]=t[r]*n+t[s+a]*i}}_lerpAdditive(t,e,s,i,r){for(let n=0;n!==r;++n){const r=e+n;t[r]=t[r]+t[s+n]*i}}}const Tu="\\[\\]\\.:\\/",zu=new RegExp("["+Tu+"]","g"),Cu="[^"+Tu+"]",Iu="[^"+Tu.replace("\\.","")+"]",Bu=new RegExp("^"+/((?:WC+[\/:])*)/.source.replace("WC",Cu)+/(WCOD+)?/.source.replace("WCOD",Iu)+/(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace("WC",Cu)+/\.(WC+)(?:\[(.+)\])?/.source.replace("WC",Cu)+"$"),ku=["material","materials","bones","map"];class Eu{constructor(t,e,s){this.path=e,this.parsedPath=s||Eu.parseTrackName(e),this.node=Eu.findNode(t,this.parsedPath.nodeName),this.rootNode=t,this.getValue=this._getValue_unbound,this.setValue=this._setValue_unbound}static create(t,e,s){return t&&t.isAnimationObjectGroup?new Eu.Composite(t,e,s):new Eu(t,e,s)}static sanitizeNodeName(t){return t.replace(/\s/g,"_").replace(zu,"")}static parseTrackName(t){const e=Bu.exec(t);if(null===e)throw new Error("PropertyBinding: Cannot parse trackName: "+t);const s={nodeName:e[2],objectName:e[3],objectIndex:e[4],propertyName:e[5],propertyIndex:e[6]},i=s.nodeName&&s.nodeName.lastIndexOf(".");if(void 0!==i&&-1!==i){const t=s.nodeName.substring(i+1);-1!==ku.indexOf(t)&&(s.nodeName=s.nodeName.substring(0,i),s.objectName=t)}if(null===s.propertyName||0===s.propertyName.length)throw new Error("PropertyBinding: can not parse propertyName from trackName: "+t);return s}static findNode(t,e){if(void 0===e||""===e||"."===e||-1===e||e===t.name||e===t.uuid)return t;if(t.skeleton){const s=t.skeleton.getBoneByName(e);if(void 0!==s)return s}if(t.children){const s=function(t){for(let i=0;i=r){const n=r++,l=t[n];e[l.uuid]=h,t[h]=l,e[o]=n,t[n]=a;for(let t=0,e=i;t!==e;++t){const e=s[t],i=e[n],r=e[h];e[h]=i,e[n]=r}}}this.nCachedObjects_=r}uncache(){const t=this._objects,e=this._indicesByUUID,s=this._bindings,i=s.length;let r=this.nCachedObjects_,n=t.length;for(let a=0,o=arguments.length;a!==o;++a){const o=arguments[a].uuid,h=e[o];if(void 0!==h)if(delete e[o],h0&&(e[a.uuid]=h),t[h]=a,t.pop();for(let t=0,e=i;t!==e;++t){const e=s[t];e[h]=e[r],e.pop()}}}this.nCachedObjects_=r}subscribe_(t,e){const s=this._bindingsIndicesByPath;let i=s[t];const r=this._bindings;if(void 0!==i)return r[i];const n=this._paths,a=this._parsedPaths,o=this._objects,h=o.length,l=this.nCachedObjects_,c=new Array(h);i=r.length,s[t]=i,n.push(t),a.push(e),r.push(c);for(let s=l,i=o.length;s!==i;++s){const i=o[s];c[s]=new Eu(i,t,e)}return c}unsubscribe_(t){const e=this._bindingsIndicesByPath,s=e[t];if(void 0!==s){const i=this._paths,r=this._parsedPaths,n=this._bindings,a=n.length-1,o=n[a];e[t[a]]=s,n[s]=o,n.pop(),r[s]=r[a],r.pop(),i[s]=i[a],i.pop()}}}class Pu{constructor(t,e,s=null,i=e.blendMode){this._mixer=t,this._clip=e,this._localRoot=s,this.blendMode=i;const r=e.tracks,n=r.length,a=new Array(n),o={endingStart:Pe,endingEnd:Pe};for(let t=0;t!==n;++t){const e=r[t].createInterpolant(null);a[t]=e,e.settings=o}this._interpolantSettings=o,this._interpolants=a,this._propertyBindings=new Array(n),this._cacheIndex=null,this._byClipCacheIndex=null,this._timeScaleInterpolant=null,this._weightInterpolant=null,this.loop=2201,this._loopCount=-1,this._startTime=null,this.time=0,this.timeScale=1,this._effectiveTimeScale=1,this.weight=1,this._effectiveWeight=1,this.repetitions=1/0,this.paused=!1,this.enabled=!0,this.clampWhenFinished=!1,this.zeroSlopeAtStart=!0,this.zeroSlopeAtEnd=!0}play(){return this._mixer._activateAction(this),this}stop(){return this._mixer._deactivateAction(this),this.reset()}reset(){return this.paused=!1,this.enabled=!0,this.time=0,this._loopCount=-1,this._startTime=null,this.stopFading().stopWarping()}isRunning(){return this.enabled&&!this.paused&&0!==this.timeScale&&null===this._startTime&&this._mixer._isActiveAction(this)}isScheduled(){return this._mixer._isActiveAction(this)}startAt(t){return this._startTime=t,this}setLoop(t,e){return this.loop=t,this.repetitions=e,this}setEffectiveWeight(t){return this.weight=t,this._effectiveWeight=this.enabled?t:0,this.stopFading()}getEffectiveWeight(){return this._effectiveWeight}fadeIn(t){return this._scheduleFading(t,0,1)}fadeOut(t){return this._scheduleFading(t,1,0)}crossFadeFrom(t,e,s=!1){if(t.fadeOut(e),this.fadeIn(e),!0===s){const s=this._clip.duration,i=t._clip.duration,r=i/s,n=s/i;t.warp(1,r,e),this.warp(n,1,e)}return this}crossFadeTo(t,e,s=!1){return t.crossFadeFrom(this,e,s)}stopFading(){const t=this._weightInterpolant;return null!==t&&(this._weightInterpolant=null,this._mixer._takeBackControlInterpolant(t)),this}setEffectiveTimeScale(t){return this.timeScale=t,this._effectiveTimeScale=this.paused?0:t,this.stopWarping()}getEffectiveTimeScale(){return this._effectiveTimeScale}setDuration(t){return this.timeScale=this._clip.duration/t,this.stopWarping()}syncWith(t){return this.time=t.time,this.timeScale=t.timeScale,this.stopWarping()}halt(t){return this.warp(this._effectiveTimeScale,0,t)}warp(t,e,s){const i=this._mixer,r=i.time,n=this.timeScale;let a=this._timeScaleInterpolant;null===a&&(a=i._lendControlInterpolant(),this._timeScaleInterpolant=a);const o=a.parameterPositions,h=a.sampleValues;return o[0]=r,o[1]=r+s,h[0]=t/n,h[1]=e/n,this}stopWarping(){const t=this._timeScaleInterpolant;return null!==t&&(this._timeScaleInterpolant=null,this._mixer._takeBackControlInterpolant(t)),this}getMixer(){return this._mixer}getClip(){return this._clip}getRoot(){return this._localRoot||this._mixer._root}_update(t,e,s,i){if(!this.enabled)return void this._updateWeight(t);const r=this._startTime;if(null!==r){const i=(t-r)*s;i<0||0===s?e=0:(this._startTime=null,e=s*i)}e*=this._updateTimeScale(t);const n=this._updateTime(e),a=this._updateWeight(t);if(a>0){const t=this._interpolants,e=this._propertyBindings;if(this.blendMode===Fe)for(let s=0,i=t.length;s!==i;++s)t[s].evaluate(n),e[s].accumulateAdditive(a);else for(let s=0,r=t.length;s!==r;++s)t[s].evaluate(n),e[s].accumulate(i,a)}}_updateWeight(t){let e=0;if(this.enabled){e=this.weight;const s=this._weightInterpolant;if(null!==s){const i=s.evaluate(t)[0];e*=i,t>s.parameterPositions[1]&&(this.stopFading(),0===i&&(this.enabled=!1))}}return this._effectiveWeight=e,e}_updateTimeScale(t){let e=0;if(!this.paused){e=this.timeScale;const s=this._timeScaleInterpolant;if(null!==s){e*=s.evaluate(t)[0],t>s.parameterPositions[1]&&(this.stopWarping(),0===e?this.paused=!0:this.timeScale=e)}}return this._effectiveTimeScale=e,e}_updateTime(t){const e=this._clip.duration,s=this.loop;let i=this.time+t,r=this._loopCount;const n=2202===s;if(0===t)return-1===r||!n||1&~r?i:e-i;if(2200===s){-1===r&&(this._loopCount=0,this._setEndings(!0,!0,!1));t:{if(i>=e)i=e;else{if(!(i<0)){this.time=i;break t}i=0}this.clampWhenFinished?this.paused=!0:this.enabled=!1,this.time=i,this._mixer.dispatchEvent({type:"finished",action:this,direction:t<0?-1:1})}}else{if(-1===r&&(t>=0?(r=0,this._setEndings(!0,0===this.repetitions,n)):this._setEndings(0===this.repetitions,!0,n)),i>=e||i<0){const s=Math.floor(i/e);i-=e*s,r+=Math.abs(s);const a=this.repetitions-r;if(a<=0)this.clampWhenFinished?this.paused=!0:this.enabled=!1,i=t>0?e:0,this.time=i,this._mixer.dispatchEvent({type:"finished",action:this,direction:t>0?1:-1});else{if(1===a){const e=t<0;this._setEndings(e,!e,n)}else this._setEndings(!1,!1,n);this._loopCount=r,this.time=i,this._mixer.dispatchEvent({type:"loop",action:this,loopDelta:s})}}else this.time=i;if(n&&!(1&~r))return e-i}return i}_setEndings(t,e,s){const i=this._interpolantSettings;s?(i.endingStart=Oe,i.endingEnd=Oe):(i.endingStart=t?this.zeroSlopeAtStart?Oe:Pe:Ne,i.endingEnd=e?this.zeroSlopeAtEnd?Oe:Pe:Ne)}_scheduleFading(t,e,s){const i=this._mixer,r=i.time;let n=this._weightInterpolant;null===n&&(n=i._lendControlInterpolant(),this._weightInterpolant=n);const a=n.parameterPositions,o=n.sampleValues;return a[0]=r,o[0]=e,a[1]=r+t,o[1]=s,this}}const Ou=new Float32Array(1);class Nu extends Ls{constructor(t){super(),this._root=t,this._initMemoryManager(),this._accuIndex=0,this.time=0,this.timeScale=1}_bindAction(t,e){const s=t._localRoot||this._root,i=t._clip.tracks,r=i.length,n=t._propertyBindings,a=t._interpolants,o=s.uuid,h=this._bindingsByRootAndName;let l=h[o];void 0===l&&(l={},h[o]=l);for(let t=0;t!==r;++t){const r=i[t],h=r.name;let c=l[h];if(void 0!==c)++c.referenceCount,n[t]=c;else{if(c=n[t],void 0!==c){null===c._cacheIndex&&(++c.referenceCount,this._addInactiveBinding(c,o,h));continue}const i=e&&e._propertyBindings[t].binding.parsedPath;c=new Au(Eu.create(s,h,i),r.ValueTypeName,r.getValueSize()),++c.referenceCount,this._addInactiveBinding(c,o,h),n[t]=c}a[t].resultBuffer=c.buffer}}_activateAction(t){if(!this._isActiveAction(t)){if(null===t._cacheIndex){const e=(t._localRoot||this._root).uuid,s=t._clip.uuid,i=this._actionsByClip[s];this._bindAction(t,i&&i.knownActions[0]),this._addInactiveAction(t,s,e)}const e=t._propertyBindings;for(let t=0,s=e.length;t!==s;++t){const s=e[t];0===s.useCount++&&(this._lendBinding(s),s.saveOriginalState())}this._lendAction(t)}}_deactivateAction(t){if(this._isActiveAction(t)){const e=t._propertyBindings;for(let t=0,s=e.length;t!==s;++t){const s=e[t];0===--s.useCount&&(s.restoreOriginalState(),this._takeBackBinding(s))}this._takeBackAction(t)}}_initMemoryManager(){this._actions=[],this._nActiveActions=0,this._actionsByClip={},this._bindings=[],this._nActiveBindings=0,this._bindingsByRootAndName={},this._controlInterpolants=[],this._nActiveControlInterpolants=0;const t=this;this.stats={actions:{get total(){return t._actions.length},get inUse(){return t._nActiveActions}},bindings:{get total(){return t._bindings.length},get inUse(){return t._nActiveBindings}},controlInterpolants:{get total(){return t._controlInterpolants.length},get inUse(){return t._nActiveControlInterpolants}}}}_isActiveAction(t){const e=t._cacheIndex;return null!==e&&e=0;--e)t[e].stop();return this}update(t){t*=this.timeScale;const e=this._actions,s=this._nActiveActions,i=this.time+=t,r=Math.sign(t),n=this._accuIndex^=1;for(let a=0;a!==s;++a){e[a]._update(i,t,r,n)}const a=this._bindings,o=this._nActiveBindings;for(let t=0;t!==o;++t)a[t].apply(n);return this}setTime(t){this.time=0;for(let t=0;t=this.min.x&&t.x<=this.max.x&&t.y>=this.min.y&&t.y<=this.max.y}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y))}intersectsBox(t){return t.max.x>=this.min.x&&t.min.x<=this.max.x&&t.max.y>=this.min.y&&t.min.y<=this.max.y}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,Qu).distanceTo(t)}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const td=new Ks,ed=new Ks,sd=new Ks,id=new Ks,rd=new Ks,nd=new Ks,ad=new Ks;class od{constructor(t=new Ks,e=new Ks){this.start=t,this.end=e}set(t,e){return this.start.copy(t),this.end.copy(e),this}copy(t){return this.start.copy(t.start),this.end.copy(t.end),this}getCenter(t){return t.addVectors(this.start,this.end).multiplyScalar(.5)}delta(t){return t.subVectors(this.end,this.start)}distanceSq(){return this.start.distanceToSquared(this.end)}distance(){return this.start.distanceTo(this.end)}at(t,e){return this.delta(e).multiplyScalar(t).add(this.start)}closestPointToPointParameter(t,e){td.subVectors(t,this.start),ed.subVectors(this.end,this.start);const s=ed.dot(ed);let i=ed.dot(td)/s;return e&&(i=qs(i,0,1)),i}closestPointToPoint(t,e,s){const i=this.closestPointToPointParameter(t,e);return this.delta(s).multiplyScalar(i).add(this.start)}distanceSqToLine3(t,e=nd,s=ad){const i=1e-8*1e-8;let r,n;const a=this.start,o=t.start,h=this.end,l=t.end;sd.subVectors(h,a),id.subVectors(l,o),rd.subVectors(a,o);const c=sd.dot(sd),u=id.dot(id),d=id.dot(rd);if(c<=i&&u<=i)return e.copy(a),s.copy(o),e.sub(s),e.dot(e);if(c<=i)r=0,n=d/u,n=qs(n,0,1);else{const t=sd.dot(rd);if(u<=i)n=0,r=qs(-t/c,0,1);else{const e=sd.dot(id),s=c*u-e*e;r=0!==s?qs((e*d-t*u)/s,0,1):0,n=(e*r+d)/u,n<0?(n=0,r=qs(-t/c,0,1)):n>1&&(n=1,r=qs((e-t)/c,0,1))}}return e.copy(a).add(sd.multiplyScalar(r)),s.copy(o).add(id.multiplyScalar(n)),e.sub(s),e.dot(e)}applyMatrix4(t){return this.start.applyMatrix4(t),this.end.applyMatrix4(t),this}equals(t){return t.start.equals(this.start)&&t.end.equals(this.end)}clone(){return(new this.constructor).copy(this)}}const hd=new Ks;class ld extends Rr{constructor(t,e){super(),this.light=t,this.matrixAutoUpdate=!1,this.color=e,this.type="SpotLightHelper";const s=new In,i=[0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,-1,0,1,0,0,0,0,1,1,0,0,0,0,-1,1];for(let t=0,e=1,s=32;t1)for(let s=0;s.99999)this.quaternion.set(0,0,0,1);else if(t.y<-.99999)this.quaternion.set(1,0,0,0);else{Pd.set(t.z,0,-t.x).normalize();const e=Math.acos(t.y);this.quaternion.setFromAxisAngle(Pd,e)}}setLength(t,e=.2*t,s=.2*e){this.line.scale.set(1,Math.max(1e-4,t-e),1),this.line.updateMatrix(),this.cone.scale.set(s,e,s),this.cone.position.y=t,this.cone.updateMatrix()}setColor(t){this.line.material.color.set(t),this.cone.material.color.set(t)}copy(t){return super.copy(t,!1),this.line.copy(t.line),this.cone.copy(t.cone),this}dispose(){this.line.geometry.dispose(),this.line.material.dispose(),this.cone.geometry.dispose(),this.cone.material.dispose()}}class Fd extends qo{constructor(t=1){const e=[0,0,0,t,0,0,0,0,0,0,t,0,0,0,0,0,0,t],s=new In;s.setAttribute("position",new wn(e,3)),s.setAttribute("color",new wn([1,0,0,1,.6,0,0,1,0,.6,1,0,0,0,1,0,.6,1],3));super(s,new Ro({vertexColors:!0,toneMapped:!1})),this.type="AxesHelper"}setColors(t,e,s){const i=new Qr,r=this.geometry.attributes.color.array;return i.set(t),i.toArray(r,0),i.toArray(r,3),i.set(e),i.toArray(r,6),i.toArray(r,9),i.set(s),i.toArray(r,12),i.toArray(r,15),this.geometry.attributes.color.needsUpdate=!0,this}dispose(){this.geometry.dispose(),this.material.dispose()}}class Ld{constructor(){this.type="ShapePath",this.color=new Qr,this.subPaths=[],this.currentPath=null}moveTo(t,e){return this.currentPath=new Dh,this.subPaths.push(this.currentPath),this.currentPath.moveTo(t,e),this}lineTo(t,e){return this.currentPath.lineTo(t,e),this}quadraticCurveTo(t,e,s,i){return this.currentPath.quadraticCurveTo(t,e,s,i),this}bezierCurveTo(t,e,s,i,r,n){return this.currentPath.bezierCurveTo(t,e,s,i,r,n),this}splineThru(t){return this.currentPath.splineThru(t),this}toShapes(t){function e(t,e){const s=e.length;let i=!1;for(let r=s-1,n=0;nNumber.EPSILON){if(h<0&&(s=e[n],o=-o,a=e[r],h=-h),t.ya.y)continue;if(t.y===s.y){if(t.x===s.x)return!0}else{const e=h*(t.x-s.x)-o*(t.y-s.y);if(0===e)return!0;if(e<0)continue;i=!i}}else{if(t.y!==s.y)continue;if(a.x<=t.x&&t.x<=s.x||s.x<=t.x&&t.x<=a.x)return!0}}return i}const s=gl.isClockWise,i=this.subPaths;if(0===i.length)return[];let r,n,a;const o=[];if(1===i.length)return n=i[0],a=new Wh,a.curves=n.curves,o.push(a),o;let h=!s(i[0].getPoints());h=t?!h:h;const l=[],c=[];let u,d,p=[],m=0;c[m]=void 0,p[m]=[];for(let e=0,a=i.length;e1){let t=!1,s=0;for(let t=0,e=c.length;t0&&!1===t&&(p=l)}for(let t=0,e=c.length;te?(t.repeat.x=1,t.repeat.y=s/e,t.offset.x=0,t.offset.y=(1-t.repeat.y)/2):(t.repeat.x=e/s,t.repeat.y=1,t.offset.x=(1-t.repeat.x)/2,t.offset.y=0),t}(t,e)}static cover(t,e){return function(t,e){const s=t.image&&t.image.width?t.image.width/t.image.height:1;return s>e?(t.repeat.x=e/s,t.repeat.y=1,t.offset.x=(1-t.repeat.x)/2,t.offset.y=0):(t.repeat.x=1,t.repeat.y=s/e,t.offset.x=0,t.offset.y=(1-t.repeat.y)/2),t}(t,e)}static fill(t){return function(t){return t.repeat.x=1,t.repeat.y=1,t.offset.x=0,t.offset.y=0,t}(t)}static getByteLength(t,e,s,i){return Dd(t,e,s,i)}}"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("register",{detail:{revision:t}})),"undefined"!=typeof window&&(window.__THREE__?console.warn("WARNING: Multiple instances of Three.js being imported."):window.__THREE__=t);export{et as ACESFilmicToneMapping,v as AddEquation,G as AddOperation,Fe as AdditiveAnimationBlendMode,g as AdditiveBlending,it as AgXToneMapping,Lt as AlphaFormat,Ms as AlwaysCompare,D as AlwaysDepth,ms as AlwaysStencilFunc,Uc as AmbientLight,Pu as AnimationAction,uc as AnimationClip,vc as AnimationLoader,Nu as AnimationMixer,Ru as AnimationObjectGroup,Ql as AnimationUtils,Mh as ArcCurve,cu as ArrayCamera,Vd as ArrowHelper,nt as AttachedBindMode,xu as Audio,_u as AudioAnalyser,ru as AudioContext,fu as AudioListener,nu as AudioLoader,Fd as AxesHelper,d as BackSide,We as BasicDepthPacking,o as BasicShadowMap,Eo as BatchedMesh,Wa as Bone,rc as BooleanKeyframeTrack,Ku as Box2,Ri as Box3,Ed as Box3Helper,Un as BoxGeometry,kd as BoxHelper,dn as BufferAttribute,In as BufferGeometry,Gc as BufferGeometryLoader,zt as ByteType,pc as Cache,Zn as Camera,Cd as CameraHelper,ah as CanvasTexture,lh as CapsuleGeometry,Ch as CatmullRomCurve3,tt as CineonToneMapping,ch as CircleGeometry,mt as ClampToEdgeWrapping,uu as Clock,Qr as Color,nc as ColorKeyframeTrack,yi as ColorManagement,rh as CompressedArrayTexture,nh as CompressedCubeTexture,ih as CompressedTexture,wc as CompressedTextureLoader,dh as ConeGeometry,F as ConstantAlphaFactor,N as ConstantColorFactor,jd as Controls,ea as CubeCamera,ht as CubeReflectionMapping,lt as CubeRefractionMapping,sa as CubeTexture,_c as CubeTextureLoader,dt as CubeUVReflectionMapping,Eh as CubicBezierCurve,Rh as CubicBezierCurve3,tc as CubicInterpolant,r as CullFaceBack,n as CullFaceFront,a as CullFaceFrontBack,i as CullFaceNone,vh as Curve,jh as CurvePath,b as CustomBlending,st as CustomToneMapping,uh as CylinderGeometry,Gu as Cylindrical,ki as Data3DTexture,Ii as DataArrayTexture,Ua as DataTexture,Ac as DataTextureLoader,hn as DataUtils,is as DecrementStencilOp,ns as DecrementWrapStencilOp,yc as DefaultLoadingManager,Wt as DepthFormat,Ut as DepthStencilFormat,oh as DepthTexture,at as DetachedBindMode,Wc as DirectionalLight,Ad as DirectionalLightHelper,sc as DiscreteInterpolant,mh as DodecahedronGeometry,p as DoubleSide,k as DstAlphaFactor,R as DstColorFactor,Bs as DynamicCopyUsage,_s as DynamicDrawUsage,zs as DynamicReadUsage,bh as EdgesGeometry,wh as EllipseCurve,fs as EqualCompare,H as EqualDepth,ls as EqualStencilFunc,ct as EquirectangularReflectionMapping,ut as EquirectangularRefractionMapping,gr as Euler,Ls as EventDispatcher,hh as ExternalTexture,bl as ExtrudeGeometry,bc as FileLoader,vn as Float16BufferAttribute,wn as Float32BufferAttribute,Et as FloatType,ha as Fog,oa as FogExp2,sh as FramebufferTexture,u as FrontSide,lo as Frustum,po as FrustumArray,Wu as GLBufferAttribute,Es as GLSL1,Rs as GLSL3,bs as GreaterCompare,J as GreaterDepth,ws as GreaterEqualCompare,q as GreaterEqualDepth,ps as GreaterEqualStencilFunc,us as GreaterStencilFunc,vd as GridHelper,ra as Group,Rt as HalfFloatType,Cc as HemisphereLight,bd as HemisphereLightHelper,wl as IcosahedronGeometry,su as ImageBitmapLoader,Sc as ImageLoader,bi as ImageUtils,ss as IncrementStencilOp,rs as IncrementWrapStencilOp,Xa as InstancedBufferAttribute,Zc as InstancedBufferGeometry,Du as InstancedInterleavedBuffer,eo as InstancedMesh,gn as Int16BufferAttribute,xn as Int32BufferAttribute,pn as Int8BufferAttribute,Bt as IntType,ca as InterleavedBuffer,da as InterleavedBufferAttribute,Kl as Interpolant,ke as InterpolateDiscrete,Ee as InterpolateLinear,Re as InterpolateSmooth,Fs as InterpolationSamplingMode,Vs as InterpolationSamplingType,as as InvertStencilOp,ts as KeepStencilOp,ic as KeyframeTrack,ka as LOD,Ml as LatheGeometry,fr as Layers,gs as LessCompare,W as LessDepth,xs as LessEqualCompare,U as LessEqualDepth,cs as LessEqualStencilFunc,hs as LessStencilFunc,zc as Light,Jc as LightProbe,Do as Line,od as Line3,Ro as LineBasicMaterial,Ph as LineCurve,Oh as LineCurve3,Jl as LineDashedMaterial,Jo as LineLoop,qo as LineSegments,wt as LinearFilter,ec as LinearInterpolant,At as LinearMipMapLinearFilter,St as LinearMipMapNearestFilter,_t as LinearMipmapLinearFilter,Mt as LinearMipmapNearestFilter,Ge as LinearSRGBColorSpace,Q as LinearToneMapping,$e as LinearTransfer,gc as Loader,Yc as LoaderUtils,mc as LoadingManager,Ce as LoopOnce,Be as LoopPingPong,Ie as LoopRepeat,e as MOUSE,en as Material,Xc as MaterialLoader,Gs as MathUtils,$u as Matrix2,si as Matrix3,ar as Matrix4,_ as MaxEquation,Dn as Mesh,sn as MeshBasicMaterial,Ul as MeshDepthMaterial,Hl as MeshDistanceMaterial,Wl as MeshLambertMaterial,ql as MeshMatcapMaterial,Dl as MeshNormalMaterial,Ll as MeshPhongMaterial,Fl as MeshPhysicalMaterial,Vl as MeshStandardMaterial,jl as MeshToonMaterial,S as MinEquation,yt as MirroredRepeatWrapping,Z as MixOperation,x as MultiplyBlending,Y as MultiplyOperation,gt as NearestFilter,vt as NearestMipMapLinearFilter,xt as NearestMipMapNearestFilter,bt as NearestMipmapLinearFilter,ft as NearestMipmapNearestFilter,rt as NeutralToneMapping,ys as NeverCompare,j as NeverDepth,os as NeverStencilFunc,m as NoBlending,Ye as NoColorSpace,$ as NoToneMapping,Ve as NormalAnimationBlendMode,y as NormalBlending,vs as NotEqualCompare,X as NotEqualDepth,ds as NotEqualStencilFunc,ac as NumberKeyframeTrack,Rr as Object3D,$c as ObjectLoader,Xe as ObjectSpaceNormalMap,Sl as OctahedronGeometry,T as OneFactor,L as OneMinusConstantAlphaFactor,V as OneMinusConstantColorFactor,E as OneMinusDstAlphaFactor,P as OneMinusDstColorFactor,B as OneMinusSrcAlphaFactor,C as OneMinusSrcColorFactor,jc as OrthographicCamera,h as PCFShadowMap,l as PCFSoftShadowMap,Dh as Path,Kn as PerspectiveCamera,no as Plane,_l as PlaneGeometry,Rd as PlaneHelper,Lc as PointLight,yd as PointLightHelper,Qo as Points,Xo as PointsMaterial,wd as PolarGridHelper,ph as PolyhedronGeometry,Su as PositionalAudio,Eu as PropertyBinding,Au as PropertyMixer,Nh as QuadraticBezierCurve,Vh as QuadraticBezierCurve3,Qs as Quaternion,hc as QuaternionKeyframeTrack,oc as QuaternionLinearInterpolant,Us as RAD2DEG,Te as RED_GREEN_RGTC2_Format,_e as RED_RGTC1_Format,t as REVISION,Ue as RGBADepthPacking,Dt as RGBAFormat,Zt as RGBAIntegerFormat,xe as RGBA_ASTC_10x10_Format,ye as RGBA_ASTC_10x5_Format,ge as RGBA_ASTC_10x6_Format,fe as RGBA_ASTC_10x8_Format,be as RGBA_ASTC_12x10_Format,ve as RGBA_ASTC_12x12_Format,oe as RGBA_ASTC_4x4_Format,he as RGBA_ASTC_5x4_Format,le as RGBA_ASTC_5x5_Format,ce as RGBA_ASTC_6x5_Format,ue as RGBA_ASTC_6x6_Format,de as RGBA_ASTC_8x5_Format,pe as RGBA_ASTC_8x6_Format,me as RGBA_ASTC_8x8_Format,we as RGBA_BPTC_Format,ae as RGBA_ETC2_EAC_Format,ie as RGBA_PVRTC_2BPPV1_Format,se as RGBA_PVRTC_4BPPV1_Format,$t as RGBA_S3TC_DXT1_Format,Qt as RGBA_S3TC_DXT3_Format,Kt as RGBA_S3TC_DXT5_Format,He as RGBDepthPacking,jt as RGBFormat,Yt as RGBIntegerFormat,Me as RGB_BPTC_SIGNED_Format,Se as RGB_BPTC_UNSIGNED_Format,re as RGB_ETC1_Format,ne as RGB_ETC2_Format,ee as RGB_PVRTC_2BPPV1_Format,te as RGB_PVRTC_4BPPV1_Format,Gt as RGB_S3TC_DXT1_Format,qe as RGDepthPacking,Jt as RGFormat,Xt as RGIntegerFormat,Nl as RawShaderMaterial,nr as Ray,Hu as Raycaster,Hc as RectAreaLight,Ht as RedFormat,qt as RedIntegerFormat,K as ReinhardToneMapping,zi as RenderTarget,Vu as RenderTarget3D,pt as RepeatWrapping,es as ReplaceStencilOp,M as ReverseSubtractEquation,Al as RingGeometry,ze as SIGNED_RED_GREEN_RGTC2_Format,Ae as SIGNED_RED_RGTC1_Format,Ze as SRGBColorSpace,Qe as SRGBTransfer,la as Scene,Yn as ShaderMaterial,Ol as ShadowMaterial,Wh as Shape,Tl as ShapeGeometry,Ld as ShapePath,gl as ShapeUtils,Ct as ShortType,Ja as Skeleton,pd as SkeletonHelper,Da as SkinnedMesh,wi as Source,$i as Sphere,zl as SphereGeometry,Zu as Spherical,qc as SphericalHarmonics3,Fh as SplineCurve,Pc as SpotLight,ld as SpotLightHelper,za as Sprite,pa as SpriteMaterial,I as SrcAlphaFactor,O as SrcAlphaSaturateFactor,z as SrcColorFactor,Is as StaticCopyUsage,Ss as StaticDrawUsage,Ts as StaticReadUsage,lu as StereoCamera,ks as StreamCopyUsage,As as StreamDrawUsage,Cs as StreamReadUsage,lc as StringKeyframeTrack,w as SubtractEquation,f as SubtractiveBlending,s as TOUCH,Je as TangentSpaceNormalMap,Cl as TetrahedronGeometry,Ai as Texture,Tc as TextureLoader,Wd as TextureUtils,Xu as Timer,Ns as TimestampQuery,Il as TorusGeometry,Bl as TorusKnotGeometry,Xr as Triangle,De as TriangleFanDrawMode,je as TriangleStripDrawMode,Le as TrianglesDrawMode,kl as TubeGeometry,ot as UVMapping,fn as Uint16BufferAttribute,bn as Uint32BufferAttribute,mn as Uint8BufferAttribute,yn as Uint8ClampedBufferAttribute,Fu as Uniform,ju as UniformsGroup,Xn as UniformsUtils,Tt as UnsignedByteType,Ft as UnsignedInt101111Type,Nt as UnsignedInt248Type,Vt as UnsignedInt5999Type,kt as UnsignedIntType,Pt as UnsignedShort4444Type,Ot as UnsignedShort5551Type,It as UnsignedShortType,c as VSMShadowMap,$s as Vector2,Ks as Vector3,Ti as Vector4,cc as VectorKeyframeTrack,eh as VideoFrameTexture,th as VideoTexture,Ei as WebGL3DRenderTarget,Bi as WebGLArrayRenderTarget,Ps as WebGLCoordinateSystem,ia as WebGLCubeRenderTarget,Ci as WebGLRenderTarget,Os as WebGPUCoordinateSystem,aa as WebXRController,El as WireframeGeometry,Ne as WrapAroundEnding,Pe as ZeroCurvatureEnding,A as ZeroFactor,Oe as ZeroSlopeEnding,Ke as ZeroStencilOp,ri as arrayNeedsUint32,Hn as cloneUniforms,hi as createCanvasElement,oi as createElementNS,Dd as getByteLength,Jn as getUnlitUniformColorSpace,qn as mergeUniforms,ui as probeAsync,ci as warnOnce}; diff --git a/demo/web/vendor/three.module.min.js b/demo/web/vendor/three.module.min.js new file mode 100644 index 0000000000000000000000000000000000000000..20d3c112761a519c7780a5ccc9f72a40937fa83b --- /dev/null +++ b/demo/web/vendor/three.module.min.js @@ -0,0 +1,6 @@ +/** + * @license + * Copyright 2010-2025 Three.js Authors + * SPDX-License-Identifier: MIT + */ +import{Matrix3 as e,Vector2 as t,Color as n,mergeUniforms as r,Vector3 as i,CubeUVReflectionMapping as a,Mesh as o,BoxGeometry as s,ShaderMaterial as l,BackSide as c,cloneUniforms as d,Euler as u,Matrix4 as f,ColorManagement as p,SRGBTransfer as m,PlaneGeometry as h,FrontSide as _,getUnlitUniformColorSpace as g,IntType as v,HalfFloatType as E,UnsignedByteType as S,FloatType as T,RGBAFormat as M,Plane as x,EquirectangularReflectionMapping as R,EquirectangularRefractionMapping as A,WebGLCubeRenderTarget as b,CubeReflectionMapping as C,CubeRefractionMapping as L,OrthographicCamera as P,PerspectiveCamera as U,NoToneMapping as D,MeshBasicMaterial as w,NoBlending as I,WebGLRenderTarget as y,BufferGeometry as N,BufferAttribute as O,LinearSRGBColorSpace as F,LinearFilter as B,warnOnce as H,Uint32BufferAttribute as G,Uint16BufferAttribute as V,arrayNeedsUint32 as z,Vector4 as k,DataArrayTexture as W,CubeTexture as X,Data3DTexture as Y,LessEqualCompare as K,DepthTexture as q,Texture as j,GLSL3 as Z,PCFShadowMap as $,PCFSoftShadowMap as Q,VSMShadowMap as J,CustomToneMapping as ee,NeutralToneMapping as te,AgXToneMapping as ne,ACESFilmicToneMapping as re,CineonToneMapping as ie,ReinhardToneMapping as ae,LinearToneMapping as oe,LinearTransfer as se,AddOperation as le,MixOperation as ce,MultiplyOperation as de,UniformsUtils as ue,DoubleSide as fe,NormalBlending as pe,TangentSpaceNormalMap as me,ObjectSpaceNormalMap as he,Layers as _e,Frustum as ge,MeshDepthMaterial as ve,RGBADepthPacking as Ee,MeshDistanceMaterial as Se,NearestFilter as Te,LessEqualDepth as Me,ReverseSubtractEquation as xe,SubtractEquation as Re,AddEquation as Ae,OneMinusConstantAlphaFactor as be,ConstantAlphaFactor as Ce,OneMinusConstantColorFactor as Le,ConstantColorFactor as Pe,OneMinusDstAlphaFactor as Ue,OneMinusDstColorFactor as De,OneMinusSrcAlphaFactor as we,OneMinusSrcColorFactor as Ie,DstAlphaFactor as ye,DstColorFactor as Ne,SrcAlphaSaturateFactor as Oe,SrcAlphaFactor as Fe,SrcColorFactor as Be,OneFactor as He,ZeroFactor as Ge,NotEqualDepth as Ve,GreaterDepth as ze,GreaterEqualDepth as ke,EqualDepth as We,LessDepth as Xe,AlwaysDepth as Ye,NeverDepth as Ke,CullFaceNone as qe,CullFaceBack as je,CullFaceFront as Ze,CustomBlending as $e,MultiplyBlending as Qe,SubtractiveBlending as Je,AdditiveBlending as et,MinEquation as tt,MaxEquation as nt,MirroredRepeatWrapping as rt,ClampToEdgeWrapping as it,RepeatWrapping as at,LinearMipmapLinearFilter as ot,LinearMipmapNearestFilter as st,NearestMipmapLinearFilter as lt,NearestMipmapNearestFilter as ct,NotEqualCompare as dt,GreaterCompare as ut,GreaterEqualCompare as ft,EqualCompare as pt,LessCompare as mt,AlwaysCompare as ht,NeverCompare as _t,NoColorSpace as gt,DepthStencilFormat as vt,getByteLength as Et,DepthFormat as St,UnsignedIntType as Tt,UnsignedInt248Type as Mt,UnsignedShortType as xt,createElementNS as Rt,UnsignedShort4444Type as At,UnsignedShort5551Type as bt,UnsignedInt5999Type as Ct,UnsignedInt101111Type as Lt,ByteType as Pt,ShortType as Ut,AlphaFormat as Dt,RGBFormat as wt,RedFormat as It,RedIntegerFormat as yt,RGFormat as Nt,RGIntegerFormat as Ot,RGBAIntegerFormat as Ft,RGB_S3TC_DXT1_Format as Bt,RGBA_S3TC_DXT1_Format as Ht,RGBA_S3TC_DXT3_Format as Gt,RGBA_S3TC_DXT5_Format as Vt,RGB_PVRTC_4BPPV1_Format as zt,RGB_PVRTC_2BPPV1_Format as kt,RGBA_PVRTC_4BPPV1_Format as Wt,RGBA_PVRTC_2BPPV1_Format as Xt,RGB_ETC1_Format as Yt,RGB_ETC2_Format as Kt,RGBA_ETC2_EAC_Format as qt,RGBA_ASTC_4x4_Format as jt,RGBA_ASTC_5x4_Format as Zt,RGBA_ASTC_5x5_Format as $t,RGBA_ASTC_6x5_Format as Qt,RGBA_ASTC_6x6_Format as Jt,RGBA_ASTC_8x5_Format as en,RGBA_ASTC_8x6_Format as tn,RGBA_ASTC_8x8_Format as nn,RGBA_ASTC_10x5_Format as rn,RGBA_ASTC_10x6_Format as an,RGBA_ASTC_10x8_Format as on,RGBA_ASTC_10x10_Format as sn,RGBA_ASTC_12x10_Format as ln,RGBA_ASTC_12x12_Format as cn,RGBA_BPTC_Format as dn,RGB_BPTC_SIGNED_Format as un,RGB_BPTC_UNSIGNED_Format as fn,RED_RGTC1_Format as pn,SIGNED_RED_RGTC1_Format as mn,RED_GREEN_RGTC2_Format as hn,SIGNED_RED_GREEN_RGTC2_Format as _n,ExternalTexture as gn,EventDispatcher as vn,ArrayCamera as En,WebXRController as Sn,RAD2DEG as Tn,createCanvasElement as Mn,SRGBColorSpace as xn,REVISION as Rn,WebGLCoordinateSystem as An,probeAsync as bn}from"./three.core.min.js";export{AdditiveAnimationBlendMode,AlwaysStencilFunc,AmbientLight,AnimationAction,AnimationClip,AnimationLoader,AnimationMixer,AnimationObjectGroup,AnimationUtils,ArcCurve,ArrowHelper,AttachedBindMode,Audio,AudioAnalyser,AudioContext,AudioListener,AudioLoader,AxesHelper,BasicDepthPacking,BasicShadowMap,BatchedMesh,Bone,BooleanKeyframeTrack,Box2,Box3,Box3Helper,BoxHelper,BufferGeometryLoader,Cache,Camera,CameraHelper,CanvasTexture,CapsuleGeometry,CatmullRomCurve3,CircleGeometry,Clock,ColorKeyframeTrack,CompressedArrayTexture,CompressedCubeTexture,CompressedTexture,CompressedTextureLoader,ConeGeometry,Controls,CubeCamera,CubeTextureLoader,CubicBezierCurve,CubicBezierCurve3,CubicInterpolant,CullFaceFrontBack,Curve,CurvePath,CylinderGeometry,Cylindrical,DataTexture,DataTextureLoader,DataUtils,DecrementStencilOp,DecrementWrapStencilOp,DefaultLoadingManager,DetachedBindMode,DirectionalLight,DirectionalLightHelper,DiscreteInterpolant,DodecahedronGeometry,DynamicCopyUsage,DynamicDrawUsage,DynamicReadUsage,EdgesGeometry,EllipseCurve,EqualStencilFunc,ExtrudeGeometry,FileLoader,Float16BufferAttribute,Float32BufferAttribute,Fog,FogExp2,FramebufferTexture,FrustumArray,GLBufferAttribute,GLSL1,GreaterEqualStencilFunc,GreaterStencilFunc,GridHelper,Group,HemisphereLight,HemisphereLightHelper,IcosahedronGeometry,ImageBitmapLoader,ImageLoader,ImageUtils,IncrementStencilOp,IncrementWrapStencilOp,InstancedBufferAttribute,InstancedBufferGeometry,InstancedInterleavedBuffer,InstancedMesh,Int16BufferAttribute,Int32BufferAttribute,Int8BufferAttribute,InterleavedBuffer,InterleavedBufferAttribute,Interpolant,InterpolateDiscrete,InterpolateLinear,InterpolateSmooth,InterpolationSamplingMode,InterpolationSamplingType,InvertStencilOp,KeepStencilOp,KeyframeTrack,LOD,LatheGeometry,LessEqualStencilFunc,LessStencilFunc,Light,LightProbe,Line,Line3,LineBasicMaterial,LineCurve,LineCurve3,LineDashedMaterial,LineLoop,LineSegments,LinearInterpolant,LinearMipMapLinearFilter,LinearMipMapNearestFilter,Loader,LoaderUtils,LoadingManager,LoopOnce,LoopPingPong,LoopRepeat,MOUSE,Material,MaterialLoader,MathUtils,Matrix2,MeshLambertMaterial,MeshMatcapMaterial,MeshNormalMaterial,MeshPhongMaterial,MeshPhysicalMaterial,MeshStandardMaterial,MeshToonMaterial,NearestMipMapLinearFilter,NearestMipMapNearestFilter,NeverStencilFunc,NormalAnimationBlendMode,NotEqualStencilFunc,NumberKeyframeTrack,Object3D,ObjectLoader,OctahedronGeometry,Path,PlaneHelper,PointLight,PointLightHelper,Points,PointsMaterial,PolarGridHelper,PolyhedronGeometry,PositionalAudio,PropertyBinding,PropertyMixer,QuadraticBezierCurve,QuadraticBezierCurve3,Quaternion,QuaternionKeyframeTrack,QuaternionLinearInterpolant,RGBDepthPacking,RGBIntegerFormat,RGDepthPacking,RawShaderMaterial,Ray,Raycaster,RectAreaLight,RenderTarget,RenderTarget3D,ReplaceStencilOp,RingGeometry,Scene,ShadowMaterial,Shape,ShapeGeometry,ShapePath,ShapeUtils,Skeleton,SkeletonHelper,SkinnedMesh,Source,Sphere,SphereGeometry,Spherical,SphericalHarmonics3,SplineCurve,SpotLight,SpotLightHelper,Sprite,SpriteMaterial,StaticCopyUsage,StaticDrawUsage,StaticReadUsage,StereoCamera,StreamCopyUsage,StreamDrawUsage,StreamReadUsage,StringKeyframeTrack,TOUCH,TetrahedronGeometry,TextureLoader,TextureUtils,Timer,TimestampQuery,TorusGeometry,TorusKnotGeometry,Triangle,TriangleFanDrawMode,TriangleStripDrawMode,TrianglesDrawMode,TubeGeometry,UVMapping,Uint8BufferAttribute,Uint8ClampedBufferAttribute,Uniform,UniformsGroup,VectorKeyframeTrack,VideoFrameTexture,VideoTexture,WebGL3DRenderTarget,WebGLArrayRenderTarget,WebGPUCoordinateSystem,WireframeGeometry,WrapAroundEnding,ZeroCurvatureEnding,ZeroSlopeEnding,ZeroStencilOp}from"./three.core.min.js";function Cn(){let e=null,t=!1,n=null,r=null;function i(t,a){n(t,a),r=e.requestAnimationFrame(i)}return{start:function(){!0!==t&&null!==n&&(r=e.requestAnimationFrame(i),t=!0)},stop:function(){e.cancelAnimationFrame(r),t=!1},setAnimationLoop:function(e){n=e},setContext:function(t){e=t}}}function Ln(e){const t=new WeakMap;return{get:function(e){return e.isInterleavedBufferAttribute&&(e=e.data),t.get(e)},remove:function(n){n.isInterleavedBufferAttribute&&(n=n.data);const r=t.get(n);r&&(e.deleteBuffer(r.buffer),t.delete(n))},update:function(n,r){if(n.isInterleavedBufferAttribute&&(n=n.data),n.isGLBufferAttribute){const e=t.get(n);return void((!e||e.versione.start-t.start);let t=0;for(let e=1;e 0\n\tvec4 plane;\n\t#ifdef ALPHA_TO_COVERAGE\n\t\tfloat distanceToPlane, distanceGradient;\n\t\tfloat clipOpacity = 1.0;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\tclipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\tif ( clipOpacity == 0.0 ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tfloat unionClipOpacity = 1.0;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\t\tunionClipOpacity *= 1.0 - smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tclipOpacity *= 1.0 - unionClipOpacity;\n\t\t#endif\n\t\tdiffuseColor.a *= clipOpacity;\n\t\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tbool clipped = true;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tif ( clipped ) discard;\n\t\t#endif\n\t#endif\n#endif",clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif",color_fragment:"#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvarying vec3 vColor;\n#endif",color_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif\n#ifdef USE_BATCHING_COLOR\n\tvec3 batchingColor = getBatchingColor( getIndirectIndex( gl_DrawID ) );\n\tvColor.xyz *= batchingColor.xyz;\n#endif",common:"#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated",cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif",defaultnormal_vertex:"vec3 transformedNormal = objectNormal;\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = objectTangent;\n#endif\n#ifdef USE_BATCHING\n\tmat3 bm = mat3( batchingMatrix );\n\ttransformedNormal /= vec3( dot( bm[ 0 ], bm[ 0 ] ), dot( bm[ 1 ], bm[ 1 ] ), dot( bm[ 2 ], bm[ 2 ] ) );\n\ttransformedNormal = bm * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = bm * transformedTangent;\n\t#endif\n#endif\n#ifdef USE_INSTANCING\n\tmat3 im = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( im[ 0 ], im[ 0 ] ), dot( im[ 1 ], im[ 1 ] ), dot( im[ 2 ], im[ 2 ] ) );\n\ttransformedNormal = im * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = im * transformedTangent;\n\t#endif\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\ttransformedTangent = ( modelViewMatrix * vec4( transformedTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif",emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE_EMISSIVE\n\t\temissiveColor = sRGBTransferEOTF( emissiveColor );\n\t#endif\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",colorspace_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",colorspace_pars_fragment:"vec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferEOTF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}",envmap_fragment:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, envMapRotation * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",envmap_common_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform mat3 envMapRotation;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",fog_vertex:"#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",gradientmap_pars_fragment:"#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_fragment:"LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;",lights_lambert_pars_fragment:"varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert",lights_pars_begin:"uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif ( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif",lights_toon_fragment:"ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;",lights_toon_pars_fragment:"varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometryNormal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon",lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong",lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_DISPERSION\n\tmaterial.dispersion = dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif",lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\tfloat dispersion;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",lights_fragment_begin:"\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowIntensity, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif",lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometryNormal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif",lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGARITHMIC_DEPTH_BUFFER )\n\tgl_FragDepth = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGARITHMIC_DEPTH_BUFFER )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_pars_vertex:"#ifdef USE_LOGARITHMIC_DEPTH_BUFFER\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGARITHMIC_DEPTH_BUFFER\n\tvFragDepth = 1.0 + gl_Position.w;\n\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n#endif",map_fragment:"#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = sRGBTransferEOTF( sampledDiffuseColor );\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif",map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif",map_particle_pars_fragment:"#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphinstance_vertex:"#ifdef USE_INSTANCING_MORPH\n\tfloat morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\tfloat morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tmorphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;\n\t}\n#endif",morphcolor_vertex:"#if defined( USE_MORPHCOLORS )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\t#ifndef USE_INSTANCING_MORPH\n\t\tuniform float morphTargetBaseInfluence;\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t#endif\n\tuniform sampler2DArray morphTargetsTexture;\n\tuniform ivec2 morphTargetsTextureSize;\n\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t}\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif",normal_fragment_begin:"float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal,\n\t\t#if defined( USE_NORMALMAP )\n\t\t\tvNormalMapUv\n\t\t#elif defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tvClearcoatNormalMapUv\n\t\t#else\n\t\t\tvUv\n\t\t#endif\n\t\t);\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 nonPerturbedNormal = normal;",normal_fragment_maps:"#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif",normal_pars_fragment:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_pars_vertex:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_vertex:"#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif",normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif",clearcoat_normal_fragment_begin:"#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = nonPerturbedNormal;\n#endif",clearcoat_normal_fragment_maps:"#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif",clearcoat_pars_fragment:"#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif",iridescence_pars_fragment:"#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif",opaque_fragment:"#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );",packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;const float ShiftRight8 = 1. / 256.;\nconst float Inv255 = 1. / 255.;\nconst vec4 PackFactors = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 );\nconst vec2 UnpackFactors2 = vec2( UnpackDownscale, 1.0 / PackFactors.g );\nconst vec3 UnpackFactors3 = vec3( UnpackDownscale / PackFactors.rg, 1.0 / PackFactors.b );\nconst vec4 UnpackFactors4 = vec4( UnpackDownscale / PackFactors.rgb, 1.0 / PackFactors.a );\nvec4 packDepthToRGBA( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec4( 0., 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec4( 1., 1., 1., 1. );\n\tfloat vuf;\n\tfloat af = modf( v * PackFactors.a, vuf );\n\tfloat bf = modf( vuf * ShiftRight8, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec4( vuf * Inv255, gf * PackUpscale, bf * PackUpscale, af );\n}\nvec3 packDepthToRGB( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec3( 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec3( 1., 1., 1. );\n\tfloat vuf;\n\tfloat bf = modf( v * PackFactors.b, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec3( vuf * Inv255, gf * PackUpscale, bf );\n}\nvec2 packDepthToRG( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec2( 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec2( 1., 1. );\n\tfloat vuf;\n\tfloat gf = modf( v * 256., vuf );\n\treturn vec2( vuf * Inv255, gf );\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors4 );\n}\nfloat unpackRGBToDepth( const in vec3 v ) {\n\treturn dot( v, UnpackFactors3 );\n}\nfloat unpackRGToDepth( const in vec2 v ) {\n\treturn v.r * UnpackFactors2.r + v.g * UnpackFactors2.g;\n}\nvec4 pack2HalfToRGBA( const in vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( const in vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}",premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_BATCHING\n\tmvPosition = batchingMatrix * mvPosition;\n#endif\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\tfloat depth = unpackRGBAToDepth( texture2D( depths, uv ) );\n\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\treturn step( depth, compare );\n\t\t#else\n\t\t\treturn step( compare, depth );\n\t\t#endif\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow( sampler2D shadow, vec2 uv, float compare ) {\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\tfloat hard_shadow = step( distribution.x, compare );\n\t\t#else\n\t\t\tfloat hard_shadow = step( compare, distribution.x );\n\t\t#endif\n\t\tif ( hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tfloat shadow = 1.0;\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\t\n\t\tfloat lightToPositionLength = length( lightToPosition );\n\t\tif ( lightToPositionLength - shadowCameraFar <= 0.0 && lightToPositionLength - shadowCameraNear >= 0.0 ) {\n\t\t\tfloat dp = ( lightToPositionLength - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\t\tdp += shadowBias;\n\t\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\t\tshadow = (\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t\t) * ( 1.0 / 9.0 );\n\t\t\t#else\n\t\t\t\tshadow = texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n#endif",shadowmap_pars_vertex:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif",shadowmap_vertex:"#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif",shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowIntensity, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowIntensity, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowIntensity, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}",skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tint size = textureSize( boneTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 CineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor *= toneMappingExposure;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\tcolor = clamp( color, 0.0, 1.0 );\n\treturn color;\n}\nvec3 NeutralToneMapping( vec3 color ) {\n\tconst float StartCompression = 0.8 - 0.04;\n\tconst float Desaturation = 0.15;\n\tcolor *= toneMappingExposure;\n\tfloat x = min( color.r, min( color.g, color.b ) );\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max( color.r, max( color.g, color.b ) );\n\tif ( peak < StartCompression ) return color;\n\tfloat d = 1. - StartCompression;\n\tfloat newPeak = 1. - d * d / ( peak + d - StartCompression );\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / ( Desaturation * ( peak - newPeak ) + 1. );\n\treturn mix( color, vec3( newPeak ), g );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }",transmission_fragment:"#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.dispersion, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif",transmission_pars_fragment:"#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float dispersion, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec4 transmittedLight;\n\t\tvec3 transmittance;\n\t\t#ifdef USE_DISPERSION\n\t\t\tfloat halfSpread = ( ior - 1.0 ) * 0.025 * dispersion;\n\t\t\tvec3 iors = vec3( ior - halfSpread, ior, ior + halfSpread );\n\t\t\tfor ( int i = 0; i < 3; i ++ ) {\n\t\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, iors[ i ], modelMatrix );\n\t\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\t\trefractionCoords += 1.0;\n\t\t\t\trefractionCoords /= 2.0;\n\t\t\t\tvec4 transmissionSample = getTransmissionSample( refractionCoords, roughness, iors[ i ] );\n\t\t\t\ttransmittedLight[ i ] = transmissionSample[ i ];\n\t\t\t\ttransmittedLight.a += transmissionSample.a;\n\t\t\t\ttransmittance[ i ] = diffuseColor[ i ] * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance )[ i ];\n\t\t\t}\n\t\t\ttransmittedLight.a /= 3.0;\n\t\t#else\n\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\trefractionCoords += 1.0;\n\t\t\trefractionCoords /= 2.0;\n\t\t\ttransmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\t\ttransmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\t#endif\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif",uv_pars_fragment:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_pars_vertex:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tuniform mat3 anisotropyMapTransform;\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_vertex:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_BATCHING\n\t\tworldPosition = batchingMatrix * worldPosition;\n\t#endif\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",background_frag:"uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",backgroundCube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",backgroundCube_frag:"#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nuniform mat3 backgroundRotation;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, backgroundRotation * vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, backgroundRotation * vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",cube_frag:"uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\tfloat fragCoordZ = vHighPrecisionZW[ 0 ] / vHighPrecisionZW[ 1 ];\n\t#else\n\t\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[ 0 ] / vHighPrecisionZW[ 1 ] + 0.5;\n\t#endif\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#elif DEPTH_PACKING == 3202\n\t\tgl_FragColor = vec4( packDepthToRGB( fragCoordZ ), 1.0 );\n\t#elif DEPTH_PACKING == 3203\n\t\tgl_FragColor = vec4( packDepthToRG( fragCoordZ ), 0.0, 1.0 );\n\t#endif\n}",distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}",linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_vert:"#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_frag:"#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_vert:"#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}",meshmatcap_frag:"#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshnormal_vert:"#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}",meshnormal_frag:"#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( 0.0, 0.0, 0.0, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), diffuseColor.a );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}",meshphong_vert:"#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphong_frag:"#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_vert:"#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}",meshphysical_frag:"#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_DISPERSION\n\tuniform float dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshtoon_vert:"#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}",meshtoon_frag:"#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",points_vert:"uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}",points_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_frag:"uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}",sprite_vert:"uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix[ 3 ];\n\tvec2 scale = vec2( length( modelMatrix[ 0 ].xyz ), length( modelMatrix[ 1 ].xyz ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}",sprite_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"},Un={common:{diffuse:{value:new n(16777215)},opacity:{value:1},map:{value:null},mapTransform:{value:new e},alphaMap:{value:null},alphaMapTransform:{value:new e},alphaTest:{value:0}},specularmap:{specularMap:{value:null},specularMapTransform:{value:new e}},envmap:{envMap:{value:null},envMapRotation:{value:new e},flipEnvMap:{value:-1},reflectivity:{value:1},ior:{value:1.5},refractionRatio:{value:.98}},aomap:{aoMap:{value:null},aoMapIntensity:{value:1},aoMapTransform:{value:new e}},lightmap:{lightMap:{value:null},lightMapIntensity:{value:1},lightMapTransform:{value:new e}},bumpmap:{bumpMap:{value:null},bumpMapTransform:{value:new e},bumpScale:{value:1}},normalmap:{normalMap:{value:null},normalMapTransform:{value:new e},normalScale:{value:new t(1,1)}},displacementmap:{displacementMap:{value:null},displacementMapTransform:{value:new e},displacementScale:{value:1},displacementBias:{value:0}},emissivemap:{emissiveMap:{value:null},emissiveMapTransform:{value:new e}},metalnessmap:{metalnessMap:{value:null},metalnessMapTransform:{value:new e}},roughnessmap:{roughnessMap:{value:null},roughnessMapTransform:{value:new e}},gradientmap:{gradientMap:{value:null}},fog:{fogDensity:{value:25e-5},fogNear:{value:1},fogFar:{value:2e3},fogColor:{value:new n(16777215)}},lights:{ambientLightColor:{value:[]},lightProbe:{value:[]},directionalLights:{value:[],properties:{direction:{},color:{}}},directionalLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},directionalShadowMap:{value:[]},directionalShadowMatrix:{value:[]},spotLights:{value:[],properties:{color:{},position:{},direction:{},distance:{},coneCos:{},penumbraCos:{},decay:{}}},spotLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},spotLightMap:{value:[]},spotShadowMap:{value:[]},spotLightMatrix:{value:[]},pointLights:{value:[],properties:{color:{},position:{},decay:{},distance:{}}},pointLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{},shadowCameraNear:{},shadowCameraFar:{}}},pointShadowMap:{value:[]},pointShadowMatrix:{value:[]},hemisphereLights:{value:[],properties:{direction:{},skyColor:{},groundColor:{}}},rectAreaLights:{value:[],properties:{color:{},position:{},width:{},height:{}}},ltc_1:{value:null},ltc_2:{value:null}},points:{diffuse:{value:new n(16777215)},opacity:{value:1},size:{value:1},scale:{value:1},map:{value:null},alphaMap:{value:null},alphaMapTransform:{value:new e},alphaTest:{value:0},uvTransform:{value:new e}},sprite:{diffuse:{value:new n(16777215)},opacity:{value:1},center:{value:new t(.5,.5)},rotation:{value:0},map:{value:null},mapTransform:{value:new e},alphaMap:{value:null},alphaMapTransform:{value:new e},alphaTest:{value:0}}},Dn={basic:{uniforms:r([Un.common,Un.specularmap,Un.envmap,Un.aomap,Un.lightmap,Un.fog]),vertexShader:Pn.meshbasic_vert,fragmentShader:Pn.meshbasic_frag},lambert:{uniforms:r([Un.common,Un.specularmap,Un.envmap,Un.aomap,Un.lightmap,Un.emissivemap,Un.bumpmap,Un.normalmap,Un.displacementmap,Un.fog,Un.lights,{emissive:{value:new n(0)}}]),vertexShader:Pn.meshlambert_vert,fragmentShader:Pn.meshlambert_frag},phong:{uniforms:r([Un.common,Un.specularmap,Un.envmap,Un.aomap,Un.lightmap,Un.emissivemap,Un.bumpmap,Un.normalmap,Un.displacementmap,Un.fog,Un.lights,{emissive:{value:new n(0)},specular:{value:new n(1118481)},shininess:{value:30}}]),vertexShader:Pn.meshphong_vert,fragmentShader:Pn.meshphong_frag},standard:{uniforms:r([Un.common,Un.envmap,Un.aomap,Un.lightmap,Un.emissivemap,Un.bumpmap,Un.normalmap,Un.displacementmap,Un.roughnessmap,Un.metalnessmap,Un.fog,Un.lights,{emissive:{value:new n(0)},roughness:{value:1},metalness:{value:0},envMapIntensity:{value:1}}]),vertexShader:Pn.meshphysical_vert,fragmentShader:Pn.meshphysical_frag},toon:{uniforms:r([Un.common,Un.aomap,Un.lightmap,Un.emissivemap,Un.bumpmap,Un.normalmap,Un.displacementmap,Un.gradientmap,Un.fog,Un.lights,{emissive:{value:new n(0)}}]),vertexShader:Pn.meshtoon_vert,fragmentShader:Pn.meshtoon_frag},matcap:{uniforms:r([Un.common,Un.bumpmap,Un.normalmap,Un.displacementmap,Un.fog,{matcap:{value:null}}]),vertexShader:Pn.meshmatcap_vert,fragmentShader:Pn.meshmatcap_frag},points:{uniforms:r([Un.points,Un.fog]),vertexShader:Pn.points_vert,fragmentShader:Pn.points_frag},dashed:{uniforms:r([Un.common,Un.fog,{scale:{value:1},dashSize:{value:1},totalSize:{value:2}}]),vertexShader:Pn.linedashed_vert,fragmentShader:Pn.linedashed_frag},depth:{uniforms:r([Un.common,Un.displacementmap]),vertexShader:Pn.depth_vert,fragmentShader:Pn.depth_frag},normal:{uniforms:r([Un.common,Un.bumpmap,Un.normalmap,Un.displacementmap,{opacity:{value:1}}]),vertexShader:Pn.meshnormal_vert,fragmentShader:Pn.meshnormal_frag},sprite:{uniforms:r([Un.sprite,Un.fog]),vertexShader:Pn.sprite_vert,fragmentShader:Pn.sprite_frag},background:{uniforms:{uvTransform:{value:new e},t2D:{value:null},backgroundIntensity:{value:1}},vertexShader:Pn.background_vert,fragmentShader:Pn.background_frag},backgroundCube:{uniforms:{envMap:{value:null},flipEnvMap:{value:-1},backgroundBlurriness:{value:0},backgroundIntensity:{value:1},backgroundRotation:{value:new e}},vertexShader:Pn.backgroundCube_vert,fragmentShader:Pn.backgroundCube_frag},cube:{uniforms:{tCube:{value:null},tFlip:{value:-1},opacity:{value:1}},vertexShader:Pn.cube_vert,fragmentShader:Pn.cube_frag},equirect:{uniforms:{tEquirect:{value:null}},vertexShader:Pn.equirect_vert,fragmentShader:Pn.equirect_frag},distanceRGBA:{uniforms:r([Un.common,Un.displacementmap,{referencePosition:{value:new i},nearDistance:{value:1},farDistance:{value:1e3}}]),vertexShader:Pn.distanceRGBA_vert,fragmentShader:Pn.distanceRGBA_frag},shadow:{uniforms:r([Un.lights,Un.fog,{color:{value:new n(0)},opacity:{value:1}}]),vertexShader:Pn.shadow_vert,fragmentShader:Pn.shadow_frag}};Dn.physical={uniforms:r([Dn.standard.uniforms,{clearcoat:{value:0},clearcoatMap:{value:null},clearcoatMapTransform:{value:new e},clearcoatNormalMap:{value:null},clearcoatNormalMapTransform:{value:new e},clearcoatNormalScale:{value:new t(1,1)},clearcoatRoughness:{value:0},clearcoatRoughnessMap:{value:null},clearcoatRoughnessMapTransform:{value:new e},dispersion:{value:0},iridescence:{value:0},iridescenceMap:{value:null},iridescenceMapTransform:{value:new e},iridescenceIOR:{value:1.3},iridescenceThicknessMinimum:{value:100},iridescenceThicknessMaximum:{value:400},iridescenceThicknessMap:{value:null},iridescenceThicknessMapTransform:{value:new e},sheen:{value:0},sheenColor:{value:new n(0)},sheenColorMap:{value:null},sheenColorMapTransform:{value:new e},sheenRoughness:{value:1},sheenRoughnessMap:{value:null},sheenRoughnessMapTransform:{value:new e},transmission:{value:0},transmissionMap:{value:null},transmissionMapTransform:{value:new e},transmissionSamplerSize:{value:new t},transmissionSamplerMap:{value:null},thickness:{value:0},thicknessMap:{value:null},thicknessMapTransform:{value:new e},attenuationDistance:{value:0},attenuationColor:{value:new n(0)},specularColor:{value:new n(1,1,1)},specularColorMap:{value:null},specularColorMapTransform:{value:new e},specularIntensity:{value:1},specularIntensityMap:{value:null},specularIntensityMapTransform:{value:new e},anisotropyVector:{value:new t},anisotropyMap:{value:null},anisotropyMapTransform:{value:new e}}]),vertexShader:Pn.meshphysical_vert,fragmentShader:Pn.meshphysical_frag};const wn={r:0,b:0,g:0},In=new u,yn=new f;function Nn(e,t,r,i,u,f,v){const E=new n(0);let S,T,M=!0===f?0:1,x=null,R=0,A=null;function b(e){let n=!0===e.isScene?e.background:null;if(n&&n.isTexture){n=(e.backgroundBlurriness>0?r:t).get(n)}return n}function C(t,n){t.getRGB(wn,g(e)),i.buffers.color.setClear(wn.r,wn.g,wn.b,n,v)}return{getClearColor:function(){return E},setClearColor:function(e,t=1){E.set(e),M=t,C(E,M)},getClearAlpha:function(){return M},setClearAlpha:function(e){M=e,C(E,M)},render:function(t){let n=!1;const r=b(t);null===r?C(E,M):r&&r.isColor&&(C(r,1),n=!0);const a=e.xr.getEnvironmentBlendMode();"additive"===a?i.buffers.color.setClear(0,0,0,1,v):"alpha-blend"===a&&i.buffers.color.setClear(0,0,0,0,v),(e.autoClear||n)&&(i.buffers.depth.setTest(!0),i.buffers.depth.setMask(!0),i.buffers.color.setMask(!0),e.clear(e.autoClearColor,e.autoClearDepth,e.autoClearStencil))},addToRenderList:function(t,n){const r=b(n);r&&(r.isCubeTexture||r.mapping===a)?(void 0===T&&(T=new o(new s(1,1,1),new l({name:"BackgroundCubeMaterial",uniforms:d(Dn.backgroundCube.uniforms),vertexShader:Dn.backgroundCube.vertexShader,fragmentShader:Dn.backgroundCube.fragmentShader,side:c,depthTest:!1,depthWrite:!1,fog:!1,allowOverride:!1})),T.geometry.deleteAttribute("normal"),T.geometry.deleteAttribute("uv"),T.onBeforeRender=function(e,t,n){this.matrixWorld.copyPosition(n.matrixWorld)},Object.defineProperty(T.material,"envMap",{get:function(){return this.uniforms.envMap.value}}),u.update(T)),In.copy(n.backgroundRotation),In.x*=-1,In.y*=-1,In.z*=-1,r.isCubeTexture&&!1===r.isRenderTargetTexture&&(In.y*=-1,In.z*=-1),T.material.uniforms.envMap.value=r,T.material.uniforms.flipEnvMap.value=r.isCubeTexture&&!1===r.isRenderTargetTexture?-1:1,T.material.uniforms.backgroundBlurriness.value=n.backgroundBlurriness,T.material.uniforms.backgroundIntensity.value=n.backgroundIntensity,T.material.uniforms.backgroundRotation.value.setFromMatrix4(yn.makeRotationFromEuler(In)),T.material.toneMapped=p.getTransfer(r.colorSpace)!==m,x===r&&R===r.version&&A===e.toneMapping||(T.material.needsUpdate=!0,x=r,R=r.version,A=e.toneMapping),T.layers.enableAll(),t.unshift(T,T.geometry,T.material,0,0,null)):r&&r.isTexture&&(void 0===S&&(S=new o(new h(2,2),new l({name:"BackgroundMaterial",uniforms:d(Dn.background.uniforms),vertexShader:Dn.background.vertexShader,fragmentShader:Dn.background.fragmentShader,side:_,depthTest:!1,depthWrite:!1,fog:!1,allowOverride:!1})),S.geometry.deleteAttribute("normal"),Object.defineProperty(S.material,"map",{get:function(){return this.uniforms.t2D.value}}),u.update(S)),S.material.uniforms.t2D.value=r,S.material.uniforms.backgroundIntensity.value=n.backgroundIntensity,S.material.toneMapped=p.getTransfer(r.colorSpace)!==m,!0===r.matrixAutoUpdate&&r.updateMatrix(),S.material.uniforms.uvTransform.value.copy(r.matrix),x===r&&R===r.version&&A===e.toneMapping||(S.material.needsUpdate=!0,x=r,R=r.version,A=e.toneMapping),S.layers.enableAll(),t.unshift(S,S.geometry,S.material,0,0,null))},dispose:function(){void 0!==T&&(T.geometry.dispose(),T.material.dispose(),T=void 0),void 0!==S&&(S.geometry.dispose(),S.material.dispose(),S=void 0)}}}function On(e,t){const n=e.getParameter(e.MAX_VERTEX_ATTRIBS),r={},i=c(null);let a=i,o=!1;function s(t){return e.bindVertexArray(t)}function l(t){return e.deleteVertexArray(t)}function c(e){const t=[],r=[],i=[];for(let e=0;e=0){const n=i[t];let r=o[t];if(void 0===r&&("instanceMatrix"===t&&e.instanceMatrix&&(r=e.instanceMatrix),"instanceColor"===t&&e.instanceColor&&(r=e.instanceColor)),void 0===n)return!0;if(n.attribute!==r)return!0;if(r&&n.data!==r.data)return!0;s++}}return a.attributesNum!==s||a.index!==r}(n,h,l,_),g&&function(e,t,n,r){const i={},o=t.attributes;let s=0;const l=n.getAttributes();for(const t in l){if(l[t].location>=0){let n=o[t];void 0===n&&("instanceMatrix"===t&&e.instanceMatrix&&(n=e.instanceMatrix),"instanceColor"===t&&e.instanceColor&&(n=e.instanceColor));const r={};r.attribute=n,n&&n.data&&(r.data=n.data),i[t]=r,s++}}a.attributes=i,a.attributesNum=s,a.index=r}(n,h,l,_),null!==_&&t.update(_,e.ELEMENT_ARRAY_BUFFER),(g||o)&&(o=!1,function(n,r,i,a){d();const o=a.attributes,s=i.getAttributes(),l=r.defaultAttributeValues;for(const r in s){const i=s[r];if(i.location>=0){let s=o[r];if(void 0===s&&("instanceMatrix"===r&&n.instanceMatrix&&(s=n.instanceMatrix),"instanceColor"===r&&n.instanceColor&&(s=n.instanceColor)),void 0!==s){const r=s.normalized,o=s.itemSize,l=t.get(s);if(void 0===l)continue;const c=l.buffer,d=l.type,p=l.bytesPerElement,h=d===e.INT||d===e.UNSIGNED_INT||s.gpuType===v;if(s.isInterleavedBufferAttribute){const t=s.data,l=t.stride,_=s.offset;if(t.isInstancedInterleavedBuffer){for(let e=0;e0&&e.getShaderPrecisionFormat(e.FRAGMENT_SHADER,e.HIGH_FLOAT).precision>0)return"highp";t="mediump"}return"mediump"===t&&e.getShaderPrecisionFormat(e.VERTEX_SHADER,e.MEDIUM_FLOAT).precision>0&&e.getShaderPrecisionFormat(e.FRAGMENT_SHADER,e.MEDIUM_FLOAT).precision>0?"mediump":"lowp"}let o=void 0!==n.precision?n.precision:"highp";const s=a(o);s!==o&&(console.warn("THREE.WebGLRenderer:",o,"not supported, using",s,"instead."),o=s);const l=!0===n.logarithmicDepthBuffer,c=!0===n.reversedDepthBuffer&&t.has("EXT_clip_control"),d=e.getParameter(e.MAX_TEXTURE_IMAGE_UNITS),u=e.getParameter(e.MAX_VERTEX_TEXTURE_IMAGE_UNITS);return{isWebGL2:!0,getMaxAnisotropy:function(){if(void 0!==i)return i;if(!0===t.has("EXT_texture_filter_anisotropic")){const n=t.get("EXT_texture_filter_anisotropic");i=e.getParameter(n.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else i=0;return i},getMaxPrecision:a,textureFormatReadable:function(t){return t===M||r.convert(t)===e.getParameter(e.IMPLEMENTATION_COLOR_READ_FORMAT)},textureTypeReadable:function(n){const i=n===E&&(t.has("EXT_color_buffer_half_float")||t.has("EXT_color_buffer_float"));return!(n!==S&&r.convert(n)!==e.getParameter(e.IMPLEMENTATION_COLOR_READ_TYPE)&&n!==T&&!i)},precision:o,logarithmicDepthBuffer:l,reversedDepthBuffer:c,maxTextures:d,maxVertexTextures:u,maxTextureSize:e.getParameter(e.MAX_TEXTURE_SIZE),maxCubemapSize:e.getParameter(e.MAX_CUBE_MAP_TEXTURE_SIZE),maxAttributes:e.getParameter(e.MAX_VERTEX_ATTRIBS),maxVertexUniforms:e.getParameter(e.MAX_VERTEX_UNIFORM_VECTORS),maxVaryings:e.getParameter(e.MAX_VARYING_VECTORS),maxFragmentUniforms:e.getParameter(e.MAX_FRAGMENT_UNIFORM_VECTORS),vertexTextures:u>0,maxSamples:e.getParameter(e.MAX_SAMPLES)}}function Hn(t){const n=this;let r=null,i=0,a=!1,o=!1;const s=new x,l=new e,c={value:null,needsUpdate:!1};function d(e,t,r,i){const a=null!==e?e.length:0;let o=null;if(0!==a){if(o=c.value,!0!==i||null===o){const n=r+4*a,i=t.matrixWorldInverse;l.getNormalMatrix(i),(null===o||o.length0);n.numPlanes=i,n.numIntersection=0}();else{const e=o?0:i,t=4*e;let n=m.clippingState||null;c.value=n,n=d(u,s,t,l);for(let e=0;e!==t;++e)n[e]=r[e];m.clippingState=n,this.numIntersection=f?this.numPlanes:0,this.numPlanes+=e}}}function Gn(e){let t=new WeakMap;function n(e,t){return t===R?e.mapping=C:t===A&&(e.mapping=L),e}function r(e){const n=e.target;n.removeEventListener("dispose",r);const i=t.get(n);void 0!==i&&(t.delete(n),i.dispose())}return{get:function(i){if(i&&i.isTexture){const a=i.mapping;if(a===R||a===A){if(t.has(i)){return n(t.get(i).texture,i.mapping)}{const a=i.image;if(a&&a.height>0){const o=new b(a.height);return o.fromEquirectangularTexture(e,i),t.set(i,o),i.addEventListener("dispose",r),n(o.texture,i.mapping)}return null}}}return i},dispose:function(){t=new WeakMap}}}const Vn=[.125,.215,.35,.446,.526,.582],zn=20,kn=new P,Wn=new n;let Xn=null,Yn=0,Kn=0,qn=!1;const jn=(1+Math.sqrt(5))/2,Zn=1/jn,$n=[new i(-jn,Zn,0),new i(jn,Zn,0),new i(-Zn,0,jn),new i(Zn,0,jn),new i(0,jn,-Zn),new i(0,jn,Zn),new i(-1,1,-1),new i(1,1,-1),new i(-1,1,1),new i(1,1,1)],Qn=new i;class Jn{constructor(e){this._renderer=e,this._pingPongRenderTarget=null,this._lodMax=0,this._cubeSize=0,this._lodPlanes=[],this._sizeLods=[],this._sigmas=[],this._blurMaterial=null,this._cubemapMaterial=null,this._equirectMaterial=null,this._compileMaterial(this._blurMaterial)}fromScene(e,t=0,n=.1,r=100,i={}){const{size:a=256,position:o=Qn}=i;Xn=this._renderer.getRenderTarget(),Yn=this._renderer.getActiveCubeFace(),Kn=this._renderer.getActiveMipmapLevel(),qn=this._renderer.xr.enabled,this._renderer.xr.enabled=!1,this._setSize(a);const s=this._allocateTargets();return s.depthBuffer=!0,this._sceneToCubeUV(e,n,r,s,o),t>0&&this._blur(s,0,0,t),this._applyPMREM(s),this._cleanup(s),s}fromEquirectangular(e,t=null){return this._fromTexture(e,t)}fromCubemap(e,t=null){return this._fromTexture(e,t)}compileCubemapShader(){null===this._cubemapMaterial&&(this._cubemapMaterial=rr(),this._compileMaterial(this._cubemapMaterial))}compileEquirectangularShader(){null===this._equirectMaterial&&(this._equirectMaterial=nr(),this._compileMaterial(this._equirectMaterial))}dispose(){this._dispose(),null!==this._cubemapMaterial&&this._cubemapMaterial.dispose(),null!==this._equirectMaterial&&this._equirectMaterial.dispose()}_setSize(e){this._lodMax=Math.floor(Math.log2(e)),this._cubeSize=Math.pow(2,this._lodMax)}_dispose(){null!==this._blurMaterial&&this._blurMaterial.dispose(),null!==this._pingPongRenderTarget&&this._pingPongRenderTarget.dispose();for(let e=0;ee-4?s=Vn[o-e+4-1]:0===o&&(s=0),r.push(s);const l=1/(a-2),c=-l,d=1+l,u=[c,c,d,c,d,d,c,c,d,d,c,d],f=6,p=6,m=3,h=2,_=1,g=new Float32Array(m*p*f),v=new Float32Array(h*p*f),E=new Float32Array(_*p*f);for(let e=0;e2?0:-1,r=[t,n,0,t+2/3,n,0,t+2/3,n+1,0,t,n,0,t+2/3,n+1,0,t,n+1,0];g.set(r,m*p*e),v.set(u,h*p*e);const i=[e,e,e,e,e,e];E.set(i,_*p*e)}const S=new N;S.setAttribute("position",new O(g,m)),S.setAttribute("uv",new O(v,h)),S.setAttribute("faceIndex",new O(E,_)),t.push(S),i>4&&i--}return{lodPlanes:t,sizeLods:n,sigmas:r}}(r)),this._blurMaterial=function(e,t,n){const r=new Float32Array(zn),a=new i(0,1,0),o=new l({name:"SphericalGaussianBlur",defines:{n:zn,CUBEUV_TEXEL_WIDTH:1/t,CUBEUV_TEXEL_HEIGHT:1/n,CUBEUV_MAX_MIP:`${e}.0`},uniforms:{envMap:{value:null},samples:{value:1},weights:{value:r},latitudinal:{value:!1},dTheta:{value:0},mipInt:{value:0},poleAxis:{value:a}},vertexShader:ir(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\t\t\tuniform int samples;\n\t\t\tuniform float weights[ n ];\n\t\t\tuniform bool latitudinal;\n\t\t\tuniform float dTheta;\n\t\t\tuniform float mipInt;\n\t\t\tuniform vec3 poleAxis;\n\n\t\t\t#define ENVMAP_TYPE_CUBE_UV\n\t\t\t#include \n\n\t\t\tvec3 getSample( float theta, vec3 axis ) {\n\n\t\t\t\tfloat cosTheta = cos( theta );\n\t\t\t\t// Rodrigues' axis-angle rotation\n\t\t\t\tvec3 sampleDirection = vOutputDirection * cosTheta\n\t\t\t\t\t+ cross( axis, vOutputDirection ) * sin( theta )\n\t\t\t\t\t+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );\n\n\t\t\t\treturn bilinearCubeUV( envMap, sampleDirection, mipInt );\n\n\t\t\t}\n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );\n\n\t\t\t\tif ( all( equal( axis, vec3( 0.0 ) ) ) ) {\n\n\t\t\t\t\taxis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );\n\n\t\t\t\t}\n\n\t\t\t\taxis = normalize( axis );\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );\n\n\t\t\t\tfor ( int i = 1; i < n; i++ ) {\n\n\t\t\t\t\tif ( i >= samples ) {\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat theta = dTheta * float( i );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( theta, axis );\n\n\t\t\t\t}\n\n\t\t\t}\n\t\t",blending:I,depthTest:!1,depthWrite:!1});return o}(r,e,t)}return r}_compileMaterial(e){const t=new o(this._lodPlanes[0],e);this._renderer.compile(t,kn)}_sceneToCubeUV(e,t,n,r,i){const a=new U(90,1,t,n),l=[1,-1,1,1,1,1],d=[1,1,1,-1,-1,-1],u=this._renderer,f=u.autoClear,p=u.toneMapping;u.getClearColor(Wn),u.toneMapping=D,u.autoClear=!1;u.state.buffers.depth.getReversed()&&(u.setRenderTarget(r),u.clearDepth(),u.setRenderTarget(null));const m=new w({name:"PMREM.Background",side:c,depthWrite:!1,depthTest:!1}),h=new o(new s,m);let _=!1;const g=e.background;g?g.isColor&&(m.color.copy(g),e.background=null,_=!0):(m.color.copy(Wn),_=!0);for(let t=0;t<6;t++){const n=t%3;0===n?(a.up.set(0,l[t],0),a.position.set(i.x,i.y,i.z),a.lookAt(i.x+d[t],i.y,i.z)):1===n?(a.up.set(0,0,l[t]),a.position.set(i.x,i.y,i.z),a.lookAt(i.x,i.y+d[t],i.z)):(a.up.set(0,l[t],0),a.position.set(i.x,i.y,i.z),a.lookAt(i.x,i.y,i.z+d[t]));const o=this._cubeSize;tr(r,n*o,t>2?o:0,o,o),u.setRenderTarget(r),_&&u.render(h,a),u.render(e,a)}h.geometry.dispose(),h.material.dispose(),u.toneMapping=p,u.autoClear=f,e.background=g}_textureToCubeUV(e,t){const n=this._renderer,r=e.mapping===C||e.mapping===L;r?(null===this._cubemapMaterial&&(this._cubemapMaterial=rr()),this._cubemapMaterial.uniforms.flipEnvMap.value=!1===e.isRenderTargetTexture?-1:1):null===this._equirectMaterial&&(this._equirectMaterial=nr());const i=r?this._cubemapMaterial:this._equirectMaterial,a=new o(this._lodPlanes[0],i);i.uniforms.envMap.value=e;const s=this._cubeSize;tr(t,0,0,3*s,2*s),n.setRenderTarget(t),n.render(a,kn)}_applyPMREM(e){const t=this._renderer,n=t.autoClear;t.autoClear=!1;const r=this._lodPlanes.length;for(let t=1;tzn&&console.warn(`sigmaRadians, ${i}, is too large and will clip, as it requested ${h} samples when the maximum is set to 20`);const _=[];let g=0;for(let e=0;ev-4?r-v+4:0),4*(this._cubeSize-E),3*E,2*E),l.setRenderTarget(t),l.render(d,kn)}}function er(e,t,n){const r=new y(e,t,n);return r.texture.mapping=a,r.texture.name="PMREM.cubeUv",r.scissorTest=!0,r}function tr(e,t,n,r,i){e.viewport.set(t,n,r,i),e.scissor.set(t,n,r,i)}function nr(){return new l({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null}},vertexShader:ir(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\n\t\t\t#include \n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 outputDirection = normalize( vOutputDirection );\n\t\t\t\tvec2 uv = equirectUv( outputDirection );\n\n\t\t\t\tgl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 );\n\n\t\t\t}\n\t\t",blending:I,depthTest:!1,depthWrite:!1})}function rr(){return new l({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},flipEnvMap:{value:-1}},vertexShader:ir(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tuniform float flipEnvMap;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform samplerCube envMap;\n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) );\n\n\t\t\t}\n\t\t",blending:I,depthTest:!1,depthWrite:!1})}function ir(){return"\n\n\t\tprecision mediump float;\n\t\tprecision mediump int;\n\n\t\tattribute float faceIndex;\n\n\t\tvarying vec3 vOutputDirection;\n\n\t\t// RH coordinate system; PMREM face-indexing convention\n\t\tvec3 getDirection( vec2 uv, float face ) {\n\n\t\t\tuv = 2.0 * uv - 1.0;\n\n\t\t\tvec3 direction = vec3( uv, 1.0 );\n\n\t\t\tif ( face == 0.0 ) {\n\n\t\t\t\tdirection = direction.zyx; // ( 1, v, u ) pos x\n\n\t\t\t} else if ( face == 1.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xz *= -1.0; // ( -u, 1, -v ) pos y\n\n\t\t\t} else if ( face == 2.0 ) {\n\n\t\t\t\tdirection.x *= -1.0; // ( -u, v, 1 ) pos z\n\n\t\t\t} else if ( face == 3.0 ) {\n\n\t\t\t\tdirection = direction.zyx;\n\t\t\t\tdirection.xz *= -1.0; // ( -1, v, -u ) neg x\n\n\t\t\t} else if ( face == 4.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xy *= -1.0; // ( -u, -1, v ) neg y\n\n\t\t\t} else if ( face == 5.0 ) {\n\n\t\t\t\tdirection.z *= -1.0; // ( u, v, -1 ) neg z\n\n\t\t\t}\n\n\t\t\treturn direction;\n\n\t\t}\n\n\t\tvoid main() {\n\n\t\t\tvOutputDirection = getDirection( uv, faceIndex );\n\t\t\tgl_Position = vec4( position, 1.0 );\n\n\t\t}\n\t"}function ar(e){let t=new WeakMap,n=null;function r(e){const n=e.target;n.removeEventListener("dispose",r);const i=t.get(n);void 0!==i&&(t.delete(n),i.dispose())}return{get:function(i){if(i&&i.isTexture){const a=i.mapping,o=a===R||a===A,s=a===C||a===L;if(o||s){let a=t.get(i);const l=void 0!==a?a.texture.pmremVersion:0;if(i.isRenderTargetTexture&&i.pmremVersion!==l)return null===n&&(n=new Jn(e)),a=o?n.fromEquirectangular(i,a):n.fromCubemap(i,a),a.texture.pmremVersion=i.pmremVersion,t.set(i,a),a.texture;if(void 0!==a)return a.texture;{const l=i.image;return o&&l&&l.height>0||s&&l&&function(e){let t=0;const n=6;for(let r=0;rn.maxTextureSize&&(M=Math.ceil(S/n.maxTextureSize),S=n.maxTextureSize);const x=new Float32Array(S*M*4*u),R=new W(x,S,M,u);R.type=T,R.needsUpdate=!0;const A=4*E;for(let C=0;C0)return e;const i=t*n;let a=gr[i];if(void 0===a&&(a=new Float32Array(i),gr[i]=a),0!==t){r.toArray(a,0);for(let r=1,i=0;r!==t;++r)i+=n,e[r].toArray(a,i)}return a}function xr(e,t){if(e.length!==t.length)return!1;for(let n=0,r=e.length;n":" "} ${i}: ${n[e]}`)}return r.join("\n")}(e.getShaderSource(t),r)}return i}function Ti(e,t){const n=function(e){p._getMatrix(Ei,p.workingColorSpace,e);const t=`mat3( ${Ei.elements.map(e=>e.toFixed(4))} )`;switch(p.getTransfer(e)){case se:return[t,"LinearTransferOETF"];case m:return[t,"sRGBTransferOETF"];default:return console.warn("THREE.WebGLProgram: Unsupported color space: ",e),[t,"LinearTransferOETF"]}}(t);return[`vec4 ${e}( vec4 value ) {`,`\treturn ${n[1]}( vec4( value.rgb * ${n[0]}, value.a ) );`,"}"].join("\n")}function Mi(e,t){let n;switch(t){case oe:n="Linear";break;case ae:n="Reinhard";break;case ie:n="Cineon";break;case re:n="ACESFilmic";break;case ne:n="AgX";break;case te:n="Neutral";break;case ee:n="Custom";break;default:console.warn("THREE.WebGLProgram: Unsupported toneMapping:",t),n="Linear"}return"vec3 "+e+"( vec3 color ) { return "+n+"ToneMapping( color ); }"}const xi=new i;function Ri(){p.getLuminanceCoefficients(xi);return["float luminance( const in vec3 rgb ) {",`\tconst vec3 weights = vec3( ${xi.x.toFixed(4)}, ${xi.y.toFixed(4)}, ${xi.z.toFixed(4)} );`,"\treturn dot( weights, rgb );","}"].join("\n")}function Ai(e){return""!==e}function bi(e,t){const n=t.numSpotLightShadows+t.numSpotLightMaps-t.numSpotLightShadowsWithMaps;return e.replace(/NUM_DIR_LIGHTS/g,t.numDirLights).replace(/NUM_SPOT_LIGHTS/g,t.numSpotLights).replace(/NUM_SPOT_LIGHT_MAPS/g,t.numSpotLightMaps).replace(/NUM_SPOT_LIGHT_COORDS/g,n).replace(/NUM_RECT_AREA_LIGHTS/g,t.numRectAreaLights).replace(/NUM_POINT_LIGHTS/g,t.numPointLights).replace(/NUM_HEMI_LIGHTS/g,t.numHemiLights).replace(/NUM_DIR_LIGHT_SHADOWS/g,t.numDirLightShadows).replace(/NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g,t.numSpotLightShadowsWithMaps).replace(/NUM_SPOT_LIGHT_SHADOWS/g,t.numSpotLightShadows).replace(/NUM_POINT_LIGHT_SHADOWS/g,t.numPointLightShadows)}function Ci(e,t){return e.replace(/NUM_CLIPPING_PLANES/g,t.numClippingPlanes).replace(/UNION_CLIPPING_PLANES/g,t.numClippingPlanes-t.numClipIntersection)}const Li=/^[ \t]*#include +<([\w\d./]+)>/gm;function Pi(e){return e.replace(Li,Di)}const Ui=new Map;function Di(e,t){let n=Pn[t];if(void 0===n){const e=Ui.get(t);if(void 0===e)throw new Error("Can not resolve #include <"+t+">");n=Pn[e],console.warn('THREE.WebGLRenderer: Shader chunk "%s" has been deprecated. Use "%s" instead.',t,e)}return Pi(n)}const wi=/#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;function Ii(e){return e.replace(wi,yi)}function yi(e,t,n,r){let i="";for(let e=parseInt(t);e0&&(g+="\n"),v=["#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,h].filter(Ai).join("\n"),v.length>0&&(v+="\n")):(g=[Ni(n),"#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,h,n.extensionClipCullDistance?"#define USE_CLIP_DISTANCE":"",n.batching?"#define USE_BATCHING":"",n.batchingColor?"#define USE_BATCHING_COLOR":"",n.instancing?"#define USE_INSTANCING":"",n.instancingColor?"#define USE_INSTANCING_COLOR":"",n.instancingMorph?"#define USE_INSTANCING_MORPH":"",n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+u:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.displacementMap?"#define USE_DISPLACEMENTMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropy?"#define USE_ANISOTROPY":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaHash?"#define USE_ALPHAHASH":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.mapUv?"#define MAP_UV "+n.mapUv:"",n.alphaMapUv?"#define ALPHAMAP_UV "+n.alphaMapUv:"",n.lightMapUv?"#define LIGHTMAP_UV "+n.lightMapUv:"",n.aoMapUv?"#define AOMAP_UV "+n.aoMapUv:"",n.emissiveMapUv?"#define EMISSIVEMAP_UV "+n.emissiveMapUv:"",n.bumpMapUv?"#define BUMPMAP_UV "+n.bumpMapUv:"",n.normalMapUv?"#define NORMALMAP_UV "+n.normalMapUv:"",n.displacementMapUv?"#define DISPLACEMENTMAP_UV "+n.displacementMapUv:"",n.metalnessMapUv?"#define METALNESSMAP_UV "+n.metalnessMapUv:"",n.roughnessMapUv?"#define ROUGHNESSMAP_UV "+n.roughnessMapUv:"",n.anisotropyMapUv?"#define ANISOTROPYMAP_UV "+n.anisotropyMapUv:"",n.clearcoatMapUv?"#define CLEARCOATMAP_UV "+n.clearcoatMapUv:"",n.clearcoatNormalMapUv?"#define CLEARCOAT_NORMALMAP_UV "+n.clearcoatNormalMapUv:"",n.clearcoatRoughnessMapUv?"#define CLEARCOAT_ROUGHNESSMAP_UV "+n.clearcoatRoughnessMapUv:"",n.iridescenceMapUv?"#define IRIDESCENCEMAP_UV "+n.iridescenceMapUv:"",n.iridescenceThicknessMapUv?"#define IRIDESCENCE_THICKNESSMAP_UV "+n.iridescenceThicknessMapUv:"",n.sheenColorMapUv?"#define SHEEN_COLORMAP_UV "+n.sheenColorMapUv:"",n.sheenRoughnessMapUv?"#define SHEEN_ROUGHNESSMAP_UV "+n.sheenRoughnessMapUv:"",n.specularMapUv?"#define SPECULARMAP_UV "+n.specularMapUv:"",n.specularColorMapUv?"#define SPECULAR_COLORMAP_UV "+n.specularColorMapUv:"",n.specularIntensityMapUv?"#define SPECULAR_INTENSITYMAP_UV "+n.specularIntensityMapUv:"",n.transmissionMapUv?"#define TRANSMISSIONMAP_UV "+n.transmissionMapUv:"",n.thicknessMapUv?"#define THICKNESSMAP_UV "+n.thicknessMapUv:"",n.vertexTangents&&!1===n.flatShading?"#define USE_TANGENT":"",n.vertexColors?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.flatShading?"#define FLAT_SHADED":"",n.skinning?"#define USE_SKINNING":"",n.morphTargets?"#define USE_MORPHTARGETS":"",n.morphNormals&&!1===n.flatShading?"#define USE_MORPHNORMALS":"",n.morphColors?"#define USE_MORPHCOLORS":"",n.morphTargetsCount>0?"#define MORPHTARGETS_TEXTURE_STRIDE "+n.morphTextureStride:"",n.morphTargetsCount>0?"#define MORPHTARGETS_COUNT "+n.morphTargetsCount:"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+c:"",n.sizeAttenuation?"#define USE_SIZEATTENUATION":"",n.numLightProbes>0?"#define USE_LIGHT_PROBES":"",n.logarithmicDepthBuffer?"#define USE_LOGARITHMIC_DEPTH_BUFFER":"",n.reversedDepthBuffer?"#define USE_REVERSED_DEPTH_BUFFER":"","uniform mat4 modelMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform mat4 viewMatrix;","uniform mat3 normalMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;","#ifdef USE_INSTANCING","\tattribute mat4 instanceMatrix;","#endif","#ifdef USE_INSTANCING_COLOR","\tattribute vec3 instanceColor;","#endif","#ifdef USE_INSTANCING_MORPH","\tuniform sampler2D morphTexture;","#endif","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_UV1","\tattribute vec2 uv1;","#endif","#ifdef USE_UV2","\tattribute vec2 uv2;","#endif","#ifdef USE_UV3","\tattribute vec2 uv3;","#endif","#ifdef USE_TANGENT","\tattribute vec4 tangent;","#endif","#if defined( USE_COLOR_ALPHA )","\tattribute vec4 color;","#elif defined( USE_COLOR )","\tattribute vec3 color;","#endif","#ifdef USE_SKINNING","\tattribute vec4 skinIndex;","\tattribute vec4 skinWeight;","#endif","\n"].filter(Ai).join("\n"),v=[Ni(n),"#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,h,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.alphaToCoverage?"#define ALPHA_TO_COVERAGE":"",n.map?"#define USE_MAP":"",n.matcap?"#define USE_MATCAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+d:"",n.envMap?"#define "+u:"",n.envMap?"#define "+f:"",p?"#define CUBEUV_TEXEL_WIDTH "+p.texelWidth:"",p?"#define CUBEUV_TEXEL_HEIGHT "+p.texelHeight:"",p?"#define CUBEUV_MAX_MIP "+p.maxMip+".0":"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropy?"#define USE_ANISOTROPY":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoat?"#define USE_CLEARCOAT":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.dispersion?"#define USE_DISPERSION":"",n.iridescence?"#define USE_IRIDESCENCE":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaTest?"#define USE_ALPHATEST":"",n.alphaHash?"#define USE_ALPHAHASH":"",n.sheen?"#define USE_SHEEN":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.vertexTangents&&!1===n.flatShading?"#define USE_TANGENT":"",n.vertexColors||n.instancingColor||n.batchingColor?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.gradientMap?"#define USE_GRADIENTMAP":"",n.flatShading?"#define FLAT_SHADED":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+c:"",n.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",n.numLightProbes>0?"#define USE_LIGHT_PROBES":"",n.decodeVideoTexture?"#define DECODE_VIDEO_TEXTURE":"",n.decodeVideoTextureEmissive?"#define DECODE_VIDEO_TEXTURE_EMISSIVE":"",n.logarithmicDepthBuffer?"#define USE_LOGARITHMIC_DEPTH_BUFFER":"",n.reversedDepthBuffer?"#define USE_REVERSED_DEPTH_BUFFER":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",n.toneMapping!==D?"#define TONE_MAPPING":"",n.toneMapping!==D?Pn.tonemapping_pars_fragment:"",n.toneMapping!==D?Mi("toneMapping",n.toneMapping):"",n.dithering?"#define DITHERING":"",n.opaque?"#define OPAQUE":"",Pn.colorspace_pars_fragment,Ti("linearToOutputTexel",n.outputColorSpace),Ri(),n.useDepthPacking?"#define DEPTH_PACKING "+n.depthPacking:"","\n"].filter(Ai).join("\n")),s=Pi(s),s=bi(s,n),s=Ci(s,n),l=Pi(l),l=bi(l,n),l=Ci(l,n),s=Ii(s),l=Ii(l),!0!==n.isRawShaderMaterial&&(E="#version 300 es\n",g=[m,"#define attribute in","#define varying out","#define texture2D texture"].join("\n")+"\n"+g,v=["#define varying in",n.glslVersion===Z?"":"layout(location = 0) out highp vec4 pc_fragColor;",n.glslVersion===Z?"":"#define gl_FragColor pc_fragColor","#define gl_FragDepthEXT gl_FragDepth","#define texture2D texture","#define textureCube texture","#define texture2DProj textureProj","#define texture2DLodEXT textureLod","#define texture2DProjLodEXT textureProjLod","#define textureCubeLodEXT textureLod","#define texture2DGradEXT textureGrad","#define texture2DProjGradEXT textureProjGrad","#define textureCubeGradEXT textureGrad"].join("\n")+"\n"+v);const S=E+g+s,T=E+v+l,M=gi(i,i.VERTEX_SHADER,S),x=gi(i,i.FRAGMENT_SHADER,T);function R(t){if(e.debug.checkShaderErrors){const n=i.getProgramInfoLog(_)||"",r=i.getShaderInfoLog(M)||"",a=i.getShaderInfoLog(x)||"",o=n.trim(),s=r.trim(),l=a.trim();let c=!0,d=!0;if(!1===i.getProgramParameter(_,i.LINK_STATUS))if(c=!1,"function"==typeof e.debug.onShaderError)e.debug.onShaderError(i,_,M,x);else{const e=Si(i,M,"vertex"),n=Si(i,x,"fragment");console.error("THREE.WebGLProgram: Shader Error "+i.getError()+" - VALIDATE_STATUS "+i.getProgramParameter(_,i.VALIDATE_STATUS)+"\n\nMaterial Name: "+t.name+"\nMaterial Type: "+t.type+"\n\nProgram Info Log: "+o+"\n"+e+"\n"+n)}else""!==o?console.warn("THREE.WebGLProgram: Program Info Log:",o):""!==s&&""!==l||(d=!1);d&&(t.diagnostics={runnable:c,programLog:o,vertexShader:{log:s,prefix:g},fragmentShader:{log:l,prefix:v}})}i.deleteShader(M),i.deleteShader(x),A=new _i(i,_),b=function(e,t){const n={},r=e.getProgramParameter(t,e.ACTIVE_ATTRIBUTES);for(let i=0;i0,J=o.clearcoat>0,ee=o.dispersion>0,te=o.iridescence>0,ne=o.sheen>0,re=o.transmission>0,ie=Q&&!!o.anisotropyMap,ae=J&&!!o.clearcoatMap,oe=J&&!!o.clearcoatNormalMap,se=J&&!!o.clearcoatRoughnessMap,le=te&&!!o.iridescenceMap,ce=te&&!!o.iridescenceThicknessMap,de=ne&&!!o.sheenColorMap,ue=ne&&!!o.sheenRoughnessMap,_e=!!o.specularMap,ge=!!o.specularColorMap,ve=!!o.specularIntensityMap,Ee=re&&!!o.transmissionMap,Se=re&&!!o.thicknessMap,Te=!!o.gradientMap,Me=!!o.alphaMap,xe=o.alphaTest>0,Re=!!o.alphaHash,Ae=!!o.extensions;let be=D;o.toneMapped&&(null!==O&&!0!==O.isXRRenderTarget||(be=e.toneMapping));const Ce={shaderID:C,shaderType:o.type,shaderName:o.name,vertexShader:U,fragmentShader:w,defines:o.defines,customVertexShaderID:I,customFragmentShaderID:y,isRawShaderMaterial:!0===o.isRawShaderMaterial,glslVersion:o.glslVersion,precision:g,batching:G,batchingColor:G&&null!==T._colorsTexture,instancing:H,instancingColor:H&&null!==T.instanceColor,instancingMorph:H&&null!==T.morphTexture,supportsVertexTextures:_,outputColorSpace:null===O?e.outputColorSpace:!0===O.isXRRenderTarget?O.texture.colorSpace:F,alphaToCoverage:!!o.alphaToCoverage,map:V,matcap:z,envMap:k,envMapMode:k&&A.mapping,envMapCubeUVHeight:b,aoMap:W,lightMap:X,bumpMap:Y,normalMap:K,displacementMap:_&&q,emissiveMap:j,normalMapObjectSpace:K&&o.normalMapType===he,normalMapTangentSpace:K&&o.normalMapType===me,metalnessMap:Z,roughnessMap:$,anisotropy:Q,anisotropyMap:ie,clearcoat:J,clearcoatMap:ae,clearcoatNormalMap:oe,clearcoatRoughnessMap:se,dispersion:ee,iridescence:te,iridescenceMap:le,iridescenceThicknessMap:ce,sheen:ne,sheenColorMap:de,sheenRoughnessMap:ue,specularMap:_e,specularColorMap:ge,specularIntensityMap:ve,transmission:re,transmissionMap:Ee,thicknessMap:Se,gradientMap:Te,opaque:!1===o.transparent&&o.blending===pe&&!1===o.alphaToCoverage,alphaMap:Me,alphaTest:xe,alphaHash:Re,combine:o.combine,mapUv:V&&E(o.map.channel),aoMapUv:W&&E(o.aoMap.channel),lightMapUv:X&&E(o.lightMap.channel),bumpMapUv:Y&&E(o.bumpMap.channel),normalMapUv:K&&E(o.normalMap.channel),displacementMapUv:q&&E(o.displacementMap.channel),emissiveMapUv:j&&E(o.emissiveMap.channel),metalnessMapUv:Z&&E(o.metalnessMap.channel),roughnessMapUv:$&&E(o.roughnessMap.channel),anisotropyMapUv:ie&&E(o.anisotropyMap.channel),clearcoatMapUv:ae&&E(o.clearcoatMap.channel),clearcoatNormalMapUv:oe&&E(o.clearcoatNormalMap.channel),clearcoatRoughnessMapUv:se&&E(o.clearcoatRoughnessMap.channel),iridescenceMapUv:le&&E(o.iridescenceMap.channel),iridescenceThicknessMapUv:ce&&E(o.iridescenceThicknessMap.channel),sheenColorMapUv:de&&E(o.sheenColorMap.channel),sheenRoughnessMapUv:ue&&E(o.sheenRoughnessMap.channel),specularMapUv:_e&&E(o.specularMap.channel),specularColorMapUv:ge&&E(o.specularColorMap.channel),specularIntensityMapUv:ve&&E(o.specularIntensityMap.channel),transmissionMapUv:Ee&&E(o.transmissionMap.channel),thicknessMapUv:Se&&E(o.thicknessMap.channel),alphaMapUv:Me&&E(o.alphaMap.channel),vertexTangents:!!x.attributes.tangent&&(K||Q),vertexColors:o.vertexColors,vertexAlphas:!0===o.vertexColors&&!!x.attributes.color&&4===x.attributes.color.itemSize,pointsUvs:!0===T.isPoints&&!!x.attributes.uv&&(V||Me),fog:!!M,useFog:!0===o.fog,fogExp2:!!M&&M.isFogExp2,flatShading:!0===o.flatShading&&!1===o.wireframe,sizeAttenuation:!0===o.sizeAttenuation,logarithmicDepthBuffer:h,reversedDepthBuffer:B,skinning:!0===T.isSkinnedMesh,morphTargets:void 0!==x.morphAttributes.position,morphNormals:void 0!==x.morphAttributes.normal,morphColors:void 0!==x.morphAttributes.color,morphTargetsCount:P,morphTextureStride:N,numDirLights:l.directional.length,numPointLights:l.point.length,numSpotLights:l.spot.length,numSpotLightMaps:l.spotLightMap.length,numRectAreaLights:l.rectArea.length,numHemiLights:l.hemi.length,numDirLightShadows:l.directionalShadowMap.length,numPointLightShadows:l.pointShadowMap.length,numSpotLightShadows:l.spotShadowMap.length,numSpotLightShadowsWithMaps:l.numSpotLightShadowsWithMaps,numLightProbes:l.numLightProbes,numClippingPlanes:s.numPlanes,numClipIntersection:s.numIntersection,dithering:o.dithering,shadowMapEnabled:e.shadowMap.enabled&&f.length>0,shadowMapType:e.shadowMap.type,toneMapping:be,decodeVideoTexture:V&&!0===o.map.isVideoTexture&&p.getTransfer(o.map.colorSpace)===m,decodeVideoTextureEmissive:j&&!0===o.emissiveMap.isVideoTexture&&p.getTransfer(o.emissiveMap.colorSpace)===m,premultipliedAlpha:o.premultipliedAlpha,doubleSided:o.side===fe,flipSided:o.side===c,useDepthPacking:o.depthPacking>=0,depthPacking:o.depthPacking||0,index0AttributeName:o.index0AttributeName,extensionClipCullDistance:Ae&&!0===o.extensions.clipCullDistance&&r.has("WEBGL_clip_cull_distance"),extensionMultiDraw:(Ae&&!0===o.extensions.multiDraw||G)&&r.has("WEBGL_multi_draw"),rendererExtensionParallelShaderCompile:r.has("KHR_parallel_shader_compile"),customProgramCacheKey:o.customProgramCacheKey()};return Ce.vertexUv1s=u.has(1),Ce.vertexUv2s=u.has(2),Ce.vertexUv3s=u.has(3),u.clear(),Ce},getProgramCacheKey:function(t){const n=[];if(t.shaderID?n.push(t.shaderID):(n.push(t.customVertexShaderID),n.push(t.customFragmentShaderID)),void 0!==t.defines)for(const e in t.defines)n.push(e),n.push(t.defines[e]);return!1===t.isRawShaderMaterial&&(!function(e,t){e.push(t.precision),e.push(t.outputColorSpace),e.push(t.envMapMode),e.push(t.envMapCubeUVHeight),e.push(t.mapUv),e.push(t.alphaMapUv),e.push(t.lightMapUv),e.push(t.aoMapUv),e.push(t.bumpMapUv),e.push(t.normalMapUv),e.push(t.displacementMapUv),e.push(t.emissiveMapUv),e.push(t.metalnessMapUv),e.push(t.roughnessMapUv),e.push(t.anisotropyMapUv),e.push(t.clearcoatMapUv),e.push(t.clearcoatNormalMapUv),e.push(t.clearcoatRoughnessMapUv),e.push(t.iridescenceMapUv),e.push(t.iridescenceThicknessMapUv),e.push(t.sheenColorMapUv),e.push(t.sheenRoughnessMapUv),e.push(t.specularMapUv),e.push(t.specularColorMapUv),e.push(t.specularIntensityMapUv),e.push(t.transmissionMapUv),e.push(t.thicknessMapUv),e.push(t.combine),e.push(t.fogExp2),e.push(t.sizeAttenuation),e.push(t.morphTargetsCount),e.push(t.morphAttributeCount),e.push(t.numDirLights),e.push(t.numPointLights),e.push(t.numSpotLights),e.push(t.numSpotLightMaps),e.push(t.numHemiLights),e.push(t.numRectAreaLights),e.push(t.numDirLightShadows),e.push(t.numPointLightShadows),e.push(t.numSpotLightShadows),e.push(t.numSpotLightShadowsWithMaps),e.push(t.numLightProbes),e.push(t.shadowMapType),e.push(t.toneMapping),e.push(t.numClippingPlanes),e.push(t.numClipIntersection),e.push(t.depthPacking)}(n,t),function(e,t){l.disableAll(),t.supportsVertexTextures&&l.enable(0);t.instancing&&l.enable(1);t.instancingColor&&l.enable(2);t.instancingMorph&&l.enable(3);t.matcap&&l.enable(4);t.envMap&&l.enable(5);t.normalMapObjectSpace&&l.enable(6);t.normalMapTangentSpace&&l.enable(7);t.clearcoat&&l.enable(8);t.iridescence&&l.enable(9);t.alphaTest&&l.enable(10);t.vertexColors&&l.enable(11);t.vertexAlphas&&l.enable(12);t.vertexUv1s&&l.enable(13);t.vertexUv2s&&l.enable(14);t.vertexUv3s&&l.enable(15);t.vertexTangents&&l.enable(16);t.anisotropy&&l.enable(17);t.alphaHash&&l.enable(18);t.batching&&l.enable(19);t.dispersion&&l.enable(20);t.batchingColor&&l.enable(21);t.gradientMap&&l.enable(22);e.push(l.mask),l.disableAll(),t.fog&&l.enable(0);t.useFog&&l.enable(1);t.flatShading&&l.enable(2);t.logarithmicDepthBuffer&&l.enable(3);t.reversedDepthBuffer&&l.enable(4);t.skinning&&l.enable(5);t.morphTargets&&l.enable(6);t.morphNormals&&l.enable(7);t.morphColors&&l.enable(8);t.premultipliedAlpha&&l.enable(9);t.shadowMapEnabled&&l.enable(10);t.doubleSided&&l.enable(11);t.flipSided&&l.enable(12);t.useDepthPacking&&l.enable(13);t.dithering&&l.enable(14);t.transmission&&l.enable(15);t.sheen&&l.enable(16);t.opaque&&l.enable(17);t.pointsUvs&&l.enable(18);t.decodeVideoTexture&&l.enable(19);t.decodeVideoTextureEmissive&&l.enable(20);t.alphaToCoverage&&l.enable(21);e.push(l.mask)}(n,t),n.push(e.outputColorSpace)),n.push(t.customProgramCacheKey),n.join()},getUniforms:function(e){const t=v[e.type];let n;if(t){const e=Dn[t];n=ue.clone(e.uniforms)}else n=e.uniforms;return n},acquireProgram:function(t,n){let r;for(let e=0,t=f.length;e0?r.push(d):!0===o.transparent?i.push(d):n.push(d)},unshift:function(e,t,o,s,l,c){const d=a(e,t,o,s,l,c);o.transmission>0?r.unshift(d):!0===o.transparent?i.unshift(d):n.unshift(d)},finish:function(){for(let n=t,r=e.length;n1&&n.sort(e||zi),r.length>1&&r.sort(t||ki),i.length>1&&i.sort(t||ki)}}}function Xi(){let e=new WeakMap;return{get:function(t,n){const r=e.get(t);let i;return void 0===r?(i=new Wi,e.set(t,[i])):n>=r.length?(i=new Wi,r.push(i)):i=r[n],i},dispose:function(){e=new WeakMap}}}function Yi(){const e={};return{get:function(t){if(void 0!==e[t.id])return e[t.id];let r;switch(t.type){case"DirectionalLight":r={direction:new i,color:new n};break;case"SpotLight":r={position:new i,direction:new i,color:new n,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":r={position:new i,color:new n,distance:0,decay:0};break;case"HemisphereLight":r={direction:new i,skyColor:new n,groundColor:new n};break;case"RectAreaLight":r={color:new n,position:new i,halfWidth:new i,halfHeight:new i}}return e[t.id]=r,r}}}let Ki=0;function qi(e,t){return(t.castShadow?2:0)-(e.castShadow?2:0)+(t.map?1:0)-(e.map?1:0)}function ji(e){const n=new Yi,r=function(){const e={};return{get:function(n){if(void 0!==e[n.id])return e[n.id];let r;switch(n.type){case"DirectionalLight":case"SpotLight":r={shadowIntensity:1,shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new t};break;case"PointLight":r={shadowIntensity:1,shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new t,shadowCameraNear:1,shadowCameraFar:1e3}}return e[n.id]=r,r}}}(),a={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1,numSpotMaps:-1,numLightProbes:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotLightMap:[],spotShadow:[],spotShadowMap:[],spotLightMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[],numSpotLightShadowsWithMaps:0,numLightProbes:0};for(let e=0;e<9;e++)a.probe.push(new i);const o=new i,s=new f,l=new f;return{setup:function(t){let i=0,o=0,s=0;for(let e=0;e<9;e++)a.probe[e].set(0,0,0);let l=0,c=0,d=0,u=0,f=0,p=0,m=0,h=0,_=0,g=0,v=0;t.sort(qi);for(let e=0,E=t.length;e0&&(!0===e.has("OES_texture_float_linear")?(a.rectAreaLTC1=Un.LTC_FLOAT_1,a.rectAreaLTC2=Un.LTC_FLOAT_2):(a.rectAreaLTC1=Un.LTC_HALF_1,a.rectAreaLTC2=Un.LTC_HALF_2)),a.ambient[0]=i,a.ambient[1]=o,a.ambient[2]=s;const E=a.hash;E.directionalLength===l&&E.pointLength===c&&E.spotLength===d&&E.rectAreaLength===u&&E.hemiLength===f&&E.numDirectionalShadows===p&&E.numPointShadows===m&&E.numSpotShadows===h&&E.numSpotMaps===_&&E.numLightProbes===v||(a.directional.length=l,a.spot.length=d,a.rectArea.length=u,a.point.length=c,a.hemi.length=f,a.directionalShadow.length=p,a.directionalShadowMap.length=p,a.pointShadow.length=m,a.pointShadowMap.length=m,a.spotShadow.length=h,a.spotShadowMap.length=h,a.directionalShadowMatrix.length=p,a.pointShadowMatrix.length=m,a.spotLightMatrix.length=h+_-g,a.spotLightMap.length=_,a.numSpotLightShadowsWithMaps=g,a.numLightProbes=v,E.directionalLength=l,E.pointLength=c,E.spotLength=d,E.rectAreaLength=u,E.hemiLength=f,E.numDirectionalShadows=p,E.numPointShadows=m,E.numSpotShadows=h,E.numSpotMaps=_,E.numLightProbes=v,a.version=Ki++)},setupView:function(e,t){let n=0,r=0,i=0,c=0,d=0;const u=t.matrixWorldInverse;for(let t=0,f=e.length;t=i.length?(a=new Zi(e),i.push(a)):a=i[r],a},dispose:function(){t=new WeakMap}}}function Qi(e,n,r){let i=new ge;const a=new t,s=new t,d=new k,u=new ve({depthPacking:Ee}),f=new Se,p={},m=r.maxTextureSize,h={[_]:c,[c]:_,[fe]:fe},g=new l({defines:{VSM_SAMPLES:8},uniforms:{shadow_pass:{value:null},resolution:{value:new t},radius:{value:4}},vertexShader:"void main() {\n\tgl_Position = vec4( position, 1.0 );\n}",fragmentShader:"uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"}),v=g.clone();v.defines.HORIZONTAL_PASS=1;const E=new N;E.setAttribute("position",new O(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const S=new o(E,g),T=this;this.enabled=!1,this.autoUpdate=!0,this.needsUpdate=!1,this.type=$;let M=this.type;function x(t,r){const i=n.update(S);g.defines.VSM_SAMPLES!==t.blurSamples&&(g.defines.VSM_SAMPLES=t.blurSamples,v.defines.VSM_SAMPLES=t.blurSamples,g.needsUpdate=!0,v.needsUpdate=!0),null===t.mapPass&&(t.mapPass=new y(a.x,a.y)),g.uniforms.shadow_pass.value=t.map.texture,g.uniforms.resolution.value=t.mapSize,g.uniforms.radius.value=t.radius,e.setRenderTarget(t.mapPass),e.clear(),e.renderBufferDirect(r,null,i,g,S,null),v.uniforms.shadow_pass.value=t.mapPass.texture,v.uniforms.resolution.value=t.mapSize,v.uniforms.radius.value=t.radius,e.setRenderTarget(t.map),e.clear(),e.renderBufferDirect(r,null,i,v,S,null)}function R(t,n,r,i){let a=null;const o=!0===r.isPointLight?t.customDistanceMaterial:t.customDepthMaterial;if(void 0!==o)a=o;else if(a=!0===r.isPointLight?f:u,e.localClippingEnabled&&!0===n.clipShadows&&Array.isArray(n.clippingPlanes)&&0!==n.clippingPlanes.length||n.displacementMap&&0!==n.displacementScale||n.alphaMap&&n.alphaTest>0||n.map&&n.alphaTest>0||!0===n.alphaToCoverage){const e=a.uuid,t=n.uuid;let r=p[e];void 0===r&&(r={},p[e]=r);let i=r[t];void 0===i&&(i=a.clone(),r[t]=i,n.addEventListener("dispose",b)),a=i}if(a.visible=n.visible,a.wireframe=n.wireframe,a.side=i===J?null!==n.shadowSide?n.shadowSide:n.side:null!==n.shadowSide?n.shadowSide:h[n.side],a.alphaMap=n.alphaMap,a.alphaTest=!0===n.alphaToCoverage?.5:n.alphaTest,a.map=n.map,a.clipShadows=n.clipShadows,a.clippingPlanes=n.clippingPlanes,a.clipIntersection=n.clipIntersection,a.displacementMap=n.displacementMap,a.displacementScale=n.displacementScale,a.displacementBias=n.displacementBias,a.wireframeLinewidth=n.wireframeLinewidth,a.linewidth=n.linewidth,!0===r.isPointLight&&!0===a.isMeshDistanceMaterial){e.properties.get(a).light=r}return a}function A(t,r,a,o,s){if(!1===t.visible)return;if(t.layers.test(r.layers)&&(t.isMesh||t.isLine||t.isPoints)&&(t.castShadow||t.receiveShadow&&s===J)&&(!t.frustumCulled||i.intersectsObject(t))){t.modelViewMatrix.multiplyMatrices(a.matrixWorldInverse,t.matrixWorld);const i=n.update(t),l=t.material;if(Array.isArray(l)){const n=i.groups;for(let c=0,d=n.length;cm||a.y>m)&&(a.x>m&&(s.x=Math.floor(m/h.x),a.x=s.x*h.x,c.mapSize.x=s.x),a.y>m&&(s.y=Math.floor(m/h.y),a.y=s.y*h.y,c.mapSize.y=s.y)),null===c.map||!0===f||!0===p){const e=this.type!==J?{minFilter:Te,magFilter:Te}:{};null!==c.map&&c.map.dispose(),c.map=new y(a.x,a.y,e),c.map.texture.name=l.name+".shadowMap",c.camera.updateProjectionMatrix()}e.setRenderTarget(c.map),e.clear();const _=c.getViewportCount();for(let e=0;e<_;e++){const t=c.getViewport(e);d.set(s.x*t.x,s.y*t.y,s.x*t.z,s.y*t.w),u.viewport(d),c.updateMatrices(l,e),i=c.getFrustum(),A(n,r,c.camera,l,this.type)}!0!==c.isPointLightShadow&&this.type===J&&x(c,r),c.needsUpdate=!1}M=this.type,T.needsUpdate=!1,e.setRenderTarget(o,l,c)}}const Ji={[Ke]:Ye,[Xe]:ze,[We]:Ve,[Me]:ke,[Ye]:Ke,[ze]:Xe,[Ve]:We,[ke]:Me};function ea(e,t){const r=new function(){let t=!1;const n=new k;let r=null;const i=new k(0,0,0,0);return{setMask:function(n){r===n||t||(e.colorMask(n,n,n,n),r=n)},setLocked:function(e){t=e},setClear:function(t,r,a,o,s){!0===s&&(t*=o,r*=o,a*=o),n.set(t,r,a,o),!1===i.equals(n)&&(e.clearColor(t,r,a,o),i.copy(n))},reset:function(){t=!1,r=null,i.set(-1,0,0,0)}}},i=new function(){let n=!1,r=!1,i=null,a=null,o=null;return{setReversed:function(e){if(r!==e){const n=t.get("EXT_clip_control");e?n.clipControlEXT(n.LOWER_LEFT_EXT,n.ZERO_TO_ONE_EXT):n.clipControlEXT(n.LOWER_LEFT_EXT,n.NEGATIVE_ONE_TO_ONE_EXT),r=e;const i=o;o=null,this.setClear(i)}},getReversed:function(){return r},setTest:function(t){t?W(e.DEPTH_TEST):X(e.DEPTH_TEST)},setMask:function(t){i===t||n||(e.depthMask(t),i=t)},setFunc:function(t){if(r&&(t=Ji[t]),a!==t){switch(t){case Ke:e.depthFunc(e.NEVER);break;case Ye:e.depthFunc(e.ALWAYS);break;case Xe:e.depthFunc(e.LESS);break;case Me:e.depthFunc(e.LEQUAL);break;case We:e.depthFunc(e.EQUAL);break;case ke:e.depthFunc(e.GEQUAL);break;case ze:e.depthFunc(e.GREATER);break;case Ve:e.depthFunc(e.NOTEQUAL);break;default:e.depthFunc(e.LEQUAL)}a=t}},setLocked:function(e){n=e},setClear:function(t){o!==t&&(r&&(t=1-t),e.clearDepth(t),o=t)},reset:function(){n=!1,i=null,a=null,o=null,r=!1}}},a=new function(){let t=!1,n=null,r=null,i=null,a=null,o=null,s=null,l=null,c=null;return{setTest:function(n){t||(n?W(e.STENCIL_TEST):X(e.STENCIL_TEST))},setMask:function(r){n===r||t||(e.stencilMask(r),n=r)},setFunc:function(t,n,o){r===t&&i===n&&a===o||(e.stencilFunc(t,n,o),r=t,i=n,a=o)},setOp:function(t,n,r){o===t&&s===n&&l===r||(e.stencilOp(t,n,r),o=t,s=n,l=r)},setLocked:function(e){t=e},setClear:function(t){c!==t&&(e.clearStencil(t),c=t)},reset:function(){t=!1,n=null,r=null,i=null,a=null,o=null,s=null,l=null,c=null}}},o=new WeakMap,s=new WeakMap;let l={},d={},u=new WeakMap,f=[],p=null,m=!1,h=null,_=null,g=null,v=null,E=null,S=null,T=null,M=new n(0,0,0),x=0,R=!1,A=null,b=null,C=null,L=null,P=null;const U=e.getParameter(e.MAX_COMBINED_TEXTURE_IMAGE_UNITS);let D=!1,w=0;const y=e.getParameter(e.VERSION);-1!==y.indexOf("WebGL")?(w=parseFloat(/^WebGL (\d)/.exec(y)[1]),D=w>=1):-1!==y.indexOf("OpenGL ES")&&(w=parseFloat(/^OpenGL ES (\d)/.exec(y)[1]),D=w>=2);let N=null,O={};const F=e.getParameter(e.SCISSOR_BOX),B=e.getParameter(e.VIEWPORT),H=(new k).fromArray(F),G=(new k).fromArray(B);function V(t,n,r,i){const a=new Uint8Array(4),o=e.createTexture();e.bindTexture(t,o),e.texParameteri(t,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(t,e.TEXTURE_MAG_FILTER,e.NEAREST);for(let o=0;on||i.height>n)&&(r=n/Math.max(i.width,i.height)),r<1){if("undefined"!=typeof HTMLImageElement&&e instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&e instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&e instanceof ImageBitmap||"undefined"!=typeof VideoFrame&&e instanceof VideoFrame){const n=Math.floor(r*i.width),a=Math.floor(r*i.height);void 0===f&&(f=g(n,a));const o=t?g(n,a):f;o.width=n,o.height=a;return o.getContext("2d").drawImage(e,0,0,n,a),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+i.width+"x"+i.height+") to ("+n+"x"+a+")."),o}return"data"in e&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+i.width+"x"+i.height+")."),e}return e}function E(e){return e.generateMipmaps}function x(t){e.generateMipmap(t)}function R(t){return t.isWebGLCubeRenderTarget?e.TEXTURE_CUBE_MAP:t.isWebGL3DRenderTarget?e.TEXTURE_3D:t.isWebGLArrayRenderTarget||t.isCompressedArrayTexture?e.TEXTURE_2D_ARRAY:e.TEXTURE_2D}function A(t,r,i,a,o=!1){if(null!==t){if(void 0!==e[t])return e[t];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+t+"'")}let s=r;if(r===e.RED&&(i===e.FLOAT&&(s=e.R32F),i===e.HALF_FLOAT&&(s=e.R16F),i===e.UNSIGNED_BYTE&&(s=e.R8)),r===e.RED_INTEGER&&(i===e.UNSIGNED_BYTE&&(s=e.R8UI),i===e.UNSIGNED_SHORT&&(s=e.R16UI),i===e.UNSIGNED_INT&&(s=e.R32UI),i===e.BYTE&&(s=e.R8I),i===e.SHORT&&(s=e.R16I),i===e.INT&&(s=e.R32I)),r===e.RG&&(i===e.FLOAT&&(s=e.RG32F),i===e.HALF_FLOAT&&(s=e.RG16F),i===e.UNSIGNED_BYTE&&(s=e.RG8)),r===e.RG_INTEGER&&(i===e.UNSIGNED_BYTE&&(s=e.RG8UI),i===e.UNSIGNED_SHORT&&(s=e.RG16UI),i===e.UNSIGNED_INT&&(s=e.RG32UI),i===e.BYTE&&(s=e.RG8I),i===e.SHORT&&(s=e.RG16I),i===e.INT&&(s=e.RG32I)),r===e.RGB_INTEGER&&(i===e.UNSIGNED_BYTE&&(s=e.RGB8UI),i===e.UNSIGNED_SHORT&&(s=e.RGB16UI),i===e.UNSIGNED_INT&&(s=e.RGB32UI),i===e.BYTE&&(s=e.RGB8I),i===e.SHORT&&(s=e.RGB16I),i===e.INT&&(s=e.RGB32I)),r===e.RGBA_INTEGER&&(i===e.UNSIGNED_BYTE&&(s=e.RGBA8UI),i===e.UNSIGNED_SHORT&&(s=e.RGBA16UI),i===e.UNSIGNED_INT&&(s=e.RGBA32UI),i===e.BYTE&&(s=e.RGBA8I),i===e.SHORT&&(s=e.RGBA16I),i===e.INT&&(s=e.RGBA32I)),r===e.RGB&&(i===e.UNSIGNED_INT_5_9_9_9_REV&&(s=e.RGB9_E5),i===e.UNSIGNED_INT_10F_11F_11F_REV&&(s=e.R11F_G11F_B10F)),r===e.RGBA){const t=o?se:p.getTransfer(a);i===e.FLOAT&&(s=e.RGBA32F),i===e.HALF_FLOAT&&(s=e.RGBA16F),i===e.UNSIGNED_BYTE&&(s=t===m?e.SRGB8_ALPHA8:e.RGBA8),i===e.UNSIGNED_SHORT_4_4_4_4&&(s=e.RGBA4),i===e.UNSIGNED_SHORT_5_5_5_1&&(s=e.RGB5_A1)}return s!==e.R16F&&s!==e.R32F&&s!==e.RG16F&&s!==e.RG32F&&s!==e.RGBA16F&&s!==e.RGBA32F||n.get("EXT_color_buffer_float"),s}function b(t,n){let r;return t?null===n||n===Tt||n===Mt?r=e.DEPTH24_STENCIL8:n===T?r=e.DEPTH32F_STENCIL8:n===xt&&(r=e.DEPTH24_STENCIL8,console.warn("DepthTexture: 16 bit depth attachment is not supported with stencil. Using 24-bit attachment.")):null===n||n===Tt||n===Mt?r=e.DEPTH_COMPONENT24:n===T?r=e.DEPTH_COMPONENT32F:n===xt&&(r=e.DEPTH_COMPONENT16),r}function C(e,t){return!0===E(e)||e.isFramebufferTexture&&e.minFilter!==Te&&e.minFilter!==B?Math.log2(Math.max(t.width,t.height))+1:void 0!==e.mipmaps&&e.mipmaps.length>0?e.mipmaps.length:e.isCompressedTexture&&Array.isArray(e.image)?t.mipmaps.length:1}function L(e){const t=e.target;t.removeEventListener("dispose",L),function(e){const t=i.get(e);if(void 0===t.__webglInit)return;const n=e.source,r=h.get(n);if(r){const i=r[t.__cacheKey];i.usedTimes--,0===i.usedTimes&&U(e),0===Object.keys(r).length&&h.delete(n)}i.remove(e)}(t),t.isVideoTexture&&u.delete(t)}function P(t){const n=t.target;n.removeEventListener("dispose",P),function(t){const n=i.get(t);t.depthTexture&&(t.depthTexture.dispose(),i.remove(t.depthTexture));if(t.isWebGLCubeRenderTarget)for(let t=0;t<6;t++){if(Array.isArray(n.__webglFramebuffer[t]))for(let r=0;r0&&a.__version!==t.version){const e=t.image;if(null===e)console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");else{if(!1!==e.complete)return void V(a,t,n);console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete")}}else t.isExternalTexture&&(a.__webglTexture=t.sourceTexture?t.sourceTexture:null);r.bindTexture(e.TEXTURE_2D,a.__webglTexture,e.TEXTURE0+n)}const I={[at]:e.REPEAT,[it]:e.CLAMP_TO_EDGE,[rt]:e.MIRRORED_REPEAT},y={[Te]:e.NEAREST,[ct]:e.NEAREST_MIPMAP_NEAREST,[lt]:e.NEAREST_MIPMAP_LINEAR,[B]:e.LINEAR,[st]:e.LINEAR_MIPMAP_NEAREST,[ot]:e.LINEAR_MIPMAP_LINEAR},N={[_t]:e.NEVER,[ht]:e.ALWAYS,[mt]:e.LESS,[K]:e.LEQUAL,[pt]:e.EQUAL,[ft]:e.GEQUAL,[ut]:e.GREATER,[dt]:e.NOTEQUAL};function O(t,r){if(r.type!==T||!1!==n.has("OES_texture_float_linear")||r.magFilter!==B&&r.magFilter!==st&&r.magFilter!==lt&&r.magFilter!==ot&&r.minFilter!==B&&r.minFilter!==st&&r.minFilter!==lt&&r.minFilter!==ot||console.warn("THREE.WebGLRenderer: Unable to use linear filtering with floating point textures. OES_texture_float_linear not supported on this device."),e.texParameteri(t,e.TEXTURE_WRAP_S,I[r.wrapS]),e.texParameteri(t,e.TEXTURE_WRAP_T,I[r.wrapT]),t!==e.TEXTURE_3D&&t!==e.TEXTURE_2D_ARRAY||e.texParameteri(t,e.TEXTURE_WRAP_R,I[r.wrapR]),e.texParameteri(t,e.TEXTURE_MAG_FILTER,y[r.magFilter]),e.texParameteri(t,e.TEXTURE_MIN_FILTER,y[r.minFilter]),r.compareFunction&&(e.texParameteri(t,e.TEXTURE_COMPARE_MODE,e.COMPARE_REF_TO_TEXTURE),e.texParameteri(t,e.TEXTURE_COMPARE_FUNC,N[r.compareFunction])),!0===n.has("EXT_texture_filter_anisotropic")){if(r.magFilter===Te)return;if(r.minFilter!==lt&&r.minFilter!==ot)return;if(r.type===T&&!1===n.has("OES_texture_float_linear"))return;if(r.anisotropy>1||i.get(r).__currentAnisotropy){const o=n.get("EXT_texture_filter_anisotropic");e.texParameterf(t,o.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(r.anisotropy,a.getMaxAnisotropy())),i.get(r).__currentAnisotropy=r.anisotropy}}}function H(t,n){let r=!1;void 0===t.__webglInit&&(t.__webglInit=!0,n.addEventListener("dispose",L));const i=n.source;let a=h.get(i);void 0===a&&(a={},h.set(i,a));const o=function(e){const t=[];return t.push(e.wrapS),t.push(e.wrapT),t.push(e.wrapR||0),t.push(e.magFilter),t.push(e.minFilter),t.push(e.anisotropy),t.push(e.internalFormat),t.push(e.format),t.push(e.type),t.push(e.generateMipmaps),t.push(e.premultiplyAlpha),t.push(e.flipY),t.push(e.unpackAlignment),t.push(e.colorSpace),t.join()}(n);if(o!==t.__cacheKey){void 0===a[o]&&(a[o]={texture:e.createTexture(),usedTimes:0},s.memory.textures++,r=!0),a[o].usedTimes++;const i=a[t.__cacheKey];void 0!==i&&(a[t.__cacheKey].usedTimes--,0===i.usedTimes&&U(n)),t.__cacheKey=o,t.__webglTexture=a[o].texture}return r}function G(e,t,n){return Math.floor(Math.floor(e/n)/t)}function V(t,n,s){let l=e.TEXTURE_2D;(n.isDataArrayTexture||n.isCompressedArrayTexture)&&(l=e.TEXTURE_2D_ARRAY),n.isData3DTexture&&(l=e.TEXTURE_3D);const c=H(t,n),d=n.source;r.bindTexture(l,t.__webglTexture,e.TEXTURE0+s);const u=i.get(d);if(d.version!==u.__version||!0===c){r.activeTexture(e.TEXTURE0+s);const t=p.getPrimaries(p.workingColorSpace),i=n.colorSpace===gt?null:p.getPrimaries(n.colorSpace),f=n.colorSpace===gt||t===i?e.NONE:e.BROWSER_DEFAULT_WEBGL;e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,n.flipY),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,n.premultiplyAlpha),e.pixelStorei(e.UNPACK_ALIGNMENT,n.unpackAlignment),e.pixelStorei(e.UNPACK_COLORSPACE_CONVERSION_WEBGL,f);let m=v(n.image,!1,a.maxTextureSize);m=$(n,m);const h=o.convert(n.format,n.colorSpace),_=o.convert(n.type);let g,S=A(n.internalFormat,h,_,n.colorSpace,n.isVideoTexture);O(l,n);const T=n.mipmaps,R=!0!==n.isVideoTexture,L=void 0===u.__version||!0===c,P=d.dataReady,U=C(n,m);if(n.isDepthTexture)S=b(n.format===vt,n.type),L&&(R?r.texStorage2D(e.TEXTURE_2D,1,S,m.width,m.height):r.texImage2D(e.TEXTURE_2D,0,S,m.width,m.height,0,h,_,null));else if(n.isDataTexture)if(T.length>0){R&&L&&r.texStorage2D(e.TEXTURE_2D,U,S,T[0].width,T[0].height);for(let t=0,n=T.length;te.start-t.start);let s=0;for(let e=1;e0){const i=Et(g.width,g.height,n.format,n.type);for(const a of n.layerUpdates){const n=g.data.subarray(a*i/g.data.BYTES_PER_ELEMENT,(a+1)*i/g.data.BYTES_PER_ELEMENT);r.compressedTexSubImage3D(e.TEXTURE_2D_ARRAY,t,0,0,a,g.width,g.height,1,h,n)}n.clearLayerUpdates()}else r.compressedTexSubImage3D(e.TEXTURE_2D_ARRAY,t,0,0,0,g.width,g.height,m.depth,h,g.data)}else r.compressedTexImage3D(e.TEXTURE_2D_ARRAY,t,S,g.width,g.height,m.depth,0,g.data,0,0);else console.warn("THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()");else R?P&&r.texSubImage3D(e.TEXTURE_2D_ARRAY,t,0,0,0,g.width,g.height,m.depth,h,_,g.data):r.texImage3D(e.TEXTURE_2D_ARRAY,t,S,g.width,g.height,m.depth,0,h,_,g.data)}else{R&&L&&r.texStorage2D(e.TEXTURE_2D,U,S,T[0].width,T[0].height);for(let t=0,i=T.length;t0){const t=Et(m.width,m.height,n.format,n.type);for(const i of n.layerUpdates){const n=m.data.subarray(i*t/m.data.BYTES_PER_ELEMENT,(i+1)*t/m.data.BYTES_PER_ELEMENT);r.texSubImage3D(e.TEXTURE_2D_ARRAY,0,0,0,i,m.width,m.height,1,h,_,n)}n.clearLayerUpdates()}else r.texSubImage3D(e.TEXTURE_2D_ARRAY,0,0,0,0,m.width,m.height,m.depth,h,_,m.data)}else r.texImage3D(e.TEXTURE_2D_ARRAY,0,S,m.width,m.height,m.depth,0,h,_,m.data);else if(n.isData3DTexture)R?(L&&r.texStorage3D(e.TEXTURE_3D,U,S,m.width,m.height,m.depth),P&&r.texSubImage3D(e.TEXTURE_3D,0,0,0,0,m.width,m.height,m.depth,h,_,m.data)):r.texImage3D(e.TEXTURE_3D,0,S,m.width,m.height,m.depth,0,h,_,m.data);else if(n.isFramebufferTexture){if(L)if(R)r.texStorage2D(e.TEXTURE_2D,U,S,m.width,m.height);else{let t=m.width,n=m.height;for(let i=0;i>=1,n>>=1}}else if(T.length>0){if(R&&L){const t=Q(T[0]);r.texStorage2D(e.TEXTURE_2D,U,S,t.width,t.height)}for(let t=0,n=T.length;t>d),i=Math.max(1,n.height>>d);c===e.TEXTURE_3D||c===e.TEXTURE_2D_ARRAY?r.texImage3D(c,d,p,t,i,n.depth,0,u,f,null):r.texImage2D(c,d,p,t,i,0,u,f,null)}r.bindFramebuffer(e.FRAMEBUFFER,t),Z(n)?l.framebufferTexture2DMultisampleEXT(e.FRAMEBUFFER,s,c,h.__webglTexture,0,j(n)):(c===e.TEXTURE_2D||c>=e.TEXTURE_CUBE_MAP_POSITIVE_X&&c<=e.TEXTURE_CUBE_MAP_NEGATIVE_Z)&&e.framebufferTexture2D(e.FRAMEBUFFER,s,c,h.__webglTexture,d),r.bindFramebuffer(e.FRAMEBUFFER,null)}function k(t,n,r){if(e.bindRenderbuffer(e.RENDERBUFFER,t),n.depthBuffer){const i=n.depthTexture,a=i&&i.isDepthTexture?i.type:null,o=b(n.stencilBuffer,a),s=n.stencilBuffer?e.DEPTH_STENCIL_ATTACHMENT:e.DEPTH_ATTACHMENT,c=j(n);Z(n)?l.renderbufferStorageMultisampleEXT(e.RENDERBUFFER,c,o,n.width,n.height):r?e.renderbufferStorageMultisample(e.RENDERBUFFER,c,o,n.width,n.height):e.renderbufferStorage(e.RENDERBUFFER,o,n.width,n.height),e.framebufferRenderbuffer(e.FRAMEBUFFER,s,e.RENDERBUFFER,t)}else{const t=n.textures;for(let i=0;i{delete n.__boundDepthTexture,delete n.__depthDisposeCallback,e.removeEventListener("dispose",t)};e.addEventListener("dispose",t),n.__depthDisposeCallback=t}n.__boundDepthTexture=e}if(t.depthTexture&&!n.__autoAllocateDepthBuffer){if(a)throw new Error("target.depthTexture not supported in Cube render targets");const e=t.texture.mipmaps;e&&e.length>0?W(n.__webglFramebuffer[0],t):W(n.__webglFramebuffer,t)}else if(a){n.__webglDepthbuffer=[];for(let i=0;i<6;i++)if(r.bindFramebuffer(e.FRAMEBUFFER,n.__webglFramebuffer[i]),void 0===n.__webglDepthbuffer[i])n.__webglDepthbuffer[i]=e.createRenderbuffer(),k(n.__webglDepthbuffer[i],t,!1);else{const r=t.stencilBuffer?e.DEPTH_STENCIL_ATTACHMENT:e.DEPTH_ATTACHMENT,a=n.__webglDepthbuffer[i];e.bindRenderbuffer(e.RENDERBUFFER,a),e.framebufferRenderbuffer(e.FRAMEBUFFER,r,e.RENDERBUFFER,a)}}else{const i=t.texture.mipmaps;if(i&&i.length>0?r.bindFramebuffer(e.FRAMEBUFFER,n.__webglFramebuffer[0]):r.bindFramebuffer(e.FRAMEBUFFER,n.__webglFramebuffer),void 0===n.__webglDepthbuffer)n.__webglDepthbuffer=e.createRenderbuffer(),k(n.__webglDepthbuffer,t,!1);else{const r=t.stencilBuffer?e.DEPTH_STENCIL_ATTACHMENT:e.DEPTH_ATTACHMENT,i=n.__webglDepthbuffer;e.bindRenderbuffer(e.RENDERBUFFER,i),e.framebufferRenderbuffer(e.FRAMEBUFFER,r,e.RENDERBUFFER,i)}}r.bindFramebuffer(e.FRAMEBUFFER,null)}const Y=[],q=[];function j(e){return Math.min(a.maxSamples,e.samples)}function Z(e){const t=i.get(e);return e.samples>0&&!0===n.has("WEBGL_multisampled_render_to_texture")&&!1!==t.__useRenderToTexture}function $(e,t){const n=e.colorSpace,r=e.format,i=e.type;return!0===e.isCompressedTexture||!0===e.isVideoTexture||n!==F&&n!==gt&&(p.getTransfer(n)===m?r===M&&i===S||console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType."):console.error("THREE.WebGLTextures: Unsupported texture color space:",n)),t}function Q(e){return"undefined"!=typeof HTMLImageElement&&e instanceof HTMLImageElement?(d.width=e.naturalWidth||e.width,d.height=e.naturalHeight||e.height):"undefined"!=typeof VideoFrame&&e instanceof VideoFrame?(d.width=e.displayWidth,d.height=e.displayHeight):(d.width=e.width,d.height=e.height),d}this.allocateTextureUnit=function(){const e=D;return e>=a.maxTextures&&console.warn("THREE.WebGLTextures: Trying to use "+e+" texture units while this GPU supports only "+a.maxTextures),D+=1,e},this.resetTextureUnits=function(){D=0},this.setTexture2D=w,this.setTexture2DArray=function(t,n){const a=i.get(t);!1===t.isRenderTargetTexture&&t.version>0&&a.__version!==t.version?V(a,t,n):r.bindTexture(e.TEXTURE_2D_ARRAY,a.__webglTexture,e.TEXTURE0+n)},this.setTexture3D=function(t,n){const a=i.get(t);!1===t.isRenderTargetTexture&&t.version>0&&a.__version!==t.version?V(a,t,n):r.bindTexture(e.TEXTURE_3D,a.__webglTexture,e.TEXTURE0+n)},this.setTextureCube=function(t,n){const s=i.get(t);t.version>0&&s.__version!==t.version?function(t,n,s){if(6!==n.image.length)return;const l=H(t,n),c=n.source;r.bindTexture(e.TEXTURE_CUBE_MAP,t.__webglTexture,e.TEXTURE0+s);const d=i.get(c);if(c.version!==d.__version||!0===l){r.activeTexture(e.TEXTURE0+s);const t=p.getPrimaries(p.workingColorSpace),i=n.colorSpace===gt?null:p.getPrimaries(n.colorSpace),u=n.colorSpace===gt||t===i?e.NONE:e.BROWSER_DEFAULT_WEBGL;e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,n.flipY),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,n.premultiplyAlpha),e.pixelStorei(e.UNPACK_ALIGNMENT,n.unpackAlignment),e.pixelStorei(e.UNPACK_COLORSPACE_CONVERSION_WEBGL,u);const f=n.isCompressedTexture||n.image[0].isCompressedTexture,m=n.image[0]&&n.image[0].isDataTexture,h=[];for(let e=0;e<6;e++)h[e]=f||m?m?n.image[e].image:n.image[e]:v(n.image[e],!0,a.maxCubemapSize),h[e]=$(n,h[e]);const _=h[0],g=o.convert(n.format,n.colorSpace),S=o.convert(n.type),T=A(n.internalFormat,g,S,n.colorSpace),R=!0!==n.isVideoTexture,b=void 0===d.__version||!0===l,L=c.dataReady;let P,U=C(n,_);if(O(e.TEXTURE_CUBE_MAP,n),f){R&&b&&r.texStorage2D(e.TEXTURE_CUBE_MAP,U,T,_.width,_.height);for(let t=0;t<6;t++){P=h[t].mipmaps;for(let i=0;i0&&U++;const t=Q(h[0]);r.texStorage2D(e.TEXTURE_CUBE_MAP,U,T,t.width,t.height)}for(let t=0;t<6;t++)if(m){R?L&&r.texSubImage2D(e.TEXTURE_CUBE_MAP_POSITIVE_X+t,0,0,0,h[t].width,h[t].height,g,S,h[t].data):r.texImage2D(e.TEXTURE_CUBE_MAP_POSITIVE_X+t,0,T,h[t].width,h[t].height,0,g,S,h[t].data);for(let n=0;n1;if(u||(void 0===l.__webglTexture&&(l.__webglTexture=e.createTexture()),l.__version=n.version,s.memory.textures++),d){a.__webglFramebuffer=[];for(let t=0;t<6;t++)if(n.mipmaps&&n.mipmaps.length>0){a.__webglFramebuffer[t]=[];for(let r=0;r0){a.__webglFramebuffer=[];for(let t=0;t0&&!1===Z(t)){a.__webglMultisampledFramebuffer=e.createFramebuffer(),a.__webglColorRenderbuffer=[],r.bindFramebuffer(e.FRAMEBUFFER,a.__webglMultisampledFramebuffer);for(let n=0;n0)for(let i=0;i0)for(let r=0;r0)if(!1===Z(t)){const n=t.textures,a=t.width,o=t.height;let s=e.COLOR_BUFFER_BIT;const l=t.stencilBuffer?e.DEPTH_STENCIL_ATTACHMENT:e.DEPTH_ATTACHMENT,d=i.get(t),u=n.length>1;if(u)for(let t=0;t0?r.bindFramebuffer(e.DRAW_FRAMEBUFFER,d.__webglFramebuffer[0]):r.bindFramebuffer(e.DRAW_FRAMEBUFFER,d.__webglFramebuffer);for(let r=0;r= 1.0 ) {\n\n\t\tgl_FragDepth = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r;\n\n\t} else {\n\n\t\tgl_FragDepth = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r;\n\n\t}\n\n}",uniforms:{depthColor:{value:this.texture},depthWidth:{value:t.z},depthHeight:{value:t.w}}});this.mesh=new o(new h(20,20),n)}return this.mesh}reset(){this.texture=null,this.mesh=null}getDepthTexture(){return this.texture}}class ia extends vn{constructor(e,n){super();const r=this;let a=null,o=1,s=null,l="local-floor",c=1,d=null,u=null,f=null,p=null,m=null,h=null;const _="undefined"!=typeof XRWebGLBinding,g=new ra,v={},E=n.getContextAttributes();let T=null,x=null;const R=[],A=[],b=new t;let C=null;const L=new U;L.viewport=new k;const P=new U;P.viewport=new k;const D=[L,P],w=new En;let I=null,N=null;function O(e){const t=A.indexOf(e.inputSource);if(-1===t)return;const n=R[t];void 0!==n&&(n.update(e.inputSource,e.frame,d||s),n.dispatchEvent({type:e.type,data:e.inputSource}))}function F(){a.removeEventListener("select",O),a.removeEventListener("selectstart",O),a.removeEventListener("selectend",O),a.removeEventListener("squeeze",O),a.removeEventListener("squeezestart",O),a.removeEventListener("squeezeend",O),a.removeEventListener("end",F),a.removeEventListener("inputsourceschange",B);for(let e=0;e=0&&(A[r]=null,R[r].disconnect(n))}for(let t=0;t=A.length){A.push(n),r=e;break}if(null===A[e]){A[e]=n,r=e;break}}if(-1===r)break}const i=R[r];i&&i.connect(n)}}this.cameraAutoUpdate=!0,this.enabled=!1,this.isPresenting=!1,this.getController=function(e){let t=R[e];return void 0===t&&(t=new Sn,R[e]=t),t.getTargetRaySpace()},this.getControllerGrip=function(e){let t=R[e];return void 0===t&&(t=new Sn,R[e]=t),t.getGripSpace()},this.getHand=function(e){let t=R[e];return void 0===t&&(t=new Sn,R[e]=t),t.getHandSpace()},this.setFramebufferScaleFactor=function(e){o=e,!0===r.isPresenting&&console.warn("THREE.WebXRManager: Cannot change framebuffer scale while presenting.")},this.setReferenceSpaceType=function(e){l=e,!0===r.isPresenting&&console.warn("THREE.WebXRManager: Cannot change reference space type while presenting.")},this.getReferenceSpace=function(){return d||s},this.setReferenceSpace=function(e){d=e},this.getBaseLayer=function(){return null!==p?p:m},this.getBinding=function(){return null===f&&_&&(f=new XRWebGLBinding(a,n)),f},this.getFrame=function(){return h},this.getSession=function(){return a},this.setSession=async function(t){if(a=t,null!==a){T=e.getRenderTarget(),a.addEventListener("select",O),a.addEventListener("selectstart",O),a.addEventListener("selectend",O),a.addEventListener("squeeze",O),a.addEventListener("squeezestart",O),a.addEventListener("squeezeend",O),a.addEventListener("end",F),a.addEventListener("inputsourceschange",B),!0!==E.xrCompatible&&await n.makeXRCompatible(),C=e.getPixelRatio(),e.getSize(b);if(_&&"createProjectionLayer"in XRWebGLBinding.prototype){let t=null,r=null,i=null;E.depth&&(i=E.stencil?n.DEPTH24_STENCIL8:n.DEPTH_COMPONENT24,t=E.stencil?vt:St,r=E.stencil?Mt:Tt);const s={colorFormat:n.RGBA8,depthFormat:i,scaleFactor:o};f=this.getBinding(),p=f.createProjectionLayer(s),a.updateRenderState({layers:[p]}),e.setPixelRatio(1),e.setSize(p.textureWidth,p.textureHeight,!1),x=new y(p.textureWidth,p.textureHeight,{format:M,type:S,depthTexture:new q(p.textureWidth,p.textureHeight,r,void 0,void 0,void 0,void 0,void 0,void 0,t),stencilBuffer:E.stencil,colorSpace:e.outputColorSpace,samples:E.antialias?4:0,resolveDepthBuffer:!1===p.ignoreDepthValues,resolveStencilBuffer:!1===p.ignoreDepthValues})}else{const t={antialias:E.antialias,alpha:!0,depth:E.depth,stencil:E.stencil,framebufferScaleFactor:o};m=new XRWebGLLayer(a,n,t),a.updateRenderState({baseLayer:m}),e.setPixelRatio(1),e.setSize(m.framebufferWidth,m.framebufferHeight,!1),x=new y(m.framebufferWidth,m.framebufferHeight,{format:M,type:S,colorSpace:e.outputColorSpace,stencilBuffer:E.stencil,resolveDepthBuffer:!1===m.ignoreDepthValues,resolveStencilBuffer:!1===m.ignoreDepthValues})}x.isXRRenderTarget=!0,this.setFoveation(c),d=null,s=await a.requestReferenceSpace(l),W.setContext(a),W.start(),r.isPresenting=!0,r.dispatchEvent({type:"sessionstart"})}},this.getEnvironmentBlendMode=function(){if(null!==a)return a.environmentBlendMode},this.getDepthTexture=function(){return g.getDepthTexture()};const H=new i,G=new i;function V(e,t){null===t?e.matrixWorld.copy(e.matrix):e.matrixWorld.multiplyMatrices(t.matrixWorld,e.matrix),e.matrixWorldInverse.copy(e.matrixWorld).invert()}this.updateCamera=function(e){if(null===a)return;let t=e.near,n=e.far;null!==g.texture&&(g.depthNear>0&&(t=g.depthNear),g.depthFar>0&&(n=g.depthFar)),w.near=P.near=L.near=t,w.far=P.far=L.far=n,I===w.near&&N===w.far||(a.updateRenderState({depthNear:w.near,depthFar:w.far}),I=w.near,N=w.far),w.layers.mask=6|e.layers.mask,L.layers.mask=3&w.layers.mask,P.layers.mask=5&w.layers.mask;const r=e.parent,i=w.cameras;V(w,r);for(let e=0;e0&&(e.alphaTest.value=r.alphaTest);const i=t.get(r),a=i.envMap,o=i.envMapRotation;a&&(e.envMap.value=a,aa.copy(o),aa.x*=-1,aa.y*=-1,aa.z*=-1,a.isCubeTexture&&!1===a.isRenderTargetTexture&&(aa.y*=-1,aa.z*=-1),e.envMapRotation.value.setFromMatrix4(oa.makeRotationFromEuler(aa)),e.flipEnvMap.value=a.isCubeTexture&&!1===a.isRenderTargetTexture?-1:1,e.reflectivity.value=r.reflectivity,e.ior.value=r.ior,e.refractionRatio.value=r.refractionRatio),r.lightMap&&(e.lightMap.value=r.lightMap,e.lightMapIntensity.value=r.lightMapIntensity,n(r.lightMap,e.lightMapTransform)),r.aoMap&&(e.aoMap.value=r.aoMap,e.aoMapIntensity.value=r.aoMapIntensity,n(r.aoMap,e.aoMapTransform))}return{refreshFogUniforms:function(t,n){n.color.getRGB(t.fogColor.value,g(e)),n.isFog?(t.fogNear.value=n.near,t.fogFar.value=n.far):n.isFogExp2&&(t.fogDensity.value=n.density)},refreshMaterialUniforms:function(e,i,a,o,s){i.isMeshBasicMaterial||i.isMeshLambertMaterial?r(e,i):i.isMeshToonMaterial?(r(e,i),function(e,t){t.gradientMap&&(e.gradientMap.value=t.gradientMap)}(e,i)):i.isMeshPhongMaterial?(r(e,i),function(e,t){e.specular.value.copy(t.specular),e.shininess.value=Math.max(t.shininess,1e-4)}(e,i)):i.isMeshStandardMaterial?(r(e,i),function(e,t){e.metalness.value=t.metalness,t.metalnessMap&&(e.metalnessMap.value=t.metalnessMap,n(t.metalnessMap,e.metalnessMapTransform));e.roughness.value=t.roughness,t.roughnessMap&&(e.roughnessMap.value=t.roughnessMap,n(t.roughnessMap,e.roughnessMapTransform));t.envMap&&(e.envMapIntensity.value=t.envMapIntensity)}(e,i),i.isMeshPhysicalMaterial&&function(e,t,r){e.ior.value=t.ior,t.sheen>0&&(e.sheenColor.value.copy(t.sheenColor).multiplyScalar(t.sheen),e.sheenRoughness.value=t.sheenRoughness,t.sheenColorMap&&(e.sheenColorMap.value=t.sheenColorMap,n(t.sheenColorMap,e.sheenColorMapTransform)),t.sheenRoughnessMap&&(e.sheenRoughnessMap.value=t.sheenRoughnessMap,n(t.sheenRoughnessMap,e.sheenRoughnessMapTransform)));t.clearcoat>0&&(e.clearcoat.value=t.clearcoat,e.clearcoatRoughness.value=t.clearcoatRoughness,t.clearcoatMap&&(e.clearcoatMap.value=t.clearcoatMap,n(t.clearcoatMap,e.clearcoatMapTransform)),t.clearcoatRoughnessMap&&(e.clearcoatRoughnessMap.value=t.clearcoatRoughnessMap,n(t.clearcoatRoughnessMap,e.clearcoatRoughnessMapTransform)),t.clearcoatNormalMap&&(e.clearcoatNormalMap.value=t.clearcoatNormalMap,n(t.clearcoatNormalMap,e.clearcoatNormalMapTransform),e.clearcoatNormalScale.value.copy(t.clearcoatNormalScale),t.side===c&&e.clearcoatNormalScale.value.negate()));t.dispersion>0&&(e.dispersion.value=t.dispersion);t.iridescence>0&&(e.iridescence.value=t.iridescence,e.iridescenceIOR.value=t.iridescenceIOR,e.iridescenceThicknessMinimum.value=t.iridescenceThicknessRange[0],e.iridescenceThicknessMaximum.value=t.iridescenceThicknessRange[1],t.iridescenceMap&&(e.iridescenceMap.value=t.iridescenceMap,n(t.iridescenceMap,e.iridescenceMapTransform)),t.iridescenceThicknessMap&&(e.iridescenceThicknessMap.value=t.iridescenceThicknessMap,n(t.iridescenceThicknessMap,e.iridescenceThicknessMapTransform)));t.transmission>0&&(e.transmission.value=t.transmission,e.transmissionSamplerMap.value=r.texture,e.transmissionSamplerSize.value.set(r.width,r.height),t.transmissionMap&&(e.transmissionMap.value=t.transmissionMap,n(t.transmissionMap,e.transmissionMapTransform)),e.thickness.value=t.thickness,t.thicknessMap&&(e.thicknessMap.value=t.thicknessMap,n(t.thicknessMap,e.thicknessMapTransform)),e.attenuationDistance.value=t.attenuationDistance,e.attenuationColor.value.copy(t.attenuationColor));t.anisotropy>0&&(e.anisotropyVector.value.set(t.anisotropy*Math.cos(t.anisotropyRotation),t.anisotropy*Math.sin(t.anisotropyRotation)),t.anisotropyMap&&(e.anisotropyMap.value=t.anisotropyMap,n(t.anisotropyMap,e.anisotropyMapTransform)));e.specularIntensity.value=t.specularIntensity,e.specularColor.value.copy(t.specularColor),t.specularColorMap&&(e.specularColorMap.value=t.specularColorMap,n(t.specularColorMap,e.specularColorMapTransform));t.specularIntensityMap&&(e.specularIntensityMap.value=t.specularIntensityMap,n(t.specularIntensityMap,e.specularIntensityMapTransform))}(e,i,s)):i.isMeshMatcapMaterial?(r(e,i),function(e,t){t.matcap&&(e.matcap.value=t.matcap)}(e,i)):i.isMeshDepthMaterial?r(e,i):i.isMeshDistanceMaterial?(r(e,i),function(e,n){const r=t.get(n).light;e.referencePosition.value.setFromMatrixPosition(r.matrixWorld),e.nearDistance.value=r.shadow.camera.near,e.farDistance.value=r.shadow.camera.far}(e,i)):i.isMeshNormalMaterial?r(e,i):i.isLineBasicMaterial?(function(e,t){e.diffuse.value.copy(t.color),e.opacity.value=t.opacity,t.map&&(e.map.value=t.map,n(t.map,e.mapTransform))}(e,i),i.isLineDashedMaterial&&function(e,t){e.dashSize.value=t.dashSize,e.totalSize.value=t.dashSize+t.gapSize,e.scale.value=t.scale}(e,i)):i.isPointsMaterial?function(e,t,r,i){e.diffuse.value.copy(t.color),e.opacity.value=t.opacity,e.size.value=t.size*r,e.scale.value=.5*i,t.map&&(e.map.value=t.map,n(t.map,e.uvTransform));t.alphaMap&&(e.alphaMap.value=t.alphaMap,n(t.alphaMap,e.alphaMapTransform));t.alphaTest>0&&(e.alphaTest.value=t.alphaTest)}(e,i,a,o):i.isSpriteMaterial?function(e,t){e.diffuse.value.copy(t.color),e.opacity.value=t.opacity,e.rotation.value=t.rotation,t.map&&(e.map.value=t.map,n(t.map,e.mapTransform));t.alphaMap&&(e.alphaMap.value=t.alphaMap,n(t.alphaMap,e.alphaMapTransform));t.alphaTest>0&&(e.alphaTest.value=t.alphaTest)}(e,i):i.isShadowMaterial?(e.color.value.copy(i.color),e.opacity.value=i.opacity):i.isShaderMaterial&&(i.uniformsNeedUpdate=!1)}}}function la(e,t,n,r){let i={},a={},o=[];const s=e.getParameter(e.MAX_UNIFORM_BUFFER_BINDINGS);function l(e,t,n,r){const i=e.value,a=t+"_"+n;if(void 0===r[a])return r[a]="number"==typeof i||"boolean"==typeof i?i:i.clone(),!0;{const e=r[a];if("number"==typeof i||"boolean"==typeof i){if(e!==i)return r[a]=i,!0}else if(!1===e.equals(i))return e.copy(i),!0}return!1}function c(e){const t={boundary:0,storage:0};return"number"==typeof e||"boolean"==typeof e?(t.boundary=4,t.storage=4):e.isVector2?(t.boundary=8,t.storage=8):e.isVector3||e.isColor?(t.boundary=16,t.storage=12):e.isVector4?(t.boundary=16,t.storage=16):e.isMatrix3?(t.boundary=48,t.storage=48):e.isMatrix4?(t.boundary=64,t.storage=64):e.isTexture?console.warn("THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group."):console.warn("THREE.WebGLRenderer: Unsupported uniform value type.",e),t}function d(t){const n=t.target;n.removeEventListener("dispose",d);const r=o.indexOf(n.__bindingPointIndex);o.splice(r,1),e.deleteBuffer(i[n.id]),delete i[n.id],delete a[n.id]}return{bind:function(e,t){const n=t.program;r.uniformBlockBinding(e,n)},update:function(n,u){let f=i[n.id];void 0===f&&(!function(e){const t=e.uniforms;let n=0;const r=16;for(let e=0,i=t.length;e0&&(n+=r-i);e.__size=n,e.__cache={}}(n),f=function(t){const n=function(){for(let e=0;e0),u=!!n.morphAttributes.position,f=!!n.morphAttributes.normal,p=!!n.morphAttributes.color;let m=D;r.toneMapped&&(null!==w&&!0!==w.isXRRenderTarget||(m=C.toneMapping));const h=n.morphAttributes.position||n.morphAttributes.normal||n.morphAttributes.color,_=void 0!==h?h.length:0,g=ue.get(r),v=R.state.lights;if(!0===J&&(!0===ee||e!==N)){const t=e===N&&r.id===I;Re.setState(r,e,t)}let E=!1;r.version===g.__version?g.needsLights&&g.lightsStateVersion!==v.state.version||g.outputColorSpace!==s||i.isBatchedMesh&&!1===g.batching?E=!0:i.isBatchedMesh||!0!==g.batching?i.isBatchedMesh&&!0===g.batchingColor&&null===i.colorTexture||i.isBatchedMesh&&!1===g.batchingColor&&null!==i.colorTexture||i.isInstancedMesh&&!1===g.instancing?E=!0:i.isInstancedMesh||!0!==g.instancing?i.isSkinnedMesh&&!1===g.skinning?E=!0:i.isSkinnedMesh||!0!==g.skinning?i.isInstancedMesh&&!0===g.instancingColor&&null===i.instanceColor||i.isInstancedMesh&&!1===g.instancingColor&&null!==i.instanceColor||i.isInstancedMesh&&!0===g.instancingMorph&&null===i.morphTexture||i.isInstancedMesh&&!1===g.instancingMorph&&null!==i.morphTexture||g.envMap!==l||!0===r.fog&&g.fog!==a?E=!0:void 0===g.numClippingPlanes||g.numClippingPlanes===Re.numPlanes&&g.numIntersection===Re.numIntersection?(g.vertexAlphas!==c||g.vertexTangents!==d||g.morphTargets!==u||g.morphNormals!==f||g.morphColors!==p||g.toneMapping!==m||g.morphTargetsCount!==_)&&(E=!0):E=!0:E=!0:E=!0:E=!0:(E=!0,g.__version=r.version);let S=g.currentProgram;!0===E&&(S=$e(r,t,i));let T=!1,M=!1,x=!1;const A=S.getUniforms(),b=g.uniforms;ce.useProgram(S.program)&&(T=!0,M=!0,x=!0);r.id!==I&&(I=r.id,M=!0);if(T||N!==e){ce.buffers.depth.getReversed()&&!0!==e.reversedDepth&&(e._reversedDepth=!0,e.updateProjectionMatrix()),A.setValue(Ie,"projectionMatrix",e.projectionMatrix),A.setValue(Ie,"viewMatrix",e.matrixWorldInverse);const t=A.map.cameraPosition;void 0!==t&&t.setValue(Ie,ne.setFromMatrixPosition(e.matrixWorld)),le.logarithmicDepthBuffer&&A.setValue(Ie,"logDepthBufFC",2/(Math.log(e.far+1)/Math.LN2)),(r.isMeshPhongMaterial||r.isMeshToonMaterial||r.isMeshLambertMaterial||r.isMeshBasicMaterial||r.isMeshStandardMaterial||r.isShaderMaterial)&&A.setValue(Ie,"isOrthographic",!0===e.isOrthographicCamera),N!==e&&(N=e,M=!0,x=!0)}if(i.isSkinnedMesh){A.setOptional(Ie,i,"bindMatrix"),A.setOptional(Ie,i,"bindMatrixInverse");const e=i.skeleton;e&&(null===e.boneTexture&&e.computeBoneTexture(),A.setValue(Ie,"boneTexture",e.boneTexture,pe))}i.isBatchedMesh&&(A.setOptional(Ie,i,"batchingTexture"),A.setValue(Ie,"batchingTexture",i._matricesTexture,pe),A.setOptional(Ie,i,"batchingIdTexture"),A.setValue(Ie,"batchingIdTexture",i._indirectTexture,pe),A.setOptional(Ie,i,"batchingColorTexture"),null!==i._colorsTexture&&A.setValue(Ie,"batchingColorTexture",i._colorsTexture,pe));const L=n.morphAttributes;void 0===L.position&&void 0===L.normal&&void 0===L.color||Ce.update(i,n,S);(M||g.receiveShadow!==i.receiveShadow)&&(g.receiveShadow=i.receiveShadow,A.setValue(Ie,"receiveShadow",i.receiveShadow));r.isMeshGouraudMaterial&&null!==r.envMap&&(b.envMap.value=l,b.flipEnvMap.value=l.isCubeTexture&&!1===l.isRenderTargetTexture?-1:1);r.isMeshStandardMaterial&&null===r.envMap&&null!==t.environment&&(b.envMapIntensity.value=t.environmentIntensity);M&&(A.setValue(Ie,"toneMappingExposure",C.toneMappingExposure),g.needsLights&&(U=x,(P=b).ambientLightColor.needsUpdate=U,P.lightProbe.needsUpdate=U,P.directionalLights.needsUpdate=U,P.directionalLightShadows.needsUpdate=U,P.pointLights.needsUpdate=U,P.pointLightShadows.needsUpdate=U,P.spotLights.needsUpdate=U,P.spotLightShadows.needsUpdate=U,P.rectAreaLights.needsUpdate=U,P.hemisphereLights.needsUpdate=U),a&&!0===r.fog&&Te.refreshFogUniforms(b,a),Te.refreshMaterialUniforms(b,r,Y,X,R.state.transmissionRenderTarget[e.id]),_i.upload(Ie,Qe(g),b,pe));var P,U;r.isShaderMaterial&&!0===r.uniformsNeedUpdate&&(_i.upload(Ie,Qe(g),b,pe),r.uniformsNeedUpdate=!1);r.isSpriteMaterial&&A.setValue(Ie,"center",i.center);if(A.setValue(Ie,"modelViewMatrix",i.modelViewMatrix),A.setValue(Ie,"normalMatrix",i.normalMatrix),A.setValue(Ie,"modelMatrix",i.matrixWorld),r.isShaderMaterial||r.isRawShaderMaterial){const e=r.uniformsGroups;for(let t=0,n=e.length;t{function n(){r.forEach(function(e){ue.get(e).currentProgram.isReady()&&r.delete(e)}),0!==r.size?setTimeout(n,10):t(e)}null!==se.get("KHR_parallel_shader_compile")?n():setTimeout(n,10)})};let ze=null;function ke(){Xe.stop()}function We(){Xe.start()}const Xe=new Cn;function Ye(e,t,n,r){if(!1===e.visible)return;if(e.layers.test(t.layers))if(e.isGroup)n=e.renderOrder;else if(e.isLOD)!0===e.autoUpdate&&e.update(t);else if(e.isLight)R.pushLight(e),e.castShadow&&R.pushShadow(e);else if(e.isSprite){if(!e.frustumCulled||Q.intersectsSprite(e)){r&&re.setFromMatrixPosition(e.matrixWorld).applyMatrix4(te);const t=Ee.update(e),i=e.material;i.visible&&x.push(e,t,i,n,re.z,null)}}else if((e.isMesh||e.isLine||e.isPoints)&&(!e.frustumCulled||Q.intersectsObject(e))){const t=Ee.update(e),i=e.material;if(r&&(void 0!==e.boundingSphere?(null===e.boundingSphere&&e.computeBoundingSphere(),re.copy(e.boundingSphere.center)):(null===t.boundingSphere&&t.computeBoundingSphere(),re.copy(t.boundingSphere.center)),re.applyMatrix4(e.matrixWorld).applyMatrix4(te)),Array.isArray(i)){const r=t.groups;for(let a=0,o=r.length;a0&&je(i,t,n),a.length>0&&je(a,t,n),o.length>0&&je(o,t,n),ce.buffers.depth.setTest(!0),ce.buffers.depth.setMask(!0),ce.buffers.color.setMask(!0),ce.setPolygonOffset(!1)}function qe(e,t,n,r){if(null!==(!0===n.isScene?n.overrideMaterial:null))return;void 0===R.state.transmissionRenderTarget[r.id]&&(R.state.transmissionRenderTarget[r.id]=new y(1,1,{generateMipmaps:!0,type:se.has("EXT_color_buffer_half_float")||se.has("EXT_color_buffer_float")?E:S,minFilter:ot,samples:4,stencilBuffer:o,resolveDepthBuffer:!1,resolveStencilBuffer:!1,colorSpace:p.workingColorSpace}));const i=R.state.transmissionRenderTarget[r.id],a=r.viewport||O;i.setSize(a.z*C.transmissionResolutionScale,a.w*C.transmissionResolutionScale);const s=C.getRenderTarget(),l=C.getActiveCubeFace(),d=C.getActiveMipmapLevel();C.setRenderTarget(i),C.getClearColor(V),z=C.getClearAlpha(),z<1&&C.setClearColor(16777215,.5),C.clear(),ae&&be.render(n);const u=C.toneMapping;C.toneMapping=D;const f=r.viewport;if(void 0!==r.viewport&&(r.viewport=void 0),R.setupLightsView(r),!0===J&&Re.setGlobalState(C.clippingPlanes,r),je(e,n,r),pe.updateMultisampleRenderTarget(i),pe.updateRenderTargetMipmap(i),!1===se.has("WEBGL_multisampled_render_to_texture")){let e=!1;for(let i=0,a=t.length;i0)for(let t=0,a=n.length;t0&&qe(r,i,e,t),ae&&be.render(e),Ke(x,e,t);null!==w&&0===U&&(pe.updateMultisampleRenderTarget(w),pe.updateRenderTargetMipmap(w)),!0===e.isScene&&e.onAfterRender(C,e,t),De.resetDefaultState(),I=-1,N=null,b.pop(),b.length>0?(R=b[b.length-1],!0===J&&Re.setGlobalState(C.clippingPlanes,R.state.camera)):R=null,A.pop(),x=A.length>0?A[A.length-1]:null},this.getActiveCubeFace=function(){return P},this.getActiveMipmapLevel=function(){return U},this.getRenderTarget=function(){return w},this.setRenderTargetTextures=function(e,t,n){const r=ue.get(e);r.__autoAllocateDepthBuffer=!1===e.resolveDepthBuffer,!1===r.__autoAllocateDepthBuffer&&(r.__useRenderToTexture=!1),ue.get(e.texture).__webglTexture=t,ue.get(e.depthTexture).__webglTexture=r.__autoAllocateDepthBuffer?void 0:n,r.__hasExternalTextures=!0},this.setRenderTargetFramebuffer=function(e,t){const n=ue.get(e);n.__webglFramebuffer=t,n.__useDefaultFramebuffer=void 0===t};const et=Ie.createFramebuffer();this.setRenderTarget=function(e,t=0,n=0){w=e,P=t,U=n;let r=!0,i=null,a=!1,o=!1;if(e){const s=ue.get(e);if(void 0!==s.__useDefaultFramebuffer)ce.bindFramebuffer(Ie.FRAMEBUFFER,null),r=!1;else if(void 0===s.__webglFramebuffer)pe.setupRenderTarget(e);else if(s.__hasExternalTextures)pe.rebindTextures(e,ue.get(e.texture).__webglTexture,ue.get(e.depthTexture).__webglTexture);else if(e.depthBuffer){const t=e.depthTexture;if(s.__boundDepthTexture!==t){if(null!==t&&ue.has(t)&&(e.width!==t.image.width||e.height!==t.image.height))throw new Error("WebGLRenderTarget: Attached DepthTexture is initialized to the incorrect size.");pe.setupDepthRenderbuffer(e)}}const l=e.texture;(l.isData3DTexture||l.isDataArrayTexture||l.isCompressedArrayTexture)&&(o=!0);const c=ue.get(e).__webglFramebuffer;e.isWebGLCubeRenderTarget?(i=Array.isArray(c[t])?c[t][n]:c[t],a=!0):i=e.samples>0&&!1===pe.useMultisampledRTT(e)?ue.get(e).__webglMultisampledFramebuffer:Array.isArray(c)?c[n]:c,O.copy(e.viewport),B.copy(e.scissor),G=e.scissorTest}else O.copy(j).multiplyScalar(Y).floor(),B.copy(Z).multiplyScalar(Y).floor(),G=$;0!==n&&(i=et);if(ce.bindFramebuffer(Ie.FRAMEBUFFER,i)&&r&&ce.drawBuffers(e,i),ce.viewport(O),ce.scissor(B),ce.setScissorTest(G),a){const r=ue.get(e.texture);Ie.framebufferTexture2D(Ie.FRAMEBUFFER,Ie.COLOR_ATTACHMENT0,Ie.TEXTURE_CUBE_MAP_POSITIVE_X+t,r.__webglTexture,n)}else if(o){const r=t;for(let t=0;t=0&&t<=e.width-r&&n>=0&&n<=e.height-i&&(e.textures.length>1&&Ie.readBuffer(Ie.COLOR_ATTACHMENT0+s),Ie.readPixels(t,n,r,i,Ue.convert(l),Ue.convert(c),a))}finally{const e=null!==w?ue.get(w).__webglFramebuffer:null;ce.bindFramebuffer(Ie.FRAMEBUFFER,e)}}},this.readRenderTargetPixelsAsync=async function(e,t,n,r,i,a,o,s=0){if(!e||!e.isWebGLRenderTarget)throw new Error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let l=ue.get(e).__webglFramebuffer;if(e.isWebGLCubeRenderTarget&&void 0!==o&&(l=l[o]),l){if(t>=0&&t<=e.width-r&&n>=0&&n<=e.height-i){ce.bindFramebuffer(Ie.FRAMEBUFFER,l);const o=e.textures[s],c=o.format,d=o.type;if(!le.textureFormatReadable(c))throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in RGBA or implementation defined format.");if(!le.textureTypeReadable(d))throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in UnsignedByteType or implementation defined type.");const u=Ie.createBuffer();Ie.bindBuffer(Ie.PIXEL_PACK_BUFFER,u),Ie.bufferData(Ie.PIXEL_PACK_BUFFER,a.byteLength,Ie.STREAM_READ),e.textures.length>1&&Ie.readBuffer(Ie.COLOR_ATTACHMENT0+s),Ie.readPixels(t,n,r,i,Ue.convert(c),Ue.convert(d),0);const f=null!==w?ue.get(w).__webglFramebuffer:null;ce.bindFramebuffer(Ie.FRAMEBUFFER,f);const p=Ie.fenceSync(Ie.SYNC_GPU_COMMANDS_COMPLETE,0);return Ie.flush(),await bn(Ie,p,4),Ie.bindBuffer(Ie.PIXEL_PACK_BUFFER,u),Ie.getBufferSubData(Ie.PIXEL_PACK_BUFFER,0,a),Ie.deleteBuffer(u),Ie.deleteSync(p),a}throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested read bounds are out of range.")}},this.copyFramebufferToTexture=function(e,t=null,n=0){const r=Math.pow(2,-n),i=Math.floor(e.image.width*r),a=Math.floor(e.image.height*r),o=null!==t?t.x:0,s=null!==t?t.y:0;pe.setTexture2D(e,0),Ie.copyTexSubImage2D(Ie.TEXTURE_2D,n,0,0,o,s,i,a),ce.unbindTexture()};const tt=Ie.createFramebuffer(),nt=Ie.createFramebuffer();this.copyTextureToTexture=function(e,t,n=null,r=null,i=0,a=null){let o,s,l,c,d,u,f,p,m;null===a&&(0!==i?(H("WebGLRenderer: copyTextureToTexture function signature has changed to support src and dst mipmap levels."),a=i,i=0):a=0);const h=e.isCompressedTexture?e.mipmaps[a]:e.image;if(null!==n)o=n.max.x-n.min.x,s=n.max.y-n.min.y,l=n.isBox3?n.max.z-n.min.z:1,c=n.min.x,d=n.min.y,u=n.isBox3?n.min.z:0;else{const t=Math.pow(2,-i);o=Math.floor(h.width*t),s=Math.floor(h.height*t),l=e.isDataArrayTexture?h.depth:e.isData3DTexture?Math.floor(h.depth*t):1,c=0,d=0,u=0}null!==r?(f=r.x,p=r.y,m=r.z):(f=0,p=0,m=0);const _=Ue.convert(t.format),g=Ue.convert(t.type);let v;t.isData3DTexture?(pe.setTexture3D(t,0),v=Ie.TEXTURE_3D):t.isDataArrayTexture||t.isCompressedArrayTexture?(pe.setTexture2DArray(t,0),v=Ie.TEXTURE_2D_ARRAY):(pe.setTexture2D(t,0),v=Ie.TEXTURE_2D),Ie.pixelStorei(Ie.UNPACK_FLIP_Y_WEBGL,t.flipY),Ie.pixelStorei(Ie.UNPACK_PREMULTIPLY_ALPHA_WEBGL,t.premultiplyAlpha),Ie.pixelStorei(Ie.UNPACK_ALIGNMENT,t.unpackAlignment);const E=Ie.getParameter(Ie.UNPACK_ROW_LENGTH),S=Ie.getParameter(Ie.UNPACK_IMAGE_HEIGHT),T=Ie.getParameter(Ie.UNPACK_SKIP_PIXELS),M=Ie.getParameter(Ie.UNPACK_SKIP_ROWS),x=Ie.getParameter(Ie.UNPACK_SKIP_IMAGES);Ie.pixelStorei(Ie.UNPACK_ROW_LENGTH,h.width),Ie.pixelStorei(Ie.UNPACK_IMAGE_HEIGHT,h.height),Ie.pixelStorei(Ie.UNPACK_SKIP_PIXELS,c),Ie.pixelStorei(Ie.UNPACK_SKIP_ROWS,d),Ie.pixelStorei(Ie.UNPACK_SKIP_IMAGES,u);const R=e.isDataArrayTexture||e.isData3DTexture,A=t.isDataArrayTexture||t.isData3DTexture;if(e.isDepthTexture){const n=ue.get(e),r=ue.get(t),h=ue.get(n.__renderTarget),_=ue.get(r.__renderTarget);ce.bindFramebuffer(Ie.READ_FRAMEBUFFER,h.__webglFramebuffer),ce.bindFramebuffer(Ie.DRAW_FRAMEBUFFER,_.__webglFramebuffer);for(let n=0;n GGUF model bundle <-------+ + + runtime + + keyboard/game input + | + Go session controller -- movement, facing, speed, selected style + | + PureGo -> stable C ABI -> stateful C++ agent + | + recent 4 frames -> target construction and normalization + | + root transformer + | + predicted root path and predicted duration + | + pose transformer + | + VQ pose decoder + | + representation inverse / FK + | + root translations + local joint rotations + | + HTTP JSON chunks -> Three.js +``` + +There are three deliberately separate layers: + +1. **Neural inference** reproduces the root transformer, pose-token + transformer, and VQ decoder. +2. **Planner/controller** reproduces context management, the critically + damped spring target, style-pose sampling/alignment, replanning, and output + blending. +3. **Application** maps keyboard input to commands, owns user sessions, and + renders results. It does not implement model math. + +This separation lets network parity be established without the interactive +demo and lets controller behavior be tested with recorded neural outputs. + +## Proposed repository layout + +```text +CMakeLists.txt +CMakePresets.json +flake.nix +include/motionbricks/ + motionbricks.h stable C ABI; opaque handles only + motionbricks.hpp optional RAII C++ wrapper +src/ + backend.{hpp,cpp} GGML CPU/Vulkan selection and buffers + error.{hpp,cpp} status mapping and exception firewall + gguf.{hpp,cpp} checked metadata/tensor loading + model.{hpp,cpp} immutable model bundle + transformer.{hpp,cpp} MHA, FFN, norms, masks, embeddings + root_model.{hpp,cpp} duration and root-trajectory graph + pose_model.{hpp,cpp} masked pose-token prediction graph + vq_decoder.{hpp,cpp} multi-head code lookup and conv decoder + sampling.{hpp,cpp} argmax/Gumbel and local deterministic PRNG + skeleton_g1.{hpp,cpp} G1 hierarchy, rest pose, FK and mappings + motion_rep.{hpp,cpp} 414/413/418 feature conversion and stats + clip.{hpp,cpp} checked skeletal clip/style assets + controller.{hpp,cpp} spring target and exemplar alignment + agent.{hpp,cpp} stateful replanning and frame buffer + capi.cpp PureGo-facing ownership API + cli.cpp inspect, plan, convert-style, benchmark +scripts/ + convert_to_gguf.py safetensors/config -> GGUF + inspect_bundle.py independent metadata/tensor inspection +reference/ + Dockerfile pinned trusted PyTorch environment + extract_safe.py legacy Lightning checkpoint -> safetensors + capture_fixture.py layer/stage/end-to-end reference capture + capture_demo_session.py recorded upstream controller interaction +tests/ + unit/ deterministic C++ math and validation + parity/ F32 CPU and Vulkan fixture runners + integration/ C ABI, agent, GLB/style and demo protocol +fuzz/ + capi_fuzz.cpp public non-GGUF inputs and call sequences + clip_fuzz.cpp GLB/style parsing and validation +demo/ + main.go PureGo server and per-client sessions + web/index.html + web/app.js + web/style.css + web/vendor/ pinned Three.js distribution or import map +``` + +GGML should be a pinned submodule as in the two reference projects. CMake +builds shared and static library variants, a CLI, tests, and optional fuzzers. +No source path, model path, GPU index, or backend path is compiled in. + +## Runtime data contracts + +### Canonical public animation + +The public animation boundary should be independent of MuJoCo: + +- `root_translation`: row-major F32 `[frames, 3]`, Y-up motion space; +- `local_rotation_xyzw`: row-major F32 `[frames, 34, 4]`; +- debug target roots: row-major F32 `[4, 3]`; +- debug target local rotations: row-major F32 `[4, 34, 4]`; +- frame rate: 30 FPS for the released model; +- joint names, parents, and rest offsets: queried from model metadata; +- optional contacts: row-major F32 `[frames, 4]`. + +MuJoCo qpos is an adapter/debug output, not the primary animation format. The +29-DOF G1 qpos form is useful for exact upstream demo parity but loses the +generic skeletal-animation boundary needed by Three.js and Kimodo. + +### Neural input + +Internally the planner builds the same unnormalized sparse constraints as +upstream: + +- 4 recent context frames and up to 4 target frames; +- global root features `[constraint_frames, 5]`; +- local root features `[constraint_frames, 4]`; +- local-pose constraint features; +- masks saying which sparse constraints are present; +- a requested duration or an allowed-duration mask; +- optional text embeddings kept absent for the released demo path. + +The released inference path recenters root X/Z, normalizes features, predicts +the root trajectory and duration, predicts 8-head pose codes, decodes four +frames per token, converts local-root features back to global motion, and +restores the original world offset. + +### Style assets + +A style is application/controller data rather than a learned model selector. +It contains: + +- a name and optional command binding; +- one or more G1 reference clips; +- an allowed-duration mask; +- average travel speed or an explicit override; +- optional sampling range and heading correction; +- provenance, skeleton identity, FPS, and source hash. + +Original demo styles are extracted from `G1-clip.ckpt` in the trusted reference +container. User styles are imported from G1 skeletal GLB, including GLB output +from `kimodo.cpp`. Both are converted to the same checked style format. The +controller samples four adjacent pose frames, applies the requested world +heading and spring-produced root target, and supplies them as ending +constraints. A style can therefore be changed or added without changing +MotionBricks weights. + +Before claiming direct Kimodo compatibility, a fixture must verify the G1 +joint order, rest pose, local-rotation convention, coordinate system, and FPS. +The adapter will reject rather than guess when metadata is incompatible. + +## PureGo-compatible C API sketch + +All configurable records and returned objects are opaque heap handles. Public +integer types have explicit widths; booleans and enum-like values are +`uint32_t`; no C enum or by-value struct is part of the ABI. Every constructor +has a matching free function. Functions return a status and use a caller-owned +error buffer. + +```c +typedef uint32_t mb_status; +typedef uint32_t mb_device; + +typedef struct mb_runtime_options mb_runtime_options; +typedef struct mb_model mb_model; +typedef struct mb_style mb_style; +typedef struct mb_command mb_command; +typedef struct mb_agent mb_agent; +typedef struct mb_motion mb_motion; + +uint32_t mb_abi_version(void); + +mb_status mb_runtime_options_create(mb_runtime_options **out, + char *err, uint64_t err_cap); +void mb_runtime_options_free(mb_runtime_options *value); +mb_status mb_runtime_options_set_device(mb_runtime_options *, mb_device, + char *, uint64_t); +mb_status mb_runtime_options_set_threads(mb_runtime_options *, uint32_t, + char *, uint64_t); +mb_status mb_runtime_options_set_backend_directory(mb_runtime_options *, + const char *, char *, uint64_t); +mb_status mb_runtime_options_get_device(const mb_runtime_options *, mb_device *, + char *, uint64_t); + +mb_status mb_model_load(const char *bundle_directory, + const mb_runtime_options *, mb_model **out, + char *err, uint64_t err_cap); +void mb_model_free(mb_model *); +mb_status mb_model_get_joint_count(const mb_model *, uint32_t *out, + char *, uint64_t); +mb_status mb_model_get_joint_name(const mb_model *, uint32_t joint, + const char **borrowed, char *, uint64_t); +mb_status mb_model_get_joint_parent(const mb_model *, uint32_t joint, + int32_t *out, char *, uint64_t); +mb_status mb_model_get_neutral_joint_position(const mb_model *, uint32_t joint, + float *x, float *y, float *z, + char *, uint64_t); + +mb_status mb_style_load(const mb_model *, const char *style_path, + mb_style **out, char *err, uint64_t err_cap); +void mb_style_free(mb_style *); +mb_status mb_style_get_name(const mb_style *, const char **borrowed, + char *, uint64_t); +mb_status mb_style_set_speed(mb_style *, float metres_per_second, + char *, uint64_t); + +mb_status mb_command_create(mb_command **out, char *err, uint64_t err_cap); +void mb_command_free(mb_command *); +mb_status mb_command_set_style(mb_command *, const mb_style *, char *, uint64_t); +mb_status mb_command_set_movement_direction(mb_command *, float x, float y, float z, + char *, uint64_t); +mb_status mb_command_set_facing_direction(mb_command *, float x, float y, float z, + char *, uint64_t); +mb_status mb_command_set_target_speed(mb_command *, float, char *, uint64_t); +mb_status mb_command_set_world_target(mb_command *, float x, float y, float z, + float heading_radians, uint32_t enabled, + char *, uint64_t); +mb_status mb_command_set_seed(mb_command *, uint64_t, char *, uint64_t); + +mb_status mb_agent_create(const mb_model *, mb_agent **out, + char *err, uint64_t err_cap); +void mb_agent_free(mb_agent *); +mb_status mb_agent_reset(mb_agent *, const mb_style *initial_style, + char *err, uint64_t err_cap); +mb_status mb_agent_set_context(mb_agent *, const float *root_xyz, + const float *local_xyzw, uint64_t frames, + uint64_t joints, char *err, uint64_t err_cap); +mb_status mb_agent_plan(mb_agent *, const mb_command *, mb_motion **out, + char *err, uint64_t err_cap); +mb_status mb_agent_advance(mb_agent *, uint32_t frames, char *, uint64_t); + +void mb_motion_free(mb_motion *); +mb_status mb_motion_get_frame_count(const mb_motion *, uint64_t *out, + char *, uint64_t); +mb_status mb_motion_get_joint_count(const mb_motion *, uint64_t *out, + char *, uint64_t); +mb_status mb_motion_get_root_translations(const mb_motion *, + const float **borrowed, uint64_t *values, + char *, uint64_t); +mb_status mb_motion_get_local_rotations_xyzw(const mb_motion *, + const float **borrowed, uint64_t *values, + char *, uint64_t); +mb_status mb_motion_get_target_frame_count(const mb_motion *, uint64_t *out, + char *, uint64_t); +mb_status mb_motion_get_target_root_translations(const mb_motion *, + const float **borrowed, uint64_t *values, + char *, uint64_t); +mb_status mb_motion_get_target_local_rotations_xyzw(const mb_motion *, + const float **borrowed, uint64_t *values, + char *, uint64_t); +``` + +The final header will add getters for every option setter and explicit lifetime +documentation. Borrowed strings and arrays remain valid until their owning +handle is freed or mutated. `mb_model` is immutable and may be shared; +`mb_agent`, `mb_command`, and `mb_motion` are not concurrently mutable. No C++ +exception crosses `capi.cpp`. + +The CLI uses this same C API for end-to-end operations so the ABI is exercised +outside unit tests. Internal parity tools may access raw tensors through a +non-installed test header; raw GGML objects and 413/414-dimensional model +features are not exposed as stable public API. + +## Model conversion and bundle + +Use a directory manifest with separate memory-mappable component files: + +```text +motionbricks-g1-f32/ + manifest.json + root-f32.gguf + pose-f32.gguf + vq-decoder-f32.gguf + styles/ + upstream-defaults.mbstyle +``` + +The manifest records format version, model identity, source revision, source +and intermediate hashes, skeleton key, FPS, component dtype, GGML revision, +and mutually compatible component identities. Loading rejects mixed bundles. + +GGUF metadata records all architecture values rather than relying on C++ +defaults, including the released 512-wide root transformer, 1024-wide pose +transformer, 16 attention heads, layer counts, token range, eight pose-code +heads, four frames per token, normalization statistics, feature-index tables, +skeleton data, and sampling defaults. Every tensor lookup is named, typed, +shape checked, and bounds checked. + +The ordinary converter accepts safetensors plus JSON only. NVIDIA's Lightning +checkpoints and the serialized demo-clip checkpoint are Python pickle formats; +only the pinned, network-disabled reference container loads those files. It +then writes safetensors/JSON with hashes for the normal converter. The VQ +encoder is omitted from runtime bundles because inference only needs the code +embeddings and decoder; a reference/debug bundle may retain it for tokenizer +parity. + +## Neural implementation order + +### Shared operators + +Implement the smallest operation set used by the released checkpoints: + +- checked embedding lookup, linear layers, ReLU, LayerNorm, residual add; +- PyTorch-compatible multi-head self-attention and key-padding masks; +- fixed/learned position embeddings; +- 1D convolution, transpose convolution, and dilated residual blocks; +- reshape, permute, concatenate, gather/scatter, softmax and argmax. + +An operation audit against the actual `state_dict` and forward hooks precedes +graph work. PyTorch defaults such as post-norm transformer order, bias, +activation, epsilon, and attention scaling must be captured explicitly, not +reconstructed from memory. + +### Root model + +The root graph embeds sparse start/end pose and root constraints, duration, +and position; runs shared and output transformer stacks; predicts duration; +and expands transformer features through the conditioned convolutional decoder +to the global root trajectory. Duration filtering uses the caller/style's +allowed-token mask exactly as upstream. + +### Pose model + +The pose graph combines masked multi-head VQ code embeddings, predicted root +features, sparse pose constraints, duration embedding, and position embedding; +runs the 16-layer transformer; and produces per-head code logits. One sampling +iteration is the released demo default. Argmax is supported for deterministic +diagnostics; production Gumbel sampling uses an agent-local PRNG and seed. +Reference fixtures store the random uniforms/Gumbel values explicitly instead +of assuming PyTorch and C++ generators produce identical streams. + +### VQ decoder and representation inverse + +Selected codes are expanded through the eight code heads and conditioned +decoder. Sparse start/end constraints and the predicted root path are applied +as upstream. The resulting normalized local representation is converted to +global features, denormalized, and transformed into skeletal animation. These +ordinary math stages are implemented in C++ and tested independently of GGML. + +### Stateful controller + +An agent owns its current generated buffer and frame cursor. At a replan it: + +1. gathers the previous four actual frames; +2. derives current root velocity and heading velocity; +3. applies the upstream critically damped spring to the requested movement, + facing, speed, or explicit world target; +4. samples and aligns four frames from the selected style clip; +5. constructs sparse model constraints and runs inference; +6. restores the world transform, converts the output to local rotations, and + applies the upstream context blend; +7. exposes the valid predicted prefix and retains it as the next context. + +The controller uses seconds internally and derives frame counts from model FPS. +It must not silently enable canonicalization, target skipping, alternate root +selection, extra pose iterations, or any other non-default upstream option. + +## Web demo + +The Go server dynamically loads the shared library with PureGo. It owns one +`mb_agent` per WebSocket session and shares one immutable `mb_model`. Initially +planning requests are serialized through a worker so Vulkan ownership and peak +memory are predictable; parallel sessions can be added after profiling. + +The browser sends compact command state only: pressed controls, movement and +camera-facing vectors, selected style, and monotonically increasing sequence +number. The server returns a binary animation chunk containing a small +versioned header, frame count, root translations, and local quaternions. +Three.js keeps a short playback buffer and samples at render time. The server, +not the browser, owns authoritative agent state and drops stale commands. + +HTTP endpoints provide model/style metadata and health information. A +WebSocket carries interactive commands and generated chunks. The frontend +includes: + +- WASD movement relative to the camera; +- independently controllable facing direction; +- style/action buttons populated from the style manifest; +- a skeleton/mesh viewer, pause/reset, latency and buffered-frame display; +- an optional debug view for target position, facing vector, contacts, and + replan boundaries. + +The initial viewer can render a simple G1 skeleton. A skinned character and +retargeting are later integrations, potentially using `skin-tokens.cpp`. + +## Reference and parity strategy + +Every fixture has a JSON manifest containing the upstream commit, checkpoint +and safetensors hashes, config hash, PyTorch version, dtype/device, tensor +shapes, seed/random inputs, selected style, command stream, and tolerances. +Binary tensors are little-endian and versioned. Tests refuse mismatched +fixtures. + +Capture and compare in this order: + +| Boundary | Reference values | +|---|---| +| checkpoint extraction | every tensor name, dtype, shape, hash | +| skeletal math | qpos/local rotations, FK positions, global rotations | +| motion representation | raw, global 414, local 413, dual conversion, normalization | +| style/controller | sampled frames, average speed, spring roots/headings, aligned targets | +| shared neural ops | linear, LayerNorm, attention, masks, conv/residual blocks | +| root network | every projection, transformer layer, duration logits, decoder layer, root path | +| pose network | every embedding/projection, transformer layer, logits, sampled codes | +| VQ decoder | code embeddings and every decoder layer | +| inference composition | recenter, constraints, root, pose, decode, restore world transform | +| agent session | recorded command changes, replans, valid lengths, final skeletal frames | + +The first exemplar set should be small but discriminating: + +- idle continuation with a fixed duration; +- walk forward, then a 90-degree turn during playback; +- switch from walk to one strongly recognizable upstream style; +- explicit world target behind the character; +- a fixed Kimodo G1 reference clip used as a style; +- minimum and maximum allowed duration; +- both argmax and stored-Gumbel-token inference. + +F32 CPU tolerances are set per stage from observed error, with maximum absolute +and relative L2 limits. End-to-end animation also checks root error, joint +position error after FK, quaternion angular error, token equality where +applicable, duration equality, and contact equality. Vulkan has its own +documented tolerances but must preserve duration and discrete code decisions +on the exemplar suite unless an explicitly reviewed near-tie is present. + +### Automated upstream sessions and observational tracing + +The reference harness should drive the released upstream demo without a human +keyboard and record both numerical traces and a MuJoCo preview. Numerical +traces are the parity authority. Video is a synchronized diagnostic artifact, +not a pixel-exact test oracle. + +Instrumentation must remain observational. In particular, adding target-pose +visualization must not alter inference inputs, controller state, random-number +consumption, playback timing, MuJoCo state, or final numerical output. Use the +following intervention order, stopping as soon as the required value is +available: + +1. call an existing public method and copy its returned value in the capture + script; +2. wrap an existing method or register a PyTorch forward hook from the capture + script; +3. add an optional no-op trace callback at a stable method boundary; +4. make a small upstream source edit only when a value is otherwise confined + to a local variable; +5. never refactor or reorder model/controller calculations merely to expose a + trace. + +Before adding internal taps, run the unmodified pinned upstream revision at +least twice with the same scripted controls and seeds. Record the final motion +features, MuJoCo qpos, valid lengths, modes, and replan frame indices using +only values already returned by the demo classes. This establishes both the +baseline and any numerical nondeterminism inherent to the reference CUDA +environment. Discrete outputs must repeat exactly. Float tolerances for the +instrumentation check must be no wider than the variation observed between +the two unmodified runs. + +After each instrumentation change, repeat the same session with tracing off +and on. Both runs must match the unmodified baseline: exact for masks, modes, +seeds, duration choices and pose codes; bitwise for deterministic CPU values; +and within the measured baseline envelope for CUDA floats. Reject or redesign +any tap that changes those results. The recorder must not call random +functions, mutate a tensor or dictionary owned by upstream, change dtype or +device, enable gradients, or retain a view that upstream later modifies. It +copies with `detach().clone()` and performs CPU transfer only after the value +has been consumed by the upstream calculation. + +The initial taps are: + +| Boundary | Upstream location | Trace values | +|---|---|---| +| playback context | `full_navigation_agent.get_context_*` | four qpos frames and motion features | +| controller | `WASD_controller.generate_control_signals` | scripted keys, mode, movement/facing vectors, allowed durations | +| spring | `_generate_spring_model_position_and_heading` | start and target root positions/headings | +| placed target poses | `_generate_target_joint_transforms` | four global joint positions/rotations and root positions | +| sparse model input | `_generate_inbetween_frames` | global/local root values, poses, masks and requested/allowed durations | +| root network | root-backbone forward hook | duration logits, selected duration and predicted root path | +| pose network | pose-backbone forward hook | pose logits, selected codes and conditioning mask | +| VQ decoder | decoder forward hook | quantized input, conditions, masks and normalized decoded motion | +| composed inference | `motion_inference.predict` | valid length and global/local motion representations | +| playback output | `generate_new_frames`/`get_next_frame` | raw qpos, blended qpos, cursor and replan event | + +Prefer explicit trace calls for controller-local values and forward hooks for +neural modules. The existing `info` argument to `motion_inference.predict` may +carry copied debug values, but normal inference must continue to work with it +absent. Large layer-by-layer captures are enabled for one planning event at a +time rather than every playback frame. + +The scripted session driver supplies `control_info["key_pressed"]`, so it does +not require an OS keyboard listener. It uses a fixed camera proxy because the +WASD controller derives movement from viewer-camera orientation. NumPy, +PyTorch and per-plan MotionBricks seeds are explicit. An initial scenario is: + +- 60 frames idle; +- 60 frames walking forward; +- 45 frames walking through a right turn; +- 60 frames released to idle; +- 60 frames walking with a distinctive style such as `walk_zombie`; +- 60 frames released and allowed to settle. + +Each scenario directory contains a manifest, JSONL control/replan events, +Safetensors files for planning boundaries and playback, and optional media: + +```text +session-idle-walk-turn/ + manifest.json + controls.jsonl + events.jsonl + plan-000.safetensors + plan-001.safetensors + playback.safetensors + preview.mp4 + snapshots/ +``` + +The manifest records the upstream commit, checkpoint/config hashes, scenario +version, all relevant demo flags, dtype/device, Python/PyTorch/CUDA/MuJoCo +versions, seeds, FPS and coordinate conventions. Do not use pickle for fixture +data. Each planning file identifies tensors by semantic name, shape and dtype. + +Generate video in a second, offline pass from the stored qpos and target-pose +traces. Use `mujoco.Renderer`, assign the captured qpos, call `mj_forward`, and +render with a deterministic camera at 30 FPS. On Linux use EGL when available +and OSMesa as a software fallback. Save selected lossless PNG frames alongside +the MP4. This separation ensures rendering load cannot affect controller +cadence or inference. + +Visible target keyframes are also an offline rendering concern. Draw the four +captured target skeletons as orange/magenta ghost geometry in an `MjvScene` or +as a post-render overlay. Do not add bodies or constraints to the physics XML, +write target poses into `mjData`, or reuse mutable tensors from inference. The +animated G1 remains opaque and visually distinct. Overlay the absolute frame, +plan number, mode/style, movement/facing vectors, selected duration and replan +markers. If an interactive upstream target view is later useful, it is a +separate optional patch and must pass the same trace-off/trace-on +non-interference gate. + +The port replays each captured planning event at the same public boundaries. +It compares integer decisions exactly, floats with boundary-specific +tolerances, quaternions by sign-invariant angular distance, and joint positions +after FK. Session checks cover replan timing, valid buffer lengths, root and +joint velocity continuity, and style transitions. The idle scenario must also +confirm that upstream performs only the expected transition into idle, then +ceases idle-to-idle replanning and eventually holds a numerically constant +frame. + +Implement this work in gates: + +1. create the external scripted driver and capture unmodified final outputs; +2. prove same-seed repeatability and write the baseline manifest; +3. add controller/model-input taps one boundary at a time, running the + non-interference comparison after each change; +4. add neural forward hooks and capture one complete planning event; +5. capture the full idle/walk/turn/style session and import it into the C++ + parity runner; +6. render MuJoCo video and target ghosts offline from the accepted traces; +7. add the scenario to CPU CI and keep video generation as an optional local + or artifact-producing job. + +**Exit:** a fresh pinned upstream environment reproduces the accepted session; +trace-off and trace-on outputs match the pre-instrumentation baseline; the C++ +runtime passes every captured boundary; and the synchronized MuJoCo preview +shows playback and target keyframes without participating in inference. + +## Safety, fuzzing, and validation + +GGUF parsing remains GGML's responsibility, but motion-bricks validates bundle +metadata and every expected tensor. Fuzz targets cover: + +- the installed C ABI with generated valid/invalid call sequences; +- opaque-handle nulls, ownership order, repeated free attempts in the harness, + lengths, finite values, dimensions, and invalid enums; +- GLB/style assets, skeleton metadata, malformed animation tracks, NaN/Inf, + huge frame counts, and incompatible joint orders; +- command normalization, degenerate direction vectors, duration masks, spring + math, representation conversions, quaternion/matrix conversions, and FK; +- the versioned WebSocket binary decoder independently in Go. + +Run C++ fuzzers with ASan and UBSan. Unit and integration tests run under the +same sanitizers. File parsers impose explicit byte, frame, joint, accessor, and +allocation limits before allocating. No user-provided path is interpreted by +the browser; the server uses configured style/model roots. + +## Build and configuration + +The normal build is distro-agnostic CMake. Nix supplies a reproducible +developer shell containing Clang, CMake, Ninja, Go, Python fixture tools, +Vulkan headers/loader/tools, shaderc, and sanitizers. Docker contains upstream +Python/CUDA extraction and fixture capture. Neither is required by installed +runtime consumers. + +Suggested configuration inputs: + +- CMake cache: GGML source directory, tests/fuzzers, Vulkan enablement; +- environment/CLI: model bundle, style directory, backend directory, device, + CPU thread count, server address, fixture directory; +- Vulkan device selection: documented loader variables or a CLI-selected + GGML Vulkan device after confirming backend support. The Nix shell supplies + loader paths but never assumes an NVIDIA device index. + +CI lanes are formatting/static checks, CPU unit tests, ASan/UBSan tests, CPU +parity with optional fixture artifacts, and a hardware-tagged Vulkan parity +lane. Large weights and fixtures are downloaded by hash and never committed. + +## Implementation milestones + +### 0. Lock the reference + +- Record the upstream MotionBricks commit and GGML revision. +- Fetch Git LFS checkpoints/assets and record their hashes/licences. +- Build a pinned Python reference environment and reproduce the interactive + demo unchanged. +- Capture one deterministic end-to-end upstream exemplar. + +**Exit:** the reference script reproduces a stored animation and manifest on a +fresh environment. + +### 1. Scaffold and define contracts + +- Add CMake, presets, Nix flake, pinned GGML, library/CLI/test targets. +- Land the opaque C header, C++ wrapper, status/error rules, and stub handles. +- Define fixture, GGUF bundle, style, and WebSocket format versions. +- Add ABI lifecycle tests from C and PureGo. + +**Exit:** CPU-only stubs build, install, and are called successfully through +PureGo without any mirrored struct. + +### 2. Safe extraction and exemplar capture + +- Extract root, pose, VQ, stats, skeleton, and original style tensors to + safetensors/JSON in the trusted container. +- Capture the unmodified scripted upstream session before adding internal + instrumentation, then apply the observational tracing and non-interference + gates defined above. +- Add forward hooks/capture for every required layer and preprocessing stage. +- Capture the exemplar matrix above, including random sampling inputs. +- Render synchronized MuJoCo and target-keyframe previews offline from the + accepted numerical traces. +- Build the safetensors-to-GGUF converter and bundle inspector. + +**Exit:** source and converted tensor inventories match exactly, all bundle +identities are validated, and trace-enabled upstream output matches the +unmodified baseline. + +### 3. Deterministic motion/controller math + +- Implement G1 metadata, quaternion/matrix conversions, FK, feature extraction, + 414/413 representation conversion, stats, and skeletal output conversion. +- Implement style import, clip alignment, spring trajectory, duration masks, + context selection, and blending without neural inference. +- Add unit, fixture parity, and sanitizer tests for each stage. + +**Exit:** C++ matches all non-neural upstream fixture boundaries. + +### 4. VQ decoder on CPU + +- Audit and implement required GGML convolution/residual operations. +- Load codebooks and decoder tensors from GGUF. +- Match each decoder layer and decoded local-motion features in F32. + +**Exit:** CPU VQ decoder fixture passes end to end. + +### 5. Root model on CPU + +- Implement shared transformer operators and root embeddings/masks. +- Match each transformer layer, duration selection, conditioned decoder, and + predicted global/local root trajectories. + +**Exit:** CPU root fixtures pass for fixed, predicted, minimum, and maximum +durations. + +### 6. Pose model and composed inference on CPU + +- Implement pose embeddings, transformer, logits, argmax/Gumbel sampling. +- Compose root -> pose -> VQ -> representation inverse. +- Implement the stateful agent and verify recorded replanning sessions. + +**Exit:** F32 CPU passes every layer and end-to-end skeletal exemplar. + +### 7. C API, Kimodo styles, and web demo + +- Complete opaque getters/setters, ownership, error paths, and installed C API. +- Verify Kimodo G1 GLB import and add a style conversion workflow. +- Implement Go/PureGo server, binary WebSocket protocol, and Three.js viewer. +- Reproduce upstream styles and interactive turn/style-switch scenarios. + +**Exit:** a fresh build runs the interactive demo using both an original and a +Kimodo-authored style, with no Python process at runtime. + +### 8. Vulkan parity and performance + +- Enable GGML Vulkan and select the intended GPU through runtime configuration. +- Match every component and end-to-end fixture using Vulkan-specific + tolerances. +- Profile allocation, upload, graph construction, inference, and WebSocket + latency; cache graphs/buffers where safe. +- Establish a replan cadence the machine sustains while playback remains + buffered. + +**Exit:** Vulkan preserves exemplar behavior and meets the documented +interactive latency/buffer target on the test machine. + +### 9. Hardening and optimized weights + +- Run long ASan/UBSan fuzz campaigns and multi-session soak tests. +- Add F16 conversion/parity, then evaluate quantization component by component. +- Package install targets, model downloader, licences, deployment docs, and + reproducible release manifests. + +**Exit:** release artifacts are reproducible, validated, and contain no hidden +machine-specific paths or upstream Python dependency. + +## Decisions to revisit after the first fixtures + +- Whether the public style format should be a small GGUF, GLB plus JSON, or a + custom binary. GLB plus JSON is easiest for user-authored Kimodo assets; + GGUF is likely best for exact preprocessed upstream tensors. +- Whether `mb_agent_plan` should retain output internally or return an owning + immutable copy. The owning result above is simpler and safer for PureGo; + profiling may justify an explicit caller-buffer API later. +- Whether to expose a generic sparse-keyframe API in ABI version 1. It is more + capable than the interactive controller but substantially enlarges the + validation surface. The first slice can support movement/facing/style/world + target while keeping the generic constraint builder internal. +- Whether to link a small shared GLB module from `kimodo.cpp`/`skin-tokens.cpp` + or keep style conversion in the CLI. Reuse should happen only through a + clean library boundary, not by copying diverging parser code. +- Exact interactive latency, memory, and browser buffer targets. Measure the + F32 CPU baseline and F32 Vulkan implementation before promising numbers. + +## Explicitly deferred + +- training or fine-tuning MotionBricks; +- arbitrary skeleton inference or automatic retargeting; +- text input to MotionBricks itself; +- robot torque/control-policy output; +- WebAssembly inference in the browser; +- F16/quantized defaults before F32 parity; +- multi-batch inference and concurrent mutation of one agent. diff --git a/docs/motions-bricks.md b/docs/motions-bricks.md new file mode 100644 index 0000000000000000000000000000000000000000..a8c79f1b55848ae97e5bb7f56429fb47dbe9b9c7 --- /dev/null +++ b/docs/motions-bricks.md @@ -0,0 +1,42 @@ +# Human led design document + +Don't modify this unless instructed. + +# Questions to loop back to + +- camera controls, zoom pan etc +- Set a new target for parity, exactly what components should we select for that? +- where is the C API documentation +- Are low-level functions exposed so things like styles can be handle externally? could someone just use it for inference? +- camera relative WASD keys + +# End goal + +We want to use motion-bricks.cpp with kimodo.cpp animation key-frames and we want to be able to use these in a web demo. + +So we have a web demo that takes some key-frames for particular movements and styles of movements, like in the upstream demo, including the original key-frames. Like in the upstream demo we are going to have a bunch of movements we can do with the WASD keys and jump or some other user definable motion. + +Eventually, we want to have have a configurable list of movements, that include a keyframe, an action (e.g. move forward at 1m/s, move up (jump)), so we could define a walking action with a keyframe from someone walking and an action of moving forwards, and we could define a forward roll action by specifying one or more keyframes from rolling forwards and also a moving forward action (but perhaps faster forward motion). + +The motion-bricks.cpp library should expose a C API for FFI which is compatible with libraries like purego. So purego can't predict struct layouts, so struct based interfaces should have a constructor for allocating structs and getters and setters. + +The demo server should be written in Go and use purego (or similar). The frontend should use plain HTML/CSS and three.js. + +# Bonus Goal + +Use motion-bricks.cpp with a physically simulated robot in MuJoco or a web browser simulator. + +# Process + +- First we want to produce exemplar outputs from upstream +- We want to create parity tests for each component, including every layer of the neural network. +- We want to ensure that components feeding and consuming input and output from the neural network also match upstream in terms of functionality +- We need to ensure we are not using settings in our implementation by default which are not set as default in upstream +- We should use GGML with Vulkan and CPU backends, first CPU and then Vulkan parity should be achieved +- We can use ../kimodo.cpp and ../skin-tokens.cpp/ as a reference, you should scan these projects before designing and check them when trying to solve an issue for an existing solution +- We are on NixOS and can use a Nix flake to get depenencies for GGML/C++ and Vulkan, the machine we are on has a functioning NVIDIA Vulkan GPU and an AMD one. We want to use the NVIDIA gfx card. +- For the upstream depencies, if we can use Nix that is great, but otherwise we can use Docker +- Don't rely on the current machine's paths, use distro agnostic paths and env vars. parameterise anything that we need to specify in the CMake file or a .env +- We need to fuzz the C++ components except for GGUF input (we trust that GGML has that covered). We should do that with the ASan and UBSAN enabled. +- We should use GGML as git submodule and if we need to patch it for any reason we can apply patches during the project configuration phase +- Use headless chrome (from nix) to perform QA after changes. Use a few different actions and take screenshots before during and after diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000000000000000000000000000000000..038bc80a60f16ffda5501e3149cb08208328c7e1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1776169885, + "narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000000000000000000000000000000000..3cda4d8126b50e2e5c2f384a083843bcef42afcd --- /dev/null +++ b/flake.nix @@ -0,0 +1,39 @@ +{ + description = "motion-bricks.cpp — GGML CPU/Vulkan motion planning"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9"; + + outputs = { self, nixpkgs }: + let + systems = [ "x86_64-linux" "aarch64-linux" ]; + eachSystem = nixpkgs.lib.genAttrs systems; + in { + devShells = eachSystem (system: + let pkgs = import nixpkgs { inherit system; }; + in { + default = pkgs.mkShell { + packages = with pkgs; [ + cmake + ninja + clang + pkg-config + git + git-lfs + go + python3 + python3Packages.huggingface-hub + python3Packages.numpy + python3Packages.safetensors + shaderc + spirv-tools + vulkan-headers + vulkan-loader + vulkan-tools + ]; + shellHook = '' + export LD_LIBRARY_PATH="${pkgs.vulkan-loader}/lib:/run/opengl-driver/lib''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + ''; + }; + }); + }; +} diff --git a/include/motionbricks/motionbricks.h b/include/motionbricks/motionbricks.h new file mode 100644 index 0000000000000000000000000000000000000000..3b71fdf51c6b111ef1b4132ea59b2b6c8fda0238 --- /dev/null +++ b/include/motionbricks/motionbricks.h @@ -0,0 +1,178 @@ +#ifndef MOTIONBRICKS_MOTIONBRICKS_H +#define MOTIONBRICKS_MOTIONBRICKS_H + +#include + +#define MB_ABI_VERSION UINT32_C(1) + +#if defined(_WIN32) && defined(MOTIONBRICKS_SHARED) +# if defined(MOTIONBRICKS_BUILD) +# define MB_API __declspec(dllexport) +# else +# define MB_API __declspec(dllimport) +# endif +#elif defined(__GNUC__) && defined(MOTIONBRICKS_SHARED) +# define MB_API __attribute__((visibility("default"))) +#else +# define MB_API +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint32_t mb_status; +#define MB_OK UINT32_C(0) +#define MB_INVALID_ARGUMENT UINT32_C(1) +#define MB_OUT_OF_MEMORY UINT32_C(2) +#define MB_IO_ERROR UINT32_C(3) +#define MB_INVALID_FORMAT UINT32_C(4) +#define MB_INCOMPATIBLE_MODEL UINT32_C(5) +#define MB_BACKEND_UNAVAILABLE UINT32_C(6) +#define MB_COMPUTE_FAILED UINT32_C(7) +#define MB_NOT_IMPLEMENTED UINT32_C(8) +#define MB_INTERNAL_ERROR UINT32_C(9) + +typedef uint32_t mb_device; +#define MB_DEVICE_AUTO UINT32_C(0) +#define MB_DEVICE_CPU UINT32_C(1) +#define MB_DEVICE_VULKAN UINT32_C(2) + +typedef struct mb_runtime_options mb_runtime_options; +typedef struct mb_model mb_model; +typedef struct mb_style mb_style; +typedef struct mb_command mb_command; +typedef struct mb_agent mb_agent; +typedef struct mb_motion mb_motion; + +MB_API uint32_t mb_abi_version(void); +MB_API const char * mb_status_string(mb_status status); + +/* Runtime options are opaque so FFI callers never reproduce a struct layout. */ +MB_API mb_status mb_runtime_options_create(mb_runtime_options ** output, + char * error, uint64_t error_capacity); +MB_API void mb_runtime_options_free(mb_runtime_options * value); +MB_API mb_status mb_runtime_options_set_device(mb_runtime_options * value, mb_device device, + char * error, uint64_t error_capacity); +MB_API mb_status mb_runtime_options_get_device(const mb_runtime_options * value, mb_device * output, + char * error, uint64_t error_capacity); +MB_API mb_status mb_runtime_options_set_threads(mb_runtime_options * value, uint32_t threads, + char * error, uint64_t error_capacity); +MB_API mb_status mb_runtime_options_get_threads(const mb_runtime_options * value, uint32_t * output, + char * error, uint64_t error_capacity); +/* Passing NULL clears the override. The returned string is borrowed. */ +MB_API mb_status mb_runtime_options_set_backend_directory(mb_runtime_options * value, + const char * directory, + char * error, uint64_t error_capacity); +MB_API mb_status mb_runtime_options_get_backend_directory(const mb_runtime_options * value, + const char ** output, + char * error, uint64_t error_capacity); + +/* A command is independent of compiler layout and may be reused between plans. */ +MB_API mb_status mb_command_create(mb_command ** output, + char * error, uint64_t error_capacity); +MB_API void mb_command_free(mb_command * value); +MB_API mb_status mb_command_set_style(mb_command * value, const mb_style * style, + char * error, uint64_t error_capacity); +MB_API mb_status mb_command_get_style(const mb_command * value, const mb_style ** output, + char * error, uint64_t error_capacity); +MB_API mb_status mb_command_set_movement_direction(mb_command * value, float x, float y, float z, + char * error, uint64_t error_capacity); +MB_API mb_status mb_command_get_movement_direction(const mb_command * value, + float * x, float * y, float * z, + char * error, uint64_t error_capacity); +MB_API mb_status mb_command_set_facing_direction(mb_command * value, float x, float y, float z, + char * error, uint64_t error_capacity); +MB_API mb_status mb_command_get_facing_direction(const mb_command * value, + float * x, float * y, float * z, + char * error, uint64_t error_capacity); +/* A negative speed selects the style's configured speed. */ +MB_API mb_status mb_command_set_target_speed(mb_command * value, float metres_per_second, + char * error, uint64_t error_capacity); +MB_API mb_status mb_command_get_target_speed(const mb_command * value, float * output, + char * error, uint64_t error_capacity); +MB_API mb_status mb_command_set_world_target(mb_command * value, + float x, float y, float z, + float heading_radians, uint32_t enabled, + char * error, uint64_t error_capacity); +MB_API mb_status mb_command_get_world_target(const mb_command * value, + float * x, float * y, float * z, + float * heading_radians, uint32_t * enabled, + char * error, uint64_t error_capacity); +MB_API mb_status mb_command_set_seed(mb_command * value, uint64_t seed, + char * error, uint64_t error_capacity); +MB_API mb_status mb_command_get_seed(const mb_command * value, uint64_t * output, + char * error, uint64_t error_capacity); + +/* Neural model, style, and stateful animation planner. */ +MB_API mb_status mb_model_load(const char * bundle_directory, + const mb_runtime_options * options, mb_model ** output, + char * error, uint64_t error_capacity); +MB_API void mb_model_free(mb_model * value); +MB_API mb_status mb_model_get_parameter_count(const mb_model * value, uint64_t * output, + char * error, uint64_t error_capacity); +MB_API mb_status mb_model_get_joint_count(const mb_model * value, uint32_t * output, + char * error, uint64_t error_capacity); +MB_API mb_status mb_model_get_joint_name(const mb_model * value, uint32_t joint, + const char ** output, + char * error, uint64_t error_capacity); +MB_API mb_status mb_model_get_joint_parent(const mb_model * value, uint32_t joint, + int32_t * output, + char * error, uint64_t error_capacity); +MB_API mb_status mb_model_get_neutral_joint_position(const mb_model * value, uint32_t joint, + float * x, float * y, float * z, + char * error, uint64_t error_capacity); + +MB_API mb_status mb_style_load(const mb_model * model, const char * style_path, + mb_style ** output, char * error, uint64_t error_capacity); +MB_API void mb_style_free(mb_style * value); +MB_API mb_status mb_style_get_name(const mb_style * value, const char ** output, + char * error, uint64_t error_capacity); +MB_API mb_status mb_style_set_speed(mb_style * value, float metres_per_second, + char * error, uint64_t error_capacity); +MB_API mb_status mb_style_get_speed(const mb_style * value, float * output, + char * error, uint64_t error_capacity); + +MB_API mb_status mb_agent_create(const mb_model * model, mb_agent ** output, + char * error, uint64_t error_capacity); +MB_API void mb_agent_free(mb_agent * value); +MB_API mb_status mb_agent_reset(mb_agent * value, const mb_style * initial_style, + char * error, uint64_t error_capacity); +MB_API mb_status mb_agent_set_context(mb_agent * value, + const float * root_xyz, + const float * local_rotation_xyzw, + uint64_t frames, uint64_t joints, + char * error, uint64_t error_capacity); +MB_API mb_status mb_agent_plan(mb_agent * value, const mb_command * command, + mb_motion ** output, + char * error, uint64_t error_capacity); +MB_API mb_status mb_agent_advance(mb_agent * value, uint32_t frames, + char * error, uint64_t error_capacity); + +MB_API void mb_motion_free(mb_motion * value); +MB_API mb_status mb_motion_get_frame_count(const mb_motion * value, uint64_t * output, + char * error, uint64_t error_capacity); +MB_API mb_status mb_motion_get_joint_count(const mb_motion * value, uint64_t * output, + char * error, uint64_t error_capacity); +/* Borrowed row-major F32 buffers valid until mb_motion_free. */ +MB_API mb_status mb_motion_get_root_translations(const mb_motion * value, + const float ** output, uint64_t * values, + char * error, uint64_t error_capacity); +MB_API mb_status mb_motion_get_local_rotations_xyzw(const mb_motion * value, + const float ** output, uint64_t * values, + char * error, uint64_t error_capacity); +/* The four placed style-pose constraints actually supplied to the planner. */ +MB_API mb_status mb_motion_get_target_frame_count(const mb_motion * value, uint64_t * output, + char * error, uint64_t error_capacity); +MB_API mb_status mb_motion_get_target_root_translations(const mb_motion * value, + const float ** output, uint64_t * values, + char * error, uint64_t error_capacity); +MB_API mb_status mb_motion_get_target_local_rotations_xyzw(const mb_motion * value, + const float ** output, uint64_t * values, + char * error, uint64_t error_capacity); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/motionbricks/motionbricks.hpp b/include/motionbricks/motionbricks.hpp new file mode 100644 index 0000000000000000000000000000000000000000..99421872138958d13aaa55802794746d540949fe --- /dev/null +++ b/include/motionbricks/motionbricks.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace motionbricks { + +class api_error final : public std::runtime_error { +public: + api_error(mb_status status, const char * message) + : std::runtime_error(message != nullptr ? message : mb_status_string(status)), status_(status) {} + + [[nodiscard]] mb_status status() const noexcept { return status_; } + +private: + mb_status status_; +}; + +class runtime_options final { +public: + runtime_options() { + std::array error{}; + const auto status = mb_runtime_options_create(&value_, error.data(), error.size()); + if (status != MB_OK) throw api_error(status, error.data()); + } + + ~runtime_options() { mb_runtime_options_free(value_); } + runtime_options(const runtime_options &) = delete; + runtime_options & operator=(const runtime_options &) = delete; + + runtime_options(runtime_options && other) noexcept : value_(other.value_) { + other.value_ = nullptr; + } + + runtime_options & operator=(runtime_options && other) noexcept { + if (this != &other) { + mb_runtime_options_free(value_); + value_ = other.value_; + other.value_ = nullptr; + } + return *this; + } + + [[nodiscard]] mb_runtime_options * get() noexcept { return value_; } + [[nodiscard]] const mb_runtime_options * get() const noexcept { return value_; } + +private: + mb_runtime_options * value_ = nullptr; +}; + +} // namespace motionbricks diff --git a/patches/ggml/README.md b/patches/ggml/README.md new file mode 100644 index 0000000000000000000000000000000000000000..dd23e6f29d9bd8d71d2f0d729dcce608c676879d --- /dev/null +++ b/patches/ggml/README.md @@ -0,0 +1,9 @@ +# GGML compatibility patches + +The initial GGML revision is +`8c63e70982c95ceb862e3a1073a2c1beef75d60a` (`v0.20.2`). No compatibility +patch is currently required. + +If a patch becomes necessary, add numbered patch files here and update CMake +to copy GGML into the build directory and apply them in lexical order. Never +modify the pinned submodule worktree during configuration. diff --git a/reference/Dockerfile b/reference/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..052feae09d609e0f208675a493092e2cb88d76ec --- /dev/null +++ b/reference/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.10.14-slim-bookworm@sha256:2407c61b1a18067393fecd8a22cf6fceede893b6aaca817bf9fbfe65e33614a3 + +RUN python -m pip install --no-cache-dir --upgrade pip==24.3.1 \ + && python -m pip install --no-cache-dir \ + --index-url https://download.pytorch.org/whl/cpu \ + torch==2.4.0 \ + && python -m pip install --no-cache-dir \ + numpy==1.26.4 \ + PyYAML==6.0.2 \ + safetensors==0.4.5 + +WORKDIR /work +ENTRYPOINT ["python", "/work/reference/extract_safe.py"] diff --git a/reference/README.md b/reference/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a7cb9e4460b3b0c48d79021300d09f70e30c218d --- /dev/null +++ b/reference/README.md @@ -0,0 +1,117 @@ +# Upstream reference + +The native implementation is validated against a pinned, unmodified NVIDIA +MotionBricks checkout. + +| Component | Repository | Revision | +|---|---|---| +| MotionBricks | `https://github.com/NVlabs/GR00T-WholeBodyControl.git` | `a0732b642c0333077e127a2f56ab0014c196bca4` | +| GGML | `https://github.com/ggml-org/ggml.git` | `8c63e70982c95ceb862e3a1073a2c1beef75d60a` (`v0.20.2`) | + +The `ggml/` submodule is pinned to the same revision used by the local +`kimodo.cpp` and `skin-tokens.cpp` reference ports. Compatibility patches, if +required, belong under `patches/ggml/` and will be applied to a build-directory +copy during CMake configuration; the submodule worktree will remain untouched. + +## Checkpoint identities + +The required MotionBricks checkpoints and G1 meshes have been fetched through +Git LFS. These identities are the initial trusted reference inputs: + +| Artifact | Bytes | SHA-256 | +|---|---:|---| +| `G1-clip.ckpt` | 7,761,041 | `84afc7c229473351a24b0a7d79fc47be9dbb81bd12774285f2f60a3c0e9028df` | +| VQ-VAE `model-step=2000000.ckpt` | 285,607,244 | `f12a09d46ad390a8e2eecbe7219b2472fcab6b59df0a13f6a40c35cb6da4d99a` | +| pose `model-step=2000000.ckpt` | 1,639,126,476 | `0223c352b308ba638a499cc5c92104da36cb1d04cea2f8ce61d54a2489f853f1` | +| root `model-step=2000000.ckpt` | 409,551,754 | `d7299a9b1f5aca35730c36dfe7ea28075708ac8266bf384fe8e2ef9c9aee69c7` | + +Saved configuration identities: + +| Configuration | SHA-256 | +|---|---| +| VQ-VAE `config.yaml` | `027a2d7ba5f49cadeadbcc9a6b0c6784d657d5a842e41ff820c5233f1cd6c1f3` | +| pose `config.yaml` | `273af770d328b458510ee6049fac8e38fa486c8792cc27b3515dddc094a7cd1f` | +| root `config.yaml` | `b174f03a333f7f7857c3e2b8a32da517caddd198f228efeea2e203d046ce212a` | + +The 29-DOF G1 XML identities used by the initial interactive reference are: + +- `g1_29dof.xml`: `58660a6f1d0d33ffd8ee967ab3860def53e3327d956cb009dd1385ddaf430f56` +- `scene_29dof.xml`: `e254f11acce2ec6f6efa5bf9b15e288bbd0ca29aeeabb1e1d0fea92f65436bbf` + +Legacy Lightning `.ckpt` and demo-clip files are deserialized only inside the +reference container. Normal conversion accepts safetensors and JSON, never +pickle-bearing checkpoints. + +Build and run the trusted extractor from the project root: + +```sh +docker build -t motionbricks-reference:torch2.4 reference +docker run --rm --user "$(id -u):$(id -g)" \ + -v "$PWD:/work" \ + -v /path/to/GR00T-WholeBodyControl:/upstream:ro \ + motionbricks-reference:torch2.4 \ + --upstream-root /upstream \ + --output /work/generated/safe +``` + +The extractor verifies every pinned input hash and the small allowlist of +pickle globals before loading. It writes independent safetensors for the three +model checkpoints, original demo clips, and shared skeleton/statistics, plus a +manifest containing every tensor name, shape, dtype, value count, and hash. + +## Native inference bundle + +The normal converter reads only those verified safetensors and JSON files: + +```sh +nix develop --command python scripts/convert_to_gguf.py \ + --safe-directory generated/safe \ + --output generated/g1-f32 +``` + +It emits three inference-weight components plus one small skeleton/statistics +component. The VQ-VAE encoder and optimizer/training state are excluded. The +resulting learned parameter inventory is: + +- pose planner: 136,588,272 +- root planner: 34,122,833 +- VQ pose decoder and codebook: 12,437,277 +- total: 183,148,382 + +Validate the result through the same public model-loading path applications +use: + +```sh +./build/debug/bin/motionbricks-cli inspect generated/g1-f32 +``` + +## Reference fixtures and styles + +Generate deterministic PyTorch layer fixtures inside the same pinned +container, then package them as small GGUF test inputs: + +```sh +docker run --rm --user "$(id -u):$(id -g)" \ + --entrypoint python \ + -v "$PWD:/work" \ + -v /path/to/GR00T-WholeBodyControl:/upstream:ro \ + motionbricks-reference:torch2.4 \ + /work/reference/generate_fixtures.py \ + --upstream-root /upstream \ + --safe-directory /work/generated/safe \ + --output /work/generated/fixtures + +python scripts/convert_fixtures_to_gguf.py \ + --fixtures generated/fixtures \ + --output generated/fixtures-gguf + +python scripts/convert_styles.py \ + --safe-directory generated/safe \ + --output generated/styles +``` + +The native suite checks every released neural component against these actual +upstream PyTorch forwards. On the current reference machine, complete +style-to-animation output also matches between CPU and strict-F32 Vulkan with +the same 44-frame duration; observed maximum absolute differences were +`2.19e-5` for root translations and `1.02e-4` for local quaternion components. diff --git a/reference/extract_safe.py b/reference/extract_safe.py new file mode 100644 index 0000000000000000000000000000000000000000..451fd6733c3acc22721c07f4c1f2d94f7a49d9a6 --- /dev/null +++ b/reference/extract_safe.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +"""One-time extraction of pinned MotionBricks pickle checkpoints. + +Run this only inside the disposable reference container. Normal conversion and +native inference consume the resulting safetensors and JSON and never open a +PyTorch pickle container. +""" + +from __future__ import annotations + +import argparse +import gc +import hashlib +import json +import os +import pickletools +from pathlib import Path +from typing import Any +from zipfile import ZipFile + +import numpy as np +import torch +import yaml +from safetensors.torch import save_file + + +UPSTREAM_REVISION = "a0732b642c0333077e127a2f56ab0014c196bca4" + +INPUTS = { + "clips": ( + "motionbricks/out/G1-clip.ckpt", + "84afc7c229473351a24b0a7d79fc47be9dbb81bd12774285f2f60a3c0e9028df", + ), + "vqvae": ( + "motionbricks/out/motionbricks_vqvae/version_1/checkpoints/model-step=2000000.ckpt", + "f12a09d46ad390a8e2eecbe7219b2472fcab6b59df0a13f6a40c35cb6da4d99a", + ), + "pose": ( + "motionbricks/out/motionbricks_pose/version_1/checkpoints/model-step=2000000.ckpt", + "0223c352b308ba638a499cc5c92104da36cb1d04cea2f8ce61d54a2489f853f1", + ), + "root": ( + "motionbricks/out/motionbricks_root/version_1/checkpoints/model-step=2000000.ckpt", + "d7299a9b1f5aca35730c36dfe7ea28075708ac8266bf384fe8e2ef9c9aee69c7", + ), + "joints": ( + "motionbricks/out/motionbricks_pose/version_1/skeleton/joints.p", + "8a582b7020d1609a34a9ea5ddfa597c8727b3e726e38cc80ecefe68171f43ecd", + ), + "parents": ( + "motionbricks/out/motionbricks_pose/version_1/skeleton/parents.p", + "4ba0237379480ef33e64b7b8564f1d38dcb3e1ad5bfe01652ea1b7c97ccacb71", + ), + "mean": ( + "motionbricks/out/motionbricks_pose/version_1/stats/motion/mean.npy", + "ca390f0081e2373ab71e860a3546cb70cc11fdfac6ce525155403e047b66fdea", + ), + "std": ( + "motionbricks/out/motionbricks_pose/version_1/stats/motion/std.npy", + "fca7dd6135cbe96504b1307a7db97e9004556937a2fc72d139077b962cee6bd7", + ), +} + +CONFIGS = { + "vqvae": ( + "motionbricks/out/motionbricks_vqvae/version_1/config.yaml", + "027a2d7ba5f49cadeadbcc9a6b0c6784d657d5a842e41ff820c5233f1cd6c1f3", + ), + "pose": ( + "motionbricks/out/motionbricks_pose/version_1/config.yaml", + "273af770d328b458510ee6049fac8e38fa486c8792cc27b3515dddc094a7cd1f", + ), + "root": ( + "motionbricks/out/motionbricks_root/version_1/config.yaml", + "b174f03a333f7f7857c3e2b8a32da517caddd198f228efeea2e203d046ce212a", + ), +} + +ALLOWED_PICKLE_GLOBALS = { + "collections OrderedDict", + "torch BoolStorage", + "torch DoubleStorage", + "torch FloatStorage", + "torch IntStorage", + "torch LongStorage", + "torch._utils _rebuild_tensor_v2", +} + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + while block := stream.read(8 << 20): + digest.update(block) + return digest.hexdigest() + + +def verify(path: Path, expected: str) -> None: + actual = sha256(path) + if actual != expected: + raise ValueError(f"SHA-256 mismatch for {path}: {actual}, expected {expected}") + + +def inspect_pickle_globals(path: Path) -> None: + with ZipFile(path) as archive: + names = [name for name in archive.namelist() if name.endswith("/data.pkl")] + if len(names) != 1: + raise ValueError(f"{path} has {len(names)} data.pkl entries") + data = archive.read(names[0]) + globals_found = { + str(argument) + for opcode, argument, _ in pickletools.genops(data) + if opcode.name == "GLOBAL" + } + unexpected = globals_found - ALLOWED_PICKLE_GLOBALS + if unexpected: + raise ValueError(f"unexpected pickle globals in {path}: {sorted(unexpected)}") + + +def plain(value: Any) -> Any: + if isinstance(value, dict): + return {str(key): plain(item) for key, item in value.items()} + if isinstance(value, (list, tuple)): + return [plain(item) for item in value] + if value is None or isinstance(value, (str, int, float, bool)): + return value + return repr(value) + + +def tensor_bytes(value: torch.Tensor) -> bytes: + return value.detach().cpu().contiguous().reshape(-1).view(torch.uint8).numpy().tobytes() + + +def tensor_record(name: str, value: torch.Tensor) -> dict[str, Any]: + return { + "name": name, + "dtype": str(value.dtype).removeprefix("torch."), + "shape": [int(item) for item in value.shape], + "numel": int(value.numel()), + "sha256": hashlib.sha256(tensor_bytes(value)).hexdigest(), + } + + +def write_safetensors(path: Path, tensors: dict[str, torch.Tensor], source_hash: str) -> dict[str, Any]: + if not tensors: + raise ValueError(f"refusing to write empty tensor set to {path}") + ordered = { + name: value.detach().cpu().contiguous().clone() + for name, value in sorted(tensors.items()) + } + temporary = path.with_suffix(path.suffix + ".tmp") + save_file( + ordered, + temporary, + metadata={ + "format": "motionbricks-safe-v1", + "upstream_revision": UPSTREAM_REVISION, + "source_sha256": source_hash, + }, + ) + os.replace(temporary, path) + inventory = { + "path": path.name, + "sha256": sha256(path), + "tensor_count": len(ordered), + "parameter_count": sum(int(value.numel()) for value in ordered.values()), + "tensors": [tensor_record(name, value) for name, value in ordered.items()], + } + del ordered + return inventory + + +def load_lightning(path: Path) -> tuple[dict[str, torch.Tensor], dict[str, Any], str]: + inspect_pickle_globals(path) + checkpoint = torch.load(path, map_location="cpu", weights_only=False) + if not isinstance(checkpoint, dict) or "state_dict" not in checkpoint: + raise ValueError(f"{path} is not a Lightning state dictionary") + state = checkpoint["state_dict"] + if not isinstance(state, dict) or any(not isinstance(value, torch.Tensor) for value in state.values()): + raise ValueError(f"{path} contains an invalid state_dict") + metadata = { + "global_step": plain(checkpoint.get("global_step")), + "epoch": plain(checkpoint.get("epoch")), + "pytorch_lightning_version": plain(checkpoint.get("pytorch-lightning_version")), + "hyper_parameters": plain(checkpoint.get("hyper_parameters", {})), + } + return state, metadata, sha256(path) + + +def load_tensor_mapping(path: Path) -> tuple[dict[str, torch.Tensor], str]: + inspect_pickle_globals(path) + value = torch.load(path, map_location="cpu", weights_only=False) + if not isinstance(value, dict) or any(not isinstance(item, torch.Tensor) for item in value.values()): + raise ValueError(f"{path} is not a tensor mapping") + return value, sha256(path) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--upstream-root", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + + args.output.mkdir(parents=True, exist_ok=True) + resolved: dict[str, tuple[Path, str]] = {} + for key, (relative, expected) in INPUTS.items(): + path = args.upstream_root / relative + verify(path, expected) + resolved[key] = (path, expected) + for relative, expected in CONFIGS.values(): + verify(args.upstream_root / relative, expected) + + manifest: dict[str, Any] = { + "format": "motionbricks-safe-manifest-v1", + "upstream_revision": UPSTREAM_REVISION, + "extractor": { + "python": os.sys.version, + "torch": torch.__version__, + "numpy": np.__version__, + }, + "inputs": { + key: {"path": relative, "sha256": expected} + for key, (relative, expected) in INPUTS.items() + }, + "configs": {}, + "components": {}, + "checkpoint_metadata": {}, + } + + for component in ("vqvae", "pose", "root"): + path, _ = resolved[component] + state, checkpoint_metadata, source_hash = load_lightning(path) + manifest["components"][component] = write_safetensors( + args.output / f"{component}.safetensors", state, source_hash + ) + manifest["checkpoint_metadata"][component] = checkpoint_metadata + del state + gc.collect() + + clip_path, clip_hash = resolved["clips"] + clips, _ = load_tensor_mapping(clip_path) + manifest["components"]["clips"] = write_safetensors( + args.output / "clips.safetensors", clips, clip_hash + ) + del clips + gc.collect() + + joints_path, joints_hash = resolved["joints"] + parents_path, _ = resolved["parents"] + inspect_pickle_globals(joints_path) + inspect_pickle_globals(parents_path) + joints = torch.load(joints_path, map_location="cpu", weights_only=False) + parents = torch.load(parents_path, map_location="cpu", weights_only=False) + mean = torch.from_numpy(np.load(resolved["mean"][0], allow_pickle=False)) + std = torch.from_numpy(np.load(resolved["std"][0], allow_pickle=False)) + support = { + "neutral_joints": joints, + "joint_parents": parents, + "motion_mean": mean, + "motion_std": std, + } + if any(not isinstance(value, torch.Tensor) for value in support.values()): + raise ValueError("support inputs did not decode to tensors") + manifest["components"]["support"] = write_safetensors( + args.output / "support.safetensors", support, joints_hash + ) + + for name, (relative, expected) in CONFIGS.items(): + path = args.upstream_root / relative + value = yaml.safe_load(path.read_text(encoding="utf-8")) + manifest["configs"][name] = { + "path": relative, + "sha256": expected, + "value": value, + } + + manifest_path = args.output / "manifest.json" + temporary = manifest_path.with_suffix(".json.tmp") + temporary.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8") + os.replace(temporary, manifest_path) + + for name, component in manifest["components"].items(): + print( + f"{name}: {component['tensor_count']} tensors, " + f"{component['parameter_count']:,} values, {component['sha256']}" + ) + + +if __name__ == "__main__": + main() diff --git a/reference/generate_fixtures.py b/reference/generate_fixtures.py new file mode 100644 index 0000000000000000000000000000000000000000..839dd6fb8dc7d43f7b74c5e8bcd12975c15a1acb --- /dev/null +++ b/reference/generate_fixtures.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python3 +"""Generate deterministic PyTorch fixtures from the pinned upstream modules. + +Run only in the reference container. This script imports NVIDIA's actual +network classes, loads the verified safetensors intermediates, and records +inputs, selected layer outputs, and final outputs without opening a pickle. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import struct +import sys +from pathlib import Path +from types import SimpleNamespace +from typing import Any + +import numpy as np +import torch +from safetensors import safe_open + + +UPSTREAM_REVISION = "a0732b642c0333077e127a2f56ab0014c196bca4" +SAFE_HASHES = { + "pose": "01327768be7413111dc927947a95cfb3e9ee7c52acfa35a054e8c2f3b838b888", + "root": "d1529a8c9da915cb7dd0499272baba61db480660c0aae92b67fbe69828e83c5a", + "vqvae": "544782e605ed96d60bf999243ef8f44640ea021a5655ef20af2d2853c3e18b5b", +} + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + while block := stream.read(8 << 20): + digest.update(block) + return digest.hexdigest() + + +def verify(path: Path, expected: str) -> None: + actual = sha256(path) + if actual != expected: + raise ValueError(f"SHA-256 mismatch for {path}: {actual}, expected {expected}") + + +class Rep: + pass + + +def make_motion_rep() -> Rep: + """Minimal shape-compatible rep used only by upstream constructors.""" + global_rep, local_rep, combined = Rep(), Rep(), Rep() + global_rep.root_mode = "global" + global_rep.indices = { + "all": np.arange(418), + "root": np.arange(5), + "global_root_pos": np.arange(3), + "global_root_pos_2d": np.arange(2), + "global_root_heading": np.arange(2, 4), + "ric_data": np.arange(99), + "global_rot_data": np.arange(99, 303), + } + local_rep.root_mode = "local" + local_rep.indices = { + "all": np.arange(413), + "root": np.arange(4), + "local_root_rot_vel": np.arange(2), + "local_root_vel": np.arange(2, 4), + "global_root_y": np.arange(1), + "ric_data": np.arange(99), + "global_rot_data": np.arange(99, 303), + } + dual = SimpleNamespace(global_motion_rep=global_rep, local_motion_rep=local_rep) + combined.dual_rep = dual + combined.root_mode = "global" + combined.indices = global_rep.indices + return combined + + +def state(path: Path, prefix: str, select=lambda _name: True) -> dict[str, torch.Tensor]: + result: dict[str, torch.Tensor] = {} + with safe_open(path, framework="pt", device="cpu") as source: + for name in source.keys(): + if name.startswith(prefix) and select(name): + result[name.removeprefix(prefix)] = source.get_tensor(name) + return result + + +def ramp(shape: tuple[int, ...], scale: float, phase: float = 0.0) -> torch.Tensor: + count = int(np.prod(shape)) + values = torch.arange(count, dtype=torch.float32) + return (torch.sin(values * 0.017 + phase) * scale).reshape(shape) + + +def trace_modules(model: torch.nn.Module, output: dict[str, torch.Tensor]) -> list[Any]: + handles = [] + traced_types = (torch.nn.Linear, torch.nn.Conv1d, torch.nn.TransformerEncoderLayer) + for name, module in model.named_modules(): + if not name or not isinstance(module, traced_types): + continue + + def capture(_module, _inputs, value, trace_name=name): + if isinstance(value, torch.Tensor): + if value.is_nested: + value = value.to_padded_tensor(0.0) + output[f"trace.{trace_name}"] = value.detach().cpu().contiguous() + + handles.append(module.register_forward_hook(capture)) + return handles + + +def pose_fixture(pose_type, safe: Path) -> dict[str, torch.Tensor]: + args = { + "cond_root_feature": "root_without_hip_height", + "cond_root_feature_is_from_motion_rep": "global", + "down_t": 2, + "local_pose_feature": "joint_positions_and_rotations_and_hip_height", + "max_tokens": 16, + "min_tokens": 6, + "n_embd": 1024, + "n_head": 16, + "n_layers": 16, + "pose_feat_width": 640, + "pose_root_mode": "pose", + "pose_token_mlp_num_layers": 2, + "pose_vqvae": {"code_dim": 256, "has_codebook": True, "nb_code": 100_000_000, "num_heads": 8}, + "root_feat_width": 256, + "root_vqvae": {"code_dim": 60, "nb_code": 1024, "num_heads": 1}, + "text_emb_dim": 4096, + "text_embeddings": None, + "token_length_feat_width": 128, + } + model = pose_type(make_motion_rep(), args).eval() + missing, unexpected = model.load_state_dict(state(safe / "pose.safetensors", "backbone_net."), strict=True) + if missing or unexpected: + raise ValueError(f"pose state mismatch: missing={missing}, unexpected={unexpected}") + + result: dict[str, torch.Tensor] = {} + pose_tokens = (torch.arange(48).reshape(1, 6, 8) * 7 % 11).to(torch.int64) + local_root = ramp((1, 24, 4), 0.35, 0.1) + pose_cond = ramp((1, 24, 304), 0.2, 0.7) + has_pose = torch.zeros((1, 24), dtype=torch.bool) + has_pose[:, [0, 1, 22, 23]] = True + num_tokens = torch.tensor([[6]], dtype=torch.int64) + result.update({ + "input.pose_tokens": pose_tokens, + "input.local_root_values": local_root, + "input.pose_cond": pose_cond, + "input.has_pose_cond": has_pose, + "input.num_tokens": num_tokens, + }) + handles = trace_modules(model, result) + with torch.inference_mode(): + output = model(pose_tokens, local_root, pose_cond, has_pose, num_tokens) + result["output.pose_logits"] = output["pose_logits"].contiguous() + for handle in handles: + handle.remove() + return result + + +def root_fixture(root_type, safe: Path) -> dict[str, torch.Tensor]: + args = { + "activation": "relu", "depth": 4, "dilation_growth_rate": 3, "down_t": 2, + "global_root_feat_dim": 64, "global_root_feature": "root", + "input_feat_mlp_num_layers": 2, + "local_pose_feature": "joint_positions_and_rotations_and_hip_height", + "local_root_feat_dim": 64, "local_root_feature": "root", + "max_tokens": 16, "min_tokens": 6, "n_embd": 512, "n_head": 16, + "n_layers_root_token": 3, "n_layers_shared": 3, "norm": "None", + "pose_feat_dim": 256, + "pose_vqvae": {"code_dim": 60, "nb_code": 1024, "num_heads": 1}, + "root_vqvae": {"code_dim": 60, "nb_code": 1024, "num_heads": 1}, + "text_emb_dim": 4096, "text_embeddings": None, + "use_hard_num_token_emb_for_root_prediction": True, "width": 512, + } + model = root_type(args, make_motion_rep()).eval() + missing, unexpected = model.load_state_dict(state(safe / "root.safetensors", "backbone_net."), strict=True) + if missing or unexpected: + raise ValueError(f"root state mismatch: missing={missing}, unexpected={unexpected}") + + result: dict[str, torch.Tensor] = {} + global_root = ramp((1, 8, 5), 0.3, 0.2) + local_root = ramp((1, 8, 4), 0.25, 0.9) + poses = ramp((1, 8, 304), 0.15, 1.7) + has_global = torch.tensor([[1, 1, 1, 1, 0, 0, 1, 1]], dtype=torch.bool) + has_local = torch.tensor([[1, 1, 1, 1, 0, 0, 1, 1]], dtype=torch.bool) + has_poses = torch.tensor([[1, 1, 1, 1, 0, 0, 1, 1]], dtype=torch.bool) + num_tokens = torch.tensor([[6]], dtype=torch.int64) + result.update({ + "input.global_root_values": global_root, "input.has_global_root_values": has_global, + "input.local_root_values": local_root, "input.has_local_root_values": has_local, + "input.poses": poses, "input.has_poses": has_poses, "input.num_tokens": num_tokens, + }) + handles = trace_modules(model, result) + with torch.inference_mode(): + output = model(global_root, has_global, local_root, has_local, poses, has_poses, num_tokens) + result["output.num_token_logits"] = output["num_token_logits"].contiguous() + result["output.pred_num_tokens"] = output["pred_num_tokens"].contiguous() + result["output.pred_global_root_values"] = output["pred_global_root_values"].contiguous() + for handle in handles: + handle.remove() + return result + + +def decoder_fixture(decoder_type, safe: Path) -> dict[str, torch.Tensor]: + model = decoder_type( + input_emb_width=413, output_emb_width=256, down_t=2, width=512, depth=4, + dilation_growth_rate=3, activation="relu", norm="None", + target_cond_dim=304, external_cond_dim=2, cond_fusion_last_layer=False, + ).eval() + decoder_state = state(safe / "vqvae.safetensors", "pose_net.decoder.") + missing, unexpected = model.load_state_dict(decoder_state, strict=True) + if missing or unexpected: + raise ValueError(f"decoder state mismatch: missing={missing}, unexpected={unexpected}") + codebook = state( + safe / "vqvae.safetensors", "pose_net.", + lambda name: name == "pose_net.quantizer.vq._codebook.embed", + )["quantizer.vq._codebook.embed"] + + result: dict[str, torch.Tensor] = {} + indices = (torch.arange(48).reshape(1, 6, 8) * 3 + 1).remainder(10).to(torch.int64) + batch_index = torch.arange(indices.shape[0])[:, None, None] + head_index = torch.arange(8)[None, None, :] + quantized = codebook[head_index, indices].reshape(1, 6, 256).transpose(1, 2).contiguous() + target = ramp((1, 24, 304), 0.18, 0.4) + has_target = torch.zeros((1, 24), dtype=torch.bool) + has_target[:, [0, 1, 22, 23]] = True + external = ramp((1, 24, 2), 0.22, 1.1) + token_mask = torch.ones((1, 6), dtype=torch.bool) + result.update({ + "input.indices": indices, "input.quantized": quantized, + "input.target_cond": target, "input.has_target_cond": has_target, + "input.external_cond": external, "input.token_mask": token_mask, + }) + handles = trace_modules(model, result) + with torch.inference_mode(): + result["output.motion"] = model( + quantized, external_cond=external, target_cond=target, + has_target_cond=has_target, token_mask=token_mask, + ).transpose(1, 2).contiguous() + for handle in handles: + handle.remove() + return result + + +def tensor_record(name: str, value: torch.Tensor) -> dict[str, Any]: + raw = value.detach().cpu().contiguous().reshape(-1).view(torch.uint8).numpy().tobytes() + return { + "name": name, "dtype": str(value.dtype).removeprefix("torch."), + "shape": list(value.shape), "numel": value.numel(), + "sha256": hashlib.sha256(raw).hexdigest(), + } + + +def write_fixture(path: Path, tensors: dict[str, torch.Tensor]) -> dict[str, Any]: + ordered = {name: value.detach().cpu().contiguous().clone() for name, value in sorted(tensors.items())} + dtype_names = { + torch.float32: "F32", torch.int64: "I64", torch.int32: "I32", torch.bool: "BOOL", + } + header: dict[str, Any] = { + "__metadata__": { + "format": "motionbricks-reference-fixture-v1", + "torch": torch.__version__, + "upstream_revision": UPSTREAM_REVISION, + } + } + payloads: list[bytes] = [] + offset = 0 + for name, value in ordered.items(): + if value.dtype not in dtype_names: + raise ValueError(f"unsupported fixture dtype for {name}: {value.dtype}") + payload = value.reshape(-1).view(torch.uint8).numpy().tobytes() + header[name] = { + "dtype": dtype_names[value.dtype], "shape": list(value.shape), + "data_offsets": [offset, offset + len(payload)], + } + payloads.append(payload) + offset += len(payload) + encoded_header = json.dumps(header, separators=(",", ":"), ensure_ascii=False).encode("utf-8") + encoded_header += b" " * (-len(encoded_header) % 8) + temporary = path.with_suffix(path.suffix + ".tmp") + with temporary.open("wb") as stream: + stream.write(struct.pack(" None: + parser = argparse.ArgumentParser() + parser.add_argument("--upstream-root", type=Path, required=True) + parser.add_argument("--safe-directory", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + if not Path("/.dockerenv").exists(): + raise RuntimeError("reference fixtures must be generated inside the container") + for name, expected in SAFE_HASHES.items(): + verify(args.safe_directory / f"{name}.safetensors", expected) + + sys.path.insert(0, str(args.upstream_root / "motionbricks")) + from motionbricks.motion_backbone.neural_modules.pose_backbone import pose_backbone_network + from motionbricks.motion_backbone.neural_modules.root_backbone import root_backbone_network + from motionbricks.vqvae.neural_modules.encdec_double_cond import DoubleCondDecoder + + torch.manual_seed(7) + torch.set_num_threads(1) + torch.use_deterministic_algorithms(True) + args.output.mkdir(parents=True, exist_ok=True) + generated = { + "pose": write_fixture(args.output / "pose.safetensors", pose_fixture(pose_backbone_network, args.safe_directory)), + "root": write_fixture(args.output / "root.safetensors", root_fixture(root_backbone_network, args.safe_directory)), + "vq-decoder": write_fixture( + args.output / "vq-decoder.safetensors", decoder_fixture(DoubleCondDecoder, args.safe_directory) + ), + } + manifest = { + "format": "motionbricks-reference-fixtures-v1", + "upstream_revision": UPSTREAM_REVISION, + "torch": torch.__version__, "numpy": np.__version__, "fixtures": generated, + } + temporary = args.output / "manifest.json.tmp" + temporary.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8") + os.replace(temporary, args.output / "manifest.json") + for name, record in generated.items(): + print(f"{name}: {record['tensor_count']} tensors, {record['sha256']}") + + +if __name__ == "__main__": + main() diff --git a/scripts/convert_fixtures_to_gguf.py b/scripts/convert_fixtures_to_gguf.py new file mode 100644 index 0000000000000000000000000000000000000000..898e5b419d8b32f7ee1972949e098a17e3afb34d --- /dev/null +++ b/scripts/convert_fixtures_to_gguf.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Package deterministic safetensors reference fixtures as GGUF v3.""" + +from __future__ import annotations + +import argparse +import importlib.util +import sys +from pathlib import Path + +from safetensors import safe_open + + +def load_converter(path: Path): + spec = importlib.util.spec_from_file_location("motionbricks_gguf_converter", path) + if spec is None or spec.loader is None: + raise RuntimeError(f"cannot import {path}") + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--fixtures", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + converter = load_converter(Path(__file__).with_name("convert_to_gguf.py")) + args.output.mkdir(parents=True, exist_ok=True) + for fixture in ("pose", "root", "vq-decoder"): + source_path = args.fixtures / f"{fixture}.safetensors" + values = [] + with safe_open(source_path, framework="numpy") as source: + for name in source.keys(): + values.append((name, source.get_tensor(name))) + converter.write_component( + args.output / f"{fixture}.gguf", f"fixture-{fixture}", values, + converter.sha256(source_path), + ) + + +if __name__ == "__main__": + main() diff --git a/scripts/convert_styles.py b/scripts/convert_styles.py new file mode 100644 index 0000000000000000000000000000000000000000..970f30632c420819e6ca951073781da64d85e4e8 --- /dev/null +++ b/scripts/convert_styles.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Convert the verified upstream G1 exemplar clips into checked style GGUFs.""" + +from __future__ import annotations + +import argparse +import importlib.util +import json +import sys +from pathlib import Path + +import numpy as np +from safetensors import safe_open + + +CLIP_SHA256 = "7ea40c20130c8a9799d83c0438750b51a5be9b1e09357c4928887961fc992d33" +STYLES = ( + ("idle", 0.0, 6), ("slow_walk", 0.3, 6), ("walk", 1.0, 6), + ("hand_crawling", 0.5, 6), ("walk_boxing", 1.0, 11), + ("elbow_crawling", 0.8, 11), ("stealth_walk", 1.0, 6), + ("injured_walk", 0.5, 6), ("walk_stealth", 0.7, 6), + ("walk_happy_dance", 1.0, 6), ("walk_zombie", 0.6, 6), + ("walk_gun", 0.6, 6), ("walk_scared", 0.6, 6), + ("walk_left", 0.2, 4), ("walk_right", 0.2, 4), +) + + +def converter_module(path: Path): + spec = importlib.util.spec_from_file_location("motionbricks_converter", path) + if spec is None or spec.loader is None: + raise RuntimeError(f"cannot import {path}") + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--safe-directory", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + converter = converter_module(Path(__file__).with_name("convert_to_gguf.py")) + source = args.safe_directory / "clips.safetensors" + if converter.sha256(source) != CLIP_SHA256: + raise ValueError("upstream clip safetensors hash mismatch") + with safe_open(source, framework="numpy") as clips: + positions = clips.get_tensor("global_joint_positions") + rotations = clips.get_tensor("global_joint_rotations") + roots = clips.get_tensor("global_root_positions") + headings = clips.get_tensor("global_headings") + lengths = clips.get_tensor("num_frames_per_clip") + headings = headings.copy() + headings[3, 0] = 0.0 + headings[5] -= 0.95 + args.output.mkdir(parents=True, exist_ok=True) + records = {} + for index, (name, speed, allowed_count) in enumerate(STYLES): + frames = int(lengths[index]) + allowed = np.zeros(11, dtype=np.int32) + allowed[:allowed_count] = 1 + values = [ + ("global_joint_positions", positions[index, :frames]), + ("global_joint_rotations", rotations[index, :frames].reshape(frames, 34, 9)), + ("global_root_positions", roots[index, :frames]), + ("global_headings", headings[index, :frames]), + ("allowed_tokens", allowed), + ] + records[name] = converter.write_component( + args.output / f"{name}.mbstyle", "style", values, CLIP_SHA256, + general_name=name, + extra_metadata=[ + converter.kv_string("motionbricks.style_name", name), + converter.kv_f32("motionbricks.style_speed", speed), + converter.kv_u32("motionbricks.style_frames", frames), + converter.kv_u32("motionbricks.style_index", index), + ], + ) + (args.output / "manifest.json").write_text( + json.dumps({"format": "motionbricks-style-bundle-v1", "styles": records}, + indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/scripts/convert_to_gguf.py b/scripts/convert_to_gguf.py new file mode 100644 index 0000000000000000000000000000000000000000..ad782ab960b9e2761d8c3be289a44539a426fe91 --- /dev/null +++ b/scripts/convert_to_gguf.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 +"""Convert verified MotionBricks safetensors into inference-only GGUF v3. + +This converter deliberately rejects PyTorch checkpoint files. The pinned +reference container must first extract the upstream pickle archives into the +safe, deterministic safetensors lane documented in reference/README.md. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import struct +from dataclasses import dataclass +from pathlib import Path +from typing import Callable + +import numpy as np +from safetensors import safe_open + + +GGUF_MAGIC = b"GGUF" +GGUF_VERSION = 3 +ALIGNMENT = 32 + +GGML_TYPE_F32 = 0 +# ggml v0.20.2 / GGUF v3 enum value (the submodule is pinned with the bundle). +GGML_TYPE_I32 = 26 +GGUF_TYPE_UINT32 = 4 +GGUF_TYPE_FLOAT32 = 6 +GGUF_TYPE_STRING = 8 +GGUF_TYPE_UINT64 = 10 + +UPSTREAM_REVISION = "a0732b642c0333077e127a2f56ab0014c196bca4" +SAFE_FORMAT = "motionbricks-safe-manifest-v1" +BUNDLE_FORMAT = "motionbricks-gguf-bundle-v1" +SAFE_HASHES = { + "pose": "01327768be7413111dc927947a95cfb3e9ee7c52acfa35a054e8c2f3b838b888", + "root": "d1529a8c9da915cb7dd0499272baba61db480660c0aae92b67fbe69828e83c5a", + "vqvae": "544782e605ed96d60bf999243ef8f44640ea021a5655ef20af2d2853c3e18b5b", + "support": "229b764411652b2ab0f824481d6daf897f701a97223029759444fc5bc241ea22", +} + +JOINT_NAMES = ( + "pelvis_skel", + "left_hip_pitch_skel", "left_hip_roll_skel", "left_hip_yaw_skel", + "left_knee_skel", "left_ankle_pitch_skel", "left_ankle_roll_skel", "left_toe_base", + "right_hip_pitch_skel", "right_hip_roll_skel", "right_hip_yaw_skel", + "right_knee_skel", "right_ankle_pitch_skel", "right_ankle_roll_skel", "right_toe_base", + "waist_yaw_skel", "waist_roll_skel", "waist_pitch_skel", + "left_shoulder_pitch_skel", "left_shoulder_roll_skel", "left_shoulder_yaw_skel", + "left_elbow_skel", "left_wrist_roll_skel", "left_wrist_pitch_skel", + "left_wrist_yaw_skel", "left_hand_roll_skel", + "right_shoulder_pitch_skel", "right_shoulder_roll_skel", "right_shoulder_yaw_skel", + "right_elbow_skel", "right_wrist_roll_skel", "right_wrist_pitch_skel", + "right_wrist_yaw_skel", "right_hand_roll_skel", +) + + +def encoded(value: str) -> bytes: + data = value.encode("utf-8") + return struct.pack(" bytes: + return encoded(key) + struct.pack(" bytes: + return encoded(key) + struct.pack(" bytes: + return encoded(key) + struct.pack(" bytes: + return encoded(key) + struct.pack(" int: + return (value + ALIGNMENT - 1) // ALIGNMENT * ALIGNMENT + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + while block := stream.read(8 << 20): + digest.update(block) + return digest.hexdigest() + + +def compact_name(name: str) -> str: + """Keep upstream names readable while respecting GGML_MAX_NAME (64).""" + if len(name.encode("utf-8")) < 64: + return name + replacements = ( + ("_root_token_transformer_encoder.layers.", "root.l."), + ("_shared_transformer_encoder.layers.", "shared.l."), + ("_transformer_encoder.layers.", "transformer.l."), + ("self_attn.", "attn."), + ("in_proj_weight", "qkv.weight"), + ("in_proj_bias", "qkv.bias"), + ("external_cond_blocks.", "xcond."), + ("target_cond_blocks.", "tcond."), + ) + output = name + for old, new in replacements: + output = output.replace(old, new) + if len(output.encode("utf-8")) >= 64: + suffix = hashlib.sha256(name.encode("utf-8")).hexdigest()[:12] + output = output.encode("utf-8")[:48].decode("utf-8", "ignore") + "." + suffix + return output + + +@dataclass +class Tensor: + name: str + value: np.ndarray + kind: int + dimensions: tuple[int, ...] + offset: int + + @property + def size(self) -> int: + return int(self.value.size) * 4 + + def bytes(self) -> bytes: + dtype = " list[tuple[str, np.ndarray]]: + output: list[tuple[str, np.ndarray]] = [] + with safe_open(path, framework="numpy") as source: + for original in source.keys(): + if not select(original): + continue + name = original.removeprefix(strip) + value = source.get_tensor(original) + output.append((name, value)) + if not output: + raise ValueError(f"selection produced no tensors from {path}") + return output + + +def tensor_kind(component: str, name: str, value: np.ndarray) -> int: + if np.issubdtype(value.dtype, np.floating): + return GGML_TYPE_F32 + if np.issubdtype(value.dtype, np.integer) or np.issubdtype(value.dtype, np.bool_): + return GGML_TYPE_I32 + raise ValueError(f"unsupported tensor dtype for {component}:{name}: {value.dtype}") + + +def write_component(path: Path, component: str, values: list[tuple[str, np.ndarray]], + source_hash: str, *, general_name: str = "NVIDIA MotionBricks G1", + extra_metadata: list[bytes] | None = None) -> dict[str, object]: + tensors: list[Tensor] = [] + names: set[str] = set() + offset = 0 + parameter_count = 0 + for original_name, value in sorted(values): + name = compact_name(original_name) + if name in names: + raise ValueError(f"compacted tensor name collision: {name}") + names.add(name) + if value.ndim > 4: + raise ValueError(f"GGML supports at most four dimensions: {name} has {value.shape}") + kind = tensor_kind(component, name, value) + shape = tuple(int(item) for item in value.shape) or (1,) + tensor = Tensor(name, value, kind, tuple(reversed(shape)), offset) + tensors.append(tensor) + parameter_count += int(value.size) + offset = aligned(offset + tensor.size) + + metadata = [ + kv_string("general.architecture", "motionbricks"), + kv_string("general.name", general_name), + kv_u32("general.alignment", ALIGNMENT), + kv_u32("general.file_type", 0), + kv_u32("motionbricks.format_version", 1), + kv_string("motionbricks.component", component), + kv_string("motionbricks.skeleton", "g1skel34"), + kv_string("motionbricks.upstream_revision", UPSTREAM_REVISION), + kv_string("motionbricks.source_sha256", source_hash), + kv_u64("motionbricks.parameter_count", parameter_count), + ] + if extra_metadata: + metadata.extend(extra_metadata) + if component == "support": + metadata.append(kv_string("motionbricks.joint_names", ",".join(JOINT_NAMES))) + + header = bytearray(GGUF_MAGIC) + header += struct.pack(" None: + parser = argparse.ArgumentParser() + parser.add_argument("--safe-directory", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + + safe_manifest = json.loads((args.safe_directory / "manifest.json").read_text(encoding="utf-8")) + if safe_manifest.get("format") != SAFE_FORMAT: + raise ValueError("not a compatible trusted-extraction manifest") + if safe_manifest.get("upstream_revision") != UPSTREAM_REVISION: + raise ValueError("trusted extraction came from a different upstream revision") + for component, expected in SAFE_HASHES.items(): + path = args.safe_directory / f"{component}.safetensors" + actual = sha256(path) + recorded = safe_manifest["components"][component]["sha256"] + if actual != expected or recorded != expected: + raise ValueError(f"safe input hash mismatch for {component}: {actual}") + + selections = { + "pose": load_selected( + args.safe_directory / "pose.safetensors", + lambda name: name.startswith("backbone_net.") and name != "backbone_net.initted", + "backbone_net.", + ), + "root": load_selected( + args.safe_directory / "root.safetensors", + lambda name: name.startswith("backbone_net."), + "backbone_net.", + ), + "vq-decoder": load_selected( + args.safe_directory / "vqvae.safetensors", + lambda name: name.startswith("pose_net.decoder.") + or name == "pose_net.quantizer.vq._codebook.embed", + "pose_net.", + ), + "support": load_selected( + args.safe_directory / "support.safetensors", lambda _name: True, "" + ), + } + source_for = {"pose": "pose", "root": "root", "vq-decoder": "vqvae", "support": "support"} + expected = { + "pose": (209, 136_588_272), + "root": (150, 34_122_833), + "vq-decoder": (51, 12_437_277), + "support": (4, 972), + } + components: dict[str, object] = {} + for component, values in selections.items(): + actual = (len(values), sum(int(value.size) for _, value in values)) + if actual != expected[component]: + raise ValueError(f"unexpected {component} inventory: {actual}, expected {expected[component]}") + source = source_for[component] + components[component] = write_component( + args.output / f"{component}.gguf", component, values, SAFE_HASHES[source] + ) + + bundle = { + "format": BUNDLE_FORMAT, + "upstream_revision": UPSTREAM_REVISION, + "skeleton": "g1skel34", + "joint_count": len(JOINT_NAMES), + "inference_parameter_count": sum( + int(components[name]["parameter_count"]) for name in ("pose", "root", "vq-decoder") + ), + "components": components, + "safe_manifest_sha256": sha256(args.safe_directory / "manifest.json"), + } + temporary = args.output / "manifest.json.tmp" + temporary.write_text(json.dumps(bundle, indent=2, sort_keys=True) + "\n", encoding="utf-8") + os.replace(temporary, args.output / "manifest.json") + print(f"inference parameters: {bundle['inference_parameter_count']:,}") + + +if __name__ == "__main__": + main() diff --git a/scripts/download_gguf_weights.py b/scripts/download_gguf_weights.py new file mode 100644 index 0000000000000000000000000000000000000000..dee9fe705c1b6c7505fc64ec67561862bd3b934b --- /dev/null +++ b/scripts/download_gguf_weights.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""Download and verify the published MotionBricks GGUF/style distribution.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import shutil +import sys +import urllib.parse +import urllib.request +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +DEFAULT_REPO = "LocalAI-io/MotionBricks-G1-GGML" +DEFAULT_REVISION = "cc2a47603dbc203a4f18f35dd06ed3611833f506" +DEFAULT_MANIFEST = ROOT / "scripts/hf/MotionBricks-G1-GGML/MANIFEST.json" +FORMAT = "motionbricks-gguf-distribution-v1" +UPSTREAM_REVISION = "a0732b642c0333077e127a2f56ab0014c196bca4" + + +def digest(path: Path) -> str: + value = hashlib.sha256() + with path.open("rb") as stream: + for block in iter(lambda: stream.read(8 << 20), b""): + value.update(block) + return value.hexdigest() + + +def valid(path: Path, entry: dict[str, object]) -> bool: + return (path.is_file() and path.stat().st_size == entry["bytes"] + and digest(path) == entry["sha256"]) + + +def safe_relative(value: object) -> Path: + path = Path(str(value)) + if path.is_absolute() or ".." in path.parts or not path.parts: + raise ValueError(f"unsafe distribution path: {value!r}") + if path.parts[0] not in {"g1-f32", "styles"}: + raise ValueError(f"unexpected distribution path: {value!r}") + if path.suffix not in {".gguf", ".mbstyle", ".json"}: + raise ValueError(f"unexpected distribution file type: {value!r}") + return path + + +def load_manifest(path: Path, repo: str) -> dict[str, object]: + value = json.loads(path.read_text(encoding="utf-8")) + if value.get("format") != FORMAT: + raise ValueError(f"unsupported manifest format in {path}") + if value.get("repository") != repo: + raise ValueError(f"manifest is for {value.get('repository')}, not {repo}") + if value.get("upstream_revision") != UPSTREAM_REVISION: + raise ValueError("manifest refers to an unexpected upstream revision") + files = value.get("files") + if not isinstance(files, list) or not files: + raise ValueError("manifest has no files") + for entry in files: + if (not isinstance(entry, dict) or not isinstance(entry.get("bytes"), int) + or entry["bytes"] <= 0 or not isinstance(entry.get("sha256"), str) + or len(entry["sha256"]) != 64): + raise ValueError("manifest contains a malformed file record") + safe_relative(entry.get("path")) + return value + + +def download(url: str, destination: Path) -> None: + destination.parent.mkdir(parents=True, exist_ok=True) + temporary = destination.with_name(destination.name + ".download") + request = urllib.request.Request(url, headers={"User-Agent": "motion-bricks.cpp/0.1"}) + try: + with urllib.request.urlopen(request) as response, temporary.open("wb") as output: + shutil.copyfileobj(response, output, length=8 << 20) + output.flush() + os.fsync(output.fileno()) + os.replace(temporary, destination) + finally: + temporary.unlink(missing_ok=True) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--output", type=Path, default=ROOT / "generated", + help="destination containing g1-f32/ and styles/") + parser.add_argument("--repo", default=DEFAULT_REPO, help="Hugging Face model repository") + parser.add_argument("--revision", default=DEFAULT_REVISION, + help="pinned Hugging Face commit or revision") + parser.add_argument("--manifest", type=Path, default=DEFAULT_MANIFEST, + help="version-controlled distribution manifest") + args = parser.parse_args() + + try: + manifest = load_manifest(args.manifest, args.repo) + quoted_repo = urllib.parse.quote(args.repo, safe="/") + quoted_revision = urllib.parse.quote(args.revision, safe="") + base = f"https://huggingface.co/{quoted_repo}/resolve/{quoted_revision}" + for entry in manifest["files"]: + relative = safe_relative(entry["path"]) + destination = args.output / relative + if valid(destination, entry): + print(f"verified {destination}") + continue + print(f"downloading {relative} from {args.repo}@{args.revision}") + download(f"{base}/{urllib.parse.quote(relative.as_posix(), safe='/')}", destination) + if not valid(destination, entry): + raise ValueError(f"downloaded file failed size/SHA-256 verification: {destination}") + except (OSError, ValueError, urllib.error.URLError, json.JSONDecodeError) as error: + print(f"error: {error}", file=sys.stderr) + return 1 + print("MotionBricks GGUF and style bundles are present and verified") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/hf/MotionBricks-G1-GGML/MANIFEST.json b/scripts/hf/MotionBricks-G1-GGML/MANIFEST.json new file mode 100644 index 0000000000000000000000000000000000000000..63ee74c87685125a34604d0af01384449ab33ad5 --- /dev/null +++ b/scripts/hf/MotionBricks-G1-GGML/MANIFEST.json @@ -0,0 +1,114 @@ +{ + "files": [ + { + "bytes": 1101, + "path": "g1-f32/manifest.json", + "sha256": "35321e8f760316e9a0e73619e23af4089deeedcc8efe7e7266fddbdb8d3c3cfc" + }, + { + "bytes": 546369984, + "path": "g1-f32/pose.gguf", + "sha256": "6db82e5a67a355052f283bbcab104b71ecfc333dc8d9017c3812eb6f9650756e" + }, + { + "bytes": 136504000, + "path": "g1-f32/root.gguf", + "sha256": "46268d77e5c15449d68a39ce6ec7145e9eb457c5114d5dca934bd3ab539b29db" + }, + { + "bytes": 5472, + "path": "g1-f32/support.gguf", + "sha256": "5d41cae4bc494e612ef19e25f599351aafbc9ef1cb46d4ac3f1e42bb7ab07200" + }, + { + "bytes": 49753440, + "path": "g1-f32/vq-decoder.gguf", + "sha256": "6a1114b6906b07fab9deb18d602b4e731e1b5794318b1643ffd0605692f62d1e" + }, + { + "bytes": 9408, + "path": "styles/elbow_crawling.mbstyle", + "sha256": "8ce3e04aa92d91628f10824a2f19a0cc0ba5a9c16008cd95e091b05c9528dc54" + }, + { + "bytes": 50624, + "path": "styles/hand_crawling.mbstyle", + "sha256": "5687f3cd037d7d0942df180727729e04c08d6f0af48b32e9bf793729c5c69fd8" + }, + { + "bytes": 50624, + "path": "styles/idle.mbstyle", + "sha256": "ae800a700d29a8d1082d32c2c82e893aa292c6c18848ffd2c0c67c6f4d64531e" + }, + { + "bytes": 14304, + "path": "styles/injured_walk.mbstyle", + "sha256": "b7b62643281b46ceb0a37fc336f201565b595afda21e93a2cbfc3dda3509a779" + }, + { + "bytes": 3114, + "path": "styles/manifest.json", + "sha256": "762f7eadb5653af53bb9a03f812b724845b3af6967b76c5f52546de97f1edac4" + }, + { + "bytes": 50624, + "path": "styles/slow_walk.mbstyle", + "sha256": "9e59ec536d22f61c53b430aecabbef92e694d6ab5caef077ac7d0cfbca6d73f0" + }, + { + "bytes": 34112, + "path": "styles/stealth_walk.mbstyle", + "sha256": "4d2d82e40f9f43c1c15c201b45e30dfb194e4a28cf7fd8a23870394c4589d87a" + }, + { + "bytes": 50624, + "path": "styles/walk.mbstyle", + "sha256": "c1657f56795f26ac22ab944cc6676dd9a7c12e37fc654b08e744a18963597d59" + }, + { + "bytes": 17664, + "path": "styles/walk_boxing.mbstyle", + "sha256": "32349902a37d79f049e52a98929680ff18f9300a270ace1709f16d5d87d70892" + }, + { + "bytes": 126400, + "path": "styles/walk_gun.mbstyle", + "sha256": "6b5f0f6febb92b1fbb090f246e48c4762c23f7d101bc2b371fd93a13a1425245" + }, + { + "bytes": 34112, + "path": "styles/walk_happy_dance.mbstyle", + "sha256": "0900c03fbdcaf4f5fac8e4e3d4ebde5965a8cf561a4ecab07db256a136f3e2e8" + }, + { + "bytes": 9408, + "path": "styles/walk_left.mbstyle", + "sha256": "7660e369e64bc711b153caba744c1ccb3d763060f87b368969fd1ec4819694e5" + }, + { + "bytes": 9408, + "path": "styles/walk_right.mbstyle", + "sha256": "106419603b7844d3f2f804b4debbff47b1ec71969f8dd4c4b4dbaef36d4e5c83" + }, + { + "bytes": 149504, + "path": "styles/walk_scared.mbstyle", + "sha256": "b88776e3d57c7008e94e2a51c8da19d0715a3a42565b47c2019cabb40b13578d" + }, + { + "bytes": 34112, + "path": "styles/walk_stealth.mbstyle", + "sha256": "bbb4b69e29cd78cad102da433dc553f6dc16b6710f9a7d4c7df0148f1492f778" + }, + { + "bytes": 149504, + "path": "styles/walk_zombie.mbstyle", + "sha256": "9ce2769010ecb20c5ad38d36f841ec72e29dd1e4556dff8f0ecb258855a9705b" + } + ], + "format": "motionbricks-gguf-distribution-v1", + "repository": "LocalAI-io/MotionBricks-G1-GGML", + "upstream_license": "NVIDIA Open Model License", + "upstream_repository": "NVlabs/GR00T-WholeBodyControl", + "upstream_revision": "a0732b642c0333077e127a2f56ab0014c196bca4" +} diff --git a/scripts/hf/MotionBricks-G1-GGML/NOTICE b/scripts/hf/MotionBricks-G1-GGML/NOTICE new file mode 100644 index 0000000000000000000000000000000000000000..56bf8db4d52be5ef8ee281146e75e69cec919c15 --- /dev/null +++ b/scripts/hf/MotionBricks-G1-GGML/NOTICE @@ -0,0 +1,5 @@ +Licensed by NVIDIA Corporation under the NVIDIA Open Model License. + +Upstream model: NVlabs/GR00T-WholeBodyControl, motionbricks/, revision +a0732b642c0333077e127a2f56ab0014c196bca4. This GGUF conversion grants no +additional rights. diff --git a/scripts/hf/MotionBricks-G1-GGML/README.md b/scripts/hf/MotionBricks-G1-GGML/README.md new file mode 100644 index 0000000000000000000000000000000000000000..22b3a98fb4f7bfe9ce65115c98b59e6b0b060d00 --- /dev/null +++ b/scripts/hf/MotionBricks-G1-GGML/README.md @@ -0,0 +1,36 @@ +--- +license: other +library_name: ggml +tags: [gguf, ggml, motion-generation, unitree-g1, motionbricks] +--- + +# MotionBricks-G1-GGML + +Native F32 GGML/GGUF conversion of NVIDIA MotionBricks' released G1 pose, +root, and VQ-decoder checkpoints, plus the 15 released style primitives. It is +produced by +[`motion-bricks.cpp`](https://github.com/localai-org/motion-bricks.cpp) for CPU and +Vulkan inference. + +NVIDIA currently publishes MotionBricks through Git LFS rather than a separate +Hugging Face model repository. This conversion is linked to the pinned +[`NVlabs/GR00T-WholeBodyControl` revision](https://github.com/NVlabs/GR00T-WholeBodyControl/tree/a0732b642c0333077e127a2f56ab0014c196bca4/motionbricks). + +Install the complete checked bundle at `generated/g1-f32` and +`generated/styles` with: + +```sh +python scripts/download_gguf_weights.py +``` + +`MANIFEST.json` and `SHA256SUMS` record the source revision, exact paths, +sizes, and SHA-256 hashes. The bundle contains 183,148,382 learned F32 +parameters and targets MotionBricks' released 34-joint Unitree G1 skeleton. + +## License and provenance + +The original checkpoints and this converted representation remain subject to +the NVIDIA Open Model License in `UPSTREAM_LICENSE`. Redistribution requires +retaining that agreement and NVIDIA's attribution in `NOTICE`. This conversion +grants no additional rights. Use is also subject to NVIDIA's Trustworthy AI +and trade-compliance terms described by that agreement. diff --git a/scripts/hf/MotionBricks-G1-GGML/UPSTREAM_LICENSE b/scripts/hf/MotionBricks-G1-GGML/UPSTREAM_LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..20cd33bfbb5962b2efa418c9be7f005b6e043ad4 --- /dev/null +++ b/scripts/hf/MotionBricks-G1-GGML/UPSTREAM_LICENSE @@ -0,0 +1,186 @@ +================================================================================ +DUAL LICENSE NOTICE +================================================================================ + +This repository is dual-licensed. Different components are under different terms: + +1. SOURCE CODE - Apache License 2.0 + All source code, scripts, and software components + +2. MODEL WEIGHTS - NVIDIA Open Model License + All trained model checkpoints and weights + +See below for the full text of each license. + +================================================================================ +PART 1: SOURCE CODE LICENSE (Apache License 2.0) +================================================================================ + +Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +================================================================================ +PART 2: MODEL WEIGHTS LICENSE (NVIDIA Open Model License) +================================================================================ + +NVIDIA OPEN MODEL LICENSE AGREEMENT + +Last Modified: October 24, 2025 + +NVIDIA Corporation and its affiliates ("NVIDIA") grants permission to use machine +learning models under specific conditions. Key permissions include creating +derivative models and distributing them, with NVIDIA retaining no ownership claims +over outputs generated by users. + +SECTION 1: DEFINITIONS + +1.1 "Derivative Model" means any modification of, or works based on or derived +from, the Model, excluding outputs. + +1.2 "Legal Entity" means the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. + +1.3 "Model" means the machine learning model, software, and any checkpoints, +weights, algorithms, parameters, configuration files, and documentation that NVIDIA +makes available under this Agreement. + +1.4 "NVIDIA Cosmos Model" means a multimodal Model that is covered by this Agreement. + +1.5 "Special-Purpose Model" means a Model that is limited to narrow, +purpose-specific tasks. + +1.6 "You" or "Your" means an individual or Legal Entity exercising permissions +granted by this Agreement. + +SECTION 2: CONDITIONS FOR USE, LICENSE GRANT, AI ETHICS AND IP OWNERSHIP + +2.1 Conditions for Use. You must comply with all terms and conditions of this +Agreement. If You initiate copyright or patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the Model +constitutes direct or contributory infringement, then Your licenses under this +Agreement shall terminate. If You circumvent any safety guardrails or safety +measures built in to the Model without providing comparable alternatives, Your +rights under this Agreement shall terminate. NVIDIA may update this Agreement at +any time to comply with applicable law; Your continued use constitutes Your +acceptance of the updated terms. + +2.2 License Grant. Subject to the terms and conditions of this Agreement, NVIDIA +hereby grants You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +revocable license to publicly perform, publicly display, reproduce, use, create +derivative works of, make, have made, sell, offer for sale, distribute and import +the Model. + +2.3 AI Ethics. Your use of the Model must be in accordance with NVIDIA's +Trustworthy AI terms, which can be found at +https://www.nvidia.com/en-us/agreements/trustworthy-ai/terms/. + +2.4 IP Ownership. NVIDIA owns the original Model and NVIDIA's Derivative Models. +You own Your Derivative Models. NVIDIA makes no claim of ownership to outputs. You +are responsible for outputs and their subsequent uses. + +SECTION 3: REDISTRIBUTION + +You may reproduce and distribute copies of the Model or Derivative Models thereof, +with or without modifications, provided that You meet the following conditions: + +a. You must include a copy of this Agreement. + +b. You must include the following attribution notice, which can appear in the same +location as other third-party notices or license information: "Licensed by NVIDIA +Corporation under the NVIDIA Open Model License." + +c. If You are distributing a NVIDIA Cosmos Model, You must also include the phrase +"Built on NVIDIA Cosmos" on the applicable website, in the user interface, in a +blog, in an "about" page, or in product documentation. + +d. You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications or for any Derivative Models as a whole, +provided Your use, reproduction, and distribution otherwise complies with this +Agreement. + +SECTION 4: SEPARATE COMPONENTS + +The Model may contain components that are subject to separate legal notices or +governed by separate licenses (including Open Source Software Licenses), as may be +described in any files made available with the Model. Your use of those separate +components is subject to the applicable license. This Agreement shall control over +the separate licenses for third-party Open Source Software to the extent that the +separate license imposes additional restrictions. "Open Source Software License" +means any software license approved by the Open Source Initiative, Free Software +Foundation, or similar recognized organization, or a license identified by SPDX. + +SECTION 5: TRADEMARKS + +This Agreement does not grant permission to use the trade names, trademarks, +service marks, or product names of NVIDIA, except as required for reasonable and +customary use in describing the origin of the Model and reproducing the content of +the notice. + +SECTION 6: DISCLAIMER OF WARRANTY + +NVIDIA provides the Model on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +ANY KIND, either express or implied, including, without limitation, any warranties +or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for reviewing the documentation +accompanying the Model and determining the appropriateness of using the Model, and +You understand that Special-Purpose Models are limited to narrow, purpose-specific +tasks and must not be deployed for uses that are beyond such tasks. + +SECTION 7: LIMITATION OF LIABILITY + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate and +grossly negligent acts) or agreed to in writing, will NVIDIA be liable to You for +damages, including any direct, indirect, special, incidental, or consequential +damages of any character arising as a result of this Agreement or out of the use +or inability to use the Model or Derivative Models or outputs (including but not +limited to damages for loss of goodwill, work stoppage, computer failure or +malfunction, or any and all other commercial damages or losses), even if NVIDIA has +been advised of the possibility of such damages. + +SECTION 8: INDEMNITY + +You will defend, indemnify and hold harmless NVIDIA and its affiliates, and their +respective employees, contractors, directors, officers and agents, from and against +any and all claims, damages, obligations, losses, liabilities, costs or debt, and +expenses (including but not limited to attorney's fees) arising from Your use or +distribution of the Model or Derivative Models or outputs. + +SECTION 9: FEEDBACK + +NVIDIA may use feedback You provide without restriction and without any +compensation to You. + +SECTION 10: GOVERNING LAW + +This Agreement will be governed in all respects by the laws of the United States +and of the State of Delaware, without regard to conflict of laws provisions. The +federal and state courts residing in Santa Clara County, California shall have +exclusive jurisdiction over any dispute arising out of this Agreement, and You +hereby consent to the personal jurisdiction of such courts. However, NVIDIA shall +have the right to seek injunctive relief in any court of competent jurisdiction. + +SECTION 11: TRADE AND COMPLIANCE + +You shall comply with all applicable import, export, trade, and economic sanctions +laws, including without limitation the Export Administration Regulations and +economic sanctions laws implemented by the Office of Foreign Assets Control, that +restrict or govern the destination, end-user and end-use of NVIDIA products, +technology, software, and services. + +--- + +Version Release Date: October 24, 2025 diff --git a/scripts/publish_gguf.py b/scripts/publish_gguf.py new file mode 100644 index 0000000000000000000000000000000000000000..39701ce344d4391dfc07e201c1508f70d99e885a --- /dev/null +++ b/scripts/publish_gguf.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""Validate and optionally publish the MotionBricks GGUF distribution to HF.""" + +from __future__ import annotations + +import argparse +import hashlib +import io +import json +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +HF_REPO = "LocalAI-io/MotionBricks-G1-GGML" +UPSTREAM_REPO = "NVlabs/GR00T-WholeBodyControl" +UPSTREAM_REVISION = "a0732b642c0333077e127a2f56ab0014c196bca4" +FORMAT = "motionbricks-gguf-distribution-v1" +CARD_DIR = ROOT / "scripts/hf/MotionBricks-G1-GGML" + + +def digest(path: Path) -> str: + value = hashlib.sha256() + with path.open("rb") as stream: + for block in iter(lambda: stream.read(8 << 20), b""): + value.update(block) + return value.hexdigest() + + +def checked_json(path: Path, expected_format: str) -> dict[str, object]: + if not path.is_file(): + raise ValueError(f"missing manifest: {path}") + value = json.loads(path.read_text(encoding="utf-8")) + if value.get("format") != expected_format: + raise ValueError(f"unexpected format in {path}") + return value + + +def artifacts(model: Path, styles: Path) -> list[tuple[Path, str]]: + model_manifest = checked_json(model / "manifest.json", "motionbricks-gguf-bundle-v1") + style_manifest = checked_json(styles / "manifest.json", "motionbricks-style-bundle-v1") + if model_manifest.get("upstream_revision") != UPSTREAM_REVISION: + raise ValueError("model bundle came from an unexpected upstream revision") + result = [(model / "manifest.json", "g1-f32/manifest.json")] + for record in model_manifest["components"].values(): + source = model / record["path"] + if digest(source) != record["sha256"]: + raise ValueError(f"model manifest SHA-256 mismatch: {source}") + result.append((source, f"g1-f32/{source.name}")) + result.append((styles / "manifest.json", "styles/manifest.json")) + for record in style_manifest["styles"].values(): + source = styles / record["path"] + if digest(source) != record["sha256"]: + raise ValueError(f"style manifest SHA-256 mismatch: {source}") + result.append((source, f"styles/{source.name}")) + for source, destination in result: + if not source.is_file() or source.stat().st_size == 0: + raise ValueError(f"missing or empty artifact: {source}") + if source.suffix in {".gguf", ".mbstyle"}: + with source.open("rb") as stream: + if stream.read(4) != b"GGUF": + raise ValueError(f"invalid GGUF magic: {source}") + path = Path(destination) + if path.is_absolute() or ".." in path.parts: + raise ValueError(f"unsafe destination: {destination}") + return sorted(result, key=lambda item: item[1]) + + +def distribution_manifest(repo: str, files: list[tuple[Path, str]]) -> tuple[dict[str, object], bytes]: + records = [ + {"path": destination, "bytes": source.stat().st_size, "sha256": digest(source)} + for source, destination in files + ] + value = { + "format": FORMAT, + "repository": repo, + "upstream_repository": UPSTREAM_REPO, + "upstream_revision": UPSTREAM_REVISION, + "upstream_license": "NVIDIA Open Model License", + "files": records, + } + payload = json.dumps(value, indent=2, sort_keys=True).encode() + b"\n" + return value, payload + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--model", type=Path, default=ROOT / "generated/g1-f32") + parser.add_argument("--styles", type=Path, default=ROOT / "generated/styles") + parser.add_argument("--repo", default=HF_REPO) + parser.add_argument("--write-manifest", type=Path, + help="write the checked distribution manifest to this path") + parser.add_argument("--upload", action="store_true") + parser.add_argument("--confirm-upstream-licence", action="store_true", + help="required with --upload; confirms redistribution conditions were reviewed") + args = parser.parse_args() + + try: + for required in (CARD_DIR / "README.md", CARD_DIR / "NOTICE", CARD_DIR / "UPSTREAM_LICENSE"): + if not required.is_file(): + raise ValueError(f"missing publication asset: {required}") + files = artifacts(args.model, args.styles) + manifest, manifest_payload = distribution_manifest(args.repo, files) + except (OSError, ValueError, KeyError, json.JSONDecodeError) as error: + print(f"error: {error}", file=sys.stderr) + return 1 + + sums = "".join(f"{entry['sha256']} {entry['path']}\n" for entry in manifest["files"]) + total = sum(entry["bytes"] for entry in manifest["files"]) + print(f"repo: https://huggingface.co/{args.repo}") + print(f"files: {len(files)} model/style artifacts, {total / 1e9:.3f} GB") + for entry in manifest["files"]: + print(f" {entry['sha256']} {entry['bytes']:>12} {entry['path']}") + if args.write_manifest: + args.write_manifest.parent.mkdir(parents=True, exist_ok=True) + args.write_manifest.write_bytes(manifest_payload) + print(f"wrote {args.write_manifest}") + if not args.upload: + print("[dry-run] nothing uploaded") + return 0 + if not args.confirm_upstream_licence: + print("error: --upload requires --confirm-upstream-licence", file=sys.stderr) + return 2 + + try: + from huggingface_hub import CommitOperationAdd, HfApi + + api = HfApi() + api.create_repo(args.repo, repo_type="model", exist_ok=True) + operations = [ + CommitOperationAdd(path_in_repo="README.md", path_or_fileobj=CARD_DIR / "README.md"), + CommitOperationAdd(path_in_repo="NOTICE", path_or_fileobj=CARD_DIR / "NOTICE"), + CommitOperationAdd(path_in_repo="UPSTREAM_LICENSE", path_or_fileobj=CARD_DIR / "UPSTREAM_LICENSE"), + *(CommitOperationAdd(path_in_repo=destination, path_or_fileobj=source) + for source, destination in files), + CommitOperationAdd(path_in_repo="MANIFEST.json", path_or_fileobj=io.BytesIO(manifest_payload)), + CommitOperationAdd(path_in_repo="SHA256SUMS", path_or_fileobj=io.BytesIO(sums.encode())), + ] + commit = api.create_commit( + repo_id=args.repo, repo_type="model", operations=operations, + commit_message=f"Publish MotionBricks G1 GGUF bundle from {UPSTREAM_REVISION[:12]}", + ) + except Exception as error: # huggingface_hub supplies the useful API detail + print(f"error: Hugging Face upload failed: {error}", file=sys.stderr) + return 1 + print(f"published: {commit.commit_url}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/agent.cpp b/src/agent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..21b63ae24d5b3a1a84134cda220acc0c6989a2ae --- /dev/null +++ b/src/agent.cpp @@ -0,0 +1,266 @@ +#include "agent.hpp" + +#include "handles.hpp" +#include "motion_rep.hpp" +#include "planner.hpp" + +#include +#include +#include +#include + +namespace motionbricks::detail { +namespace { + +using mat3 = std::array; + +mat3 multiply(const mat3 & a, const mat3 & b) { + mat3 result{}; + for (unsigned row = 0; row < 3U; ++row) + for (unsigned column = 0; column < 3U; ++column) + for (unsigned k = 0; k < 3U; ++k) + result[row * 3U + column] += a[row * 3U + k] * b[k * 3U + column]; + return result; +} + +mat3 transpose(const mat3 & value) { + return {value[0],value[3],value[6],value[1],value[4],value[7],value[2],value[5],value[8]}; +} + +mat3 y_rotation(float angle) { + const float cosine = std::cos(angle), sine = std::sin(angle); + return {cosine,0.0F,sine, 0.0F,1.0F,0.0F, -sine,0.0F,cosine}; +} + +std::array transform(const mat3 & matrix, const float * value) { + return {matrix[0]*value[0] + matrix[1]*value[1] + matrix[2]*value[2], + matrix[3]*value[0] + matrix[4]*value[1] + matrix[5]*value[2], + matrix[6]*value[0] + matrix[7]*value[1] + matrix[8]*value[2]}; +} + +std::array matrix_xyzw(const mat3 & matrix) { + std::array wxyz{}; + const float trace = matrix[0] + matrix[4] + matrix[8]; + if (trace > 0.0F) { + const float s = 2.0F * std::sqrt(std::max(0.0F, trace + 1.0F)); + wxyz = {0.25F*s, (matrix[7]-matrix[5])/s, (matrix[2]-matrix[6])/s, + (matrix[3]-matrix[1])/s}; + } else if (matrix[0] > matrix[4] && matrix[0] > matrix[8]) { + const float s = 2.0F * std::sqrt(std::max(0.0F, 1.0F+matrix[0]-matrix[4]-matrix[8])); + wxyz = {(matrix[7]-matrix[5])/s,0.25F*s,(matrix[1]+matrix[3])/s,(matrix[2]+matrix[6])/s}; + } else if (matrix[4] > matrix[8]) { + const float s = 2.0F * std::sqrt(std::max(0.0F, 1.0F+matrix[4]-matrix[0]-matrix[8])); + wxyz = {(matrix[2]-matrix[6])/s,(matrix[1]+matrix[3])/s,0.25F*s,(matrix[5]+matrix[7])/s}; + } else { + const float s = 2.0F * std::sqrt(std::max(0.0F, 1.0F+matrix[8]-matrix[0]-matrix[4])); + wxyz = {(matrix[3]-matrix[1])/s,(matrix[2]+matrix[6])/s,(matrix[5]+matrix[7])/s,0.25F*s}; + } + const float norm = std::sqrt(wxyz[0]*wxyz[0]+wxyz[1]*wxyz[1]+wxyz[2]*wxyz[2]+wxyz[3]*wxyz[3]); + for (float & value : wxyz) value /= norm; + return {wxyz[1],wxyz[2],wxyz[3],wxyz[0]}; +} + +float wrap(float value) { + constexpr float pi = 3.14159265358979323846F; + return std::remainder(value, 2.0F*pi); +} + +float spring(float current, float velocity, float target, float half_life, float time) { + constexpr float ln2 = 0.69314718056F; + const float y = (4.0F * ln2) / (half_life + 1.0e-5F) / 2.0F; + const float x = y * time; + const float decay = 1.0F / (1.0F + x + 0.48F*x*x + 0.235F*x*x*x); + const float j0 = current - target; + const float j1 = velocity + j0*y; + return (j0 + j1*time)*decay + target; +} + +mb_status build_constraints(mb_agent & agent, const mb_command & command, + const mb_style & style, transition_constraints & result, + std::string & reason) { + encoded_frames context; + auto status = encode_context(*agent.model, agent.context_root_xyz, + agent.context_local_rotations_xyzw, agent.context_frames, context, reason); + if (status != MB_OK) return status; + std::copy(context.global_root.begin(), context.global_root.end(), result.global_root.begin()); + std::copy(context.local_root.begin(), context.local_root.end(), result.local_root.begin()); + std::copy(context.poses.begin(), context.poses.end(), result.poses.begin()); + result.has_local_root[3] = 0U; + result.allowed_tokens = style.allowed_tokens; + + const float current_x = context.global_root[0], current_z = context.global_root[2]; + const float velocity_x = (context.global_root[5] - current_x) * 30.0F; + const float velocity_z = (context.global_root[7] - current_z) * 30.0F; + float direction_x = command.movement_direction[0]; + float direction_z = command.movement_direction[2]; + const float direction_norm = std::hypot(direction_x, direction_z); + if (direction_norm > 1.0e-5F) { direction_x /= direction_norm; direction_z /= direction_norm; } + else { direction_x = 0.0F; direction_z = 0.0F; } + const float desired_speed = command.target_speed >= 0.0F ? command.target_speed : style.speed; + const float target_x = command.has_world_target != 0U ? command.world_target[0] + : current_x + 2.0F * desired_speed * direction_x; + const float target_z = command.has_world_target != 0U ? command.world_target[2] + : current_z + 2.0F * desired_speed * direction_z; + + const float current_heading = std::atan2(context.global_root[4], context.global_root[3]); + const float next_heading = std::atan2(context.global_root[9], context.global_root[8]); + const float heading_velocity = wrap(next_heading - current_heading) * 30.0F; + float desired_heading = command.has_world_target != 0U ? command.world_target_heading + : std::atan2(command.facing_direction[0], command.facing_direction[2]); + desired_heading = current_heading + wrap(desired_heading - current_heading); + + std::array target_root_x{}, target_root_z{}, target_heading{}; + for (unsigned frame = 0; frame < 4U; ++frame) { + const float time = 1.0F + static_cast(frame) / 30.0F; + target_root_x[frame] = spring(current_x, velocity_x, target_x, 0.4F, time); + target_root_z[frame] = spring(current_z, velocity_z, target_z, 0.4F, time); + target_heading[frame] = spring(current_heading, heading_velocity, desired_heading, 0.17F, time); + } + const std::uint32_t first = style.frames == 4U ? 0U + : static_cast(command.seed % (style.frames - 4U)); + for (std::uint32_t frame = 0; frame < 4U; ++frame) { + const auto source_frame = first + frame; + const float correction = wrap(target_heading[frame] - style.global_headings[source_frame]); + const mat3 correction_matrix = y_rotation(correction); + auto * global = result.global_root.data() + (frame + 4U) * global_root_width; + const float * style_root_joint = style.global_joint_positions.data() + + (static_cast(source_frame) * g1_joint_count) * 3U; + global[0] = target_root_x[frame]; + global[1] = style.global_root_positions[source_frame * 3U + 1U] + style_root_joint[1]; + global[2] = target_root_z[frame]; + global[3] = std::cos(target_heading[frame]); global[4] = std::sin(target_heading[frame]); + std::copy_n(global, 3U, result.target_root_translations.data() + frame * 3U); + float * pose = result.poses.data() + (frame + 4U) * external_pose_width; + for (std::uint32_t joint = 1; joint < g1_joint_count; ++joint) { + const float * source = style.global_joint_positions.data() + + (static_cast(source_frame) * g1_joint_count + joint) * 3U; + const auto position = transform(correction_matrix, source); + std::copy(position.begin(), position.end(), pose + (joint - 1U) * 3U); + } + constexpr std::size_t rotation_offset = 99U; + std::array target_global_rotations{}; + for (std::uint32_t joint = 0; joint < g1_joint_count; ++joint) { + const float * source = style.global_joint_rotations.data() + + (static_cast(source_frame) * g1_joint_count + joint) * 9U; + mat3 source_matrix{}; + std::copy_n(source, 9, source_matrix.begin()); + const auto matrix = multiply(correction_matrix, source_matrix); + target_global_rotations[joint] = matrix; + float * six = pose + rotation_offset + joint * 6U; + six[0]=matrix[0]; six[1]=matrix[3]; six[2]=matrix[6]; + six[3]=matrix[1]; six[4]=matrix[4]; six[5]=matrix[7]; + } + for (std::uint32_t joint = 0; joint < g1_joint_count; ++joint) { + mat3 local = target_global_rotations[joint]; + const auto parent = agent.model->joint_parents[joint]; + if (parent >= 0) + local = multiply(transpose(target_global_rotations[static_cast(parent)]), + target_global_rotations[joint]); + const auto xyzw = matrix_xyzw(local); + std::copy(xyzw.begin(), xyzw.end(), result.target_local_rotations_xyzw.begin() + + static_cast((static_cast(frame) * g1_joint_count + joint) * 4U)); + } + } + for (std::uint32_t frame = 0; frame < 3U; ++frame) { + const float * current = result.global_root.data() + (frame + 4U) * global_root_width; + const float * next = current + global_root_width; + float * local = result.local_root.data() + (frame + 4U) * local_root_width; + local[0] = wrap(target_heading[frame + 1U] - target_heading[frame]) * 30.0F; + local[1] = (next[0] - current[0]) * 30.0F; + local[2] = (next[2] - current[2]) * 30.0F; + local[3] = current[1]; + } + std::copy_n(result.local_root.data() + 6U * local_root_width, local_root_width, + result.local_root.data() + 7U * local_root_width); + result.local_root[7U * local_root_width + 3U] = result.global_root[7U * global_root_width + 1U]; + return MB_OK; +} + +} // namespace + +mb_status seed_agent_from_style(mb_agent & agent, const mb_style & style, + std::string & reason) { + if (agent.model == nullptr || style.frames < 4U) { + reason = "agent or initial style is invalid"; + return MB_INVALID_ARGUMENT; + } + agent.context_frames = 4U; + agent.context_root_xyz.resize(12U); + agent.context_local_rotations_xyzw.resize(4U * g1_joint_count * 4U); + for (std::uint32_t frame = 0; frame < 4U; ++frame) { + const float * root_joint = style.global_joint_positions.data() + frame * g1_joint_count * 3U; + for (unsigned axis = 0; axis < 3U; ++axis) + agent.context_root_xyz[frame * 3U + axis] = + style.global_root_positions[frame * 3U + axis] + root_joint[axis]; + for (std::uint32_t joint = 0; joint < g1_joint_count; ++joint) { + const float * source = style.global_joint_rotations.data() + + (static_cast(frame) * g1_joint_count + joint) * 9U; + mat3 global{}; std::copy_n(source, 9, global.begin()); + const auto parent = agent.model->joint_parents[joint]; + mat3 local = global; + if (parent >= 0) { + const float * parent_source = style.global_joint_rotations.data() + + (static_cast(frame) * g1_joint_count + + static_cast(parent)) * 9U; + mat3 parent_global{}; std::copy_n(parent_source, 9, parent_global.begin()); + local = multiply(transpose(parent_global), global); + } + const auto xyzw = matrix_xyzw(local); + std::copy(xyzw.begin(), xyzw.end(), agent.context_local_rotations_xyzw.begin() + + static_cast((static_cast(frame) * g1_joint_count + joint) * 4U)); + } + } + agent.current_motion.reset(); + agent.current_frame = 0U; + agent.initial_style = &style; + return MB_OK; +} + +mb_status plan_agent(mb_agent & agent, const mb_command & command, + mb_motion & output, std::string & reason) { + const mb_style * style = command.style != nullptr ? command.style : agent.initial_style; + if (agent.model == nullptr || style == nullptr) { + reason = "agent plan requires a style"; + return MB_INVALID_ARGUMENT; + } + if (agent.context_frames < 4U) { + const auto status = seed_agent_from_style(agent, *style, reason); + if (status != MB_OK) return status; + } + transition_constraints constraints; + auto status = build_constraints(agent, command, *style, constraints, reason); + if (status != MB_OK) return status; + status = run_transition(*agent.model, constraints, output, nullptr, reason); + if (status != MB_OK) return status; + output.target_frames = 4U; + output.target_root_translations.assign(constraints.target_root_translations.begin(), + constraints.target_root_translations.end()); + output.target_local_rotations_xyzw.assign(constraints.target_local_rotations_xyzw.begin(), + constraints.target_local_rotations_xyzw.end()); + return MB_OK; +} + +mb_status advance_agent(mb_agent & agent, std::uint32_t frames, + std::string & reason) { + if (!agent.current_motion || agent.current_motion->frames == 0U) { + reason = "agent has no planned motion"; + return MB_INVALID_ARGUMENT; + } + agent.current_frame = std::min( + agent.current_frame + frames, static_cast(agent.current_motion->frames - 1U)); + agent.context_frames = 4U; + agent.context_root_xyz.resize(12U); + agent.context_local_rotations_xyzw.resize(4U * g1_joint_count * 4U); + for (std::uint32_t frame = 0; frame < 4U; ++frame) { + const auto source = std::min(agent.current_frame + frame, + agent.current_motion->frames - 1U); + std::copy_n(agent.current_motion->root_translations.data() + source * 3U, 3, + agent.context_root_xyz.data() + frame * 3U); + std::copy_n(agent.current_motion->local_rotations_xyzw.data() + source * g1_joint_count * 4U, + g1_joint_count * 4U, + agent.context_local_rotations_xyzw.data() + frame * g1_joint_count * 4U); + } + return MB_OK; +} + +} // namespace motionbricks::detail diff --git a/src/agent.hpp b/src/agent.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b3be3cf5e85415c060fab82f56c5c24c8604806d --- /dev/null +++ b/src/agent.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include +#include + +struct mb_agent; +struct mb_command; +struct mb_motion; +struct mb_style; + +namespace motionbricks::detail { + +mb_status seed_agent_from_style(mb_agent & agent, const mb_style & style, + std::string & reason); +mb_status plan_agent(mb_agent & agent, const mb_command & command, + mb_motion & output, std::string & reason); +mb_status advance_agent(mb_agent & agent, std::uint32_t frames, + std::string & reason); + +} // namespace motionbricks::detail diff --git a/src/capi.cpp b/src/capi.cpp new file mode 100644 index 0000000000000000000000000000000000000000..560cee5822aec62fa7ba822d05b068c187fd84d6 --- /dev/null +++ b/src/capi.cpp @@ -0,0 +1,552 @@ +#include + +#include "error.hpp" +#include "agent.hpp" +#include "handles.hpp" +#include "model.hpp" +#include "style.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using motionbricks::detail::fail; +using motionbricks::detail::guard; + +bool valid_device(mb_device device) noexcept { + return device == MB_DEVICE_AUTO || device == MB_DEVICE_CPU || device == MB_DEVICE_VULKAN; +} + +bool finite3(float x, float y, float z) noexcept { + return std::isfinite(x) && std::isfinite(y) && std::isfinite(z); +} + +} // namespace + +extern "C" { + +uint32_t mb_abi_version(void) { return MB_ABI_VERSION; } + +const char * mb_status_string(mb_status status) { + switch (status) { + case MB_OK: return "ok"; + case MB_INVALID_ARGUMENT: return "invalid argument"; + case MB_OUT_OF_MEMORY: return "out of memory"; + case MB_IO_ERROR: return "I/O error"; + case MB_INVALID_FORMAT: return "invalid format"; + case MB_INCOMPATIBLE_MODEL: return "incompatible model"; + case MB_BACKEND_UNAVAILABLE: return "backend unavailable"; + case MB_COMPUTE_FAILED: return "compute failed"; + case MB_NOT_IMPLEMENTED: return "not implemented"; + case MB_INTERNAL_ERROR: return "internal error"; + default: return "unknown status"; + } +} + +mb_status mb_runtime_options_create(mb_runtime_options ** output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (output == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "output is null"); + *output = nullptr; + *output = new mb_runtime_options(); + return MB_OK; + }); +} + +void mb_runtime_options_free(mb_runtime_options * value) { delete value; } + +mb_status mb_runtime_options_set_device(mb_runtime_options * value, mb_device device, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "options are null"); + if (!valid_device(device)) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "invalid device"); + value->device = device; + return MB_OK; + }); +} + +mb_status mb_runtime_options_get_device(const mb_runtime_options * value, mb_device * output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "options or output is null"); + *output = value->device; + return MB_OK; + }); +} + +mb_status mb_runtime_options_set_threads(mb_runtime_options * value, uint32_t threads, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "options are null"); + value->threads = threads; + return MB_OK; + }); +} + +mb_status mb_runtime_options_get_threads(const mb_runtime_options * value, uint32_t * output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "options or output is null"); + *output = value->threads; + return MB_OK; + }); +} + +mb_status mb_runtime_options_set_backend_directory(mb_runtime_options * value, + const char * directory, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "options are null"); + value->backend_directory = directory != nullptr ? directory : ""; + return MB_OK; + }); +} + +mb_status mb_runtime_options_get_backend_directory(const mb_runtime_options * value, + const char ** output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "options or output is null"); + *output = value->backend_directory.c_str(); + return MB_OK; + }); +} + +mb_status mb_command_create(mb_command ** output, char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (output == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "output is null"); + *output = nullptr; + *output = new mb_command(); + return MB_OK; + }); +} + +void mb_command_free(mb_command * value) { delete value; } + +mb_status mb_command_set_style(mb_command * value, const mb_style * style, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "command is null"); + value->style = style; + return MB_OK; + }); +} + +mb_status mb_command_get_style(const mb_command * value, const mb_style ** output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "command or output is null"); + *output = value->style; + return MB_OK; + }); +} + +mb_status mb_command_set_movement_direction(mb_command * value, float x, float y, float z, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "command is null"); + if (!finite3(x, y, z)) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "movement direction is not finite"); + value->movement_direction = {x, y, z}; + return MB_OK; + }); +} + +mb_status mb_command_get_movement_direction(const mb_command * value, + float * x, float * y, float * z, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || x == nullptr || y == nullptr || z == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "command or output is null"); + *x = value->movement_direction[0]; + *y = value->movement_direction[1]; + *z = value->movement_direction[2]; + return MB_OK; + }); +} + +mb_status mb_command_set_facing_direction(mb_command * value, float x, float y, float z, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "command is null"); + if (!finite3(x, y, z)) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "facing direction is not finite"); + if (x * x + y * y + z * z <= 1.0e-12F) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "facing direction is zero"); + value->facing_direction = {x, y, z}; + return MB_OK; + }); +} + +mb_status mb_command_get_facing_direction(const mb_command * value, + float * x, float * y, float * z, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || x == nullptr || y == nullptr || z == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "command or output is null"); + *x = value->facing_direction[0]; + *y = value->facing_direction[1]; + *z = value->facing_direction[2]; + return MB_OK; + }); +} + +mb_status mb_command_set_target_speed(mb_command * value, float speed, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "command is null"); + if (!std::isfinite(speed) || speed < -1.0F) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "target speed must be finite and at least -1"); + value->target_speed = speed; + return MB_OK; + }); +} + +mb_status mb_command_get_target_speed(const mb_command * value, float * output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "command or output is null"); + *output = value->target_speed; + return MB_OK; + }); +} + +mb_status mb_command_set_world_target(mb_command * value, float x, float y, float z, + float heading, uint32_t enabled, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "command is null"); + if (!finite3(x, y, z) || !std::isfinite(heading)) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "world target is not finite"); + if (enabled > 1) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "enabled must be 0 or 1"); + value->world_target = {x, y, z}; + value->world_target_heading = heading; + value->has_world_target = enabled; + return MB_OK; + }); +} + +mb_status mb_command_get_world_target(const mb_command * value, + float * x, float * y, float * z, + float * heading, uint32_t * enabled, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || x == nullptr || y == nullptr || z == nullptr || + heading == nullptr || enabled == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "command or output is null"); + *x = value->world_target[0]; + *y = value->world_target[1]; + *z = value->world_target[2]; + *heading = value->world_target_heading; + *enabled = value->has_world_target; + return MB_OK; + }); +} + +mb_status mb_command_set_seed(mb_command * value, uint64_t seed, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "command is null"); + value->seed = seed; + return MB_OK; + }); +} + +mb_status mb_command_get_seed(const mb_command * value, uint64_t * output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "command or output is null"); + *output = value->seed; + return MB_OK; + }); +} + +mb_status mb_model_load(const char * bundle_directory, const mb_runtime_options * options, mb_model ** output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (output == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "output is null"); + *output = nullptr; + if (bundle_directory == nullptr || *bundle_directory == '\0') + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "bundle directory is empty"); + if (options != nullptr && !valid_device(options->device)) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "invalid device in runtime options"); +#if !defined(MOTIONBRICKS_HAVE_VULKAN) + if (options != nullptr && options->device == MB_DEVICE_VULKAN) + return fail(MB_BACKEND_UNAVAILABLE, error, error_capacity, + "this build has no Vulkan backend"); +#endif + auto model = std::make_unique(); + std::string reason; + const auto status = motionbricks::detail::load_model_bundle(bundle_directory, options, *model, reason); + if (status != MB_OK) return fail(status, error, error_capacity, reason); + *output = model.release(); + return MB_OK; + }); +} + +void mb_model_free(mb_model * value) { delete value; } + +mb_status mb_model_get_parameter_count(const mb_model * value, uint64_t * output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "model or output is null"); + *output = value->parameter_count; + return MB_OK; + }); +} + +mb_status mb_model_get_joint_count(const mb_model * value, uint32_t * output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "model or output is null"); + *output = static_cast(value->joint_names.size()); + return MB_OK; + }); +} + +mb_status mb_model_get_joint_name(const mb_model * value, uint32_t joint, const char ** output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "model or output is null"); + if (joint >= value->joint_names.size()) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "joint index is out of range"); + *output = value->joint_names[joint].c_str(); + return MB_OK; + }); +} + +mb_status mb_model_get_joint_parent(const mb_model * value, uint32_t joint, int32_t * output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "model or output is null"); + if (joint >= value->joint_parents.size()) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "joint index is out of range"); + *output = value->joint_parents[joint]; + return MB_OK; + }); +} + +mb_status mb_model_get_neutral_joint_position(const mb_model * value, uint32_t joint, + float * x, float * y, float * z, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || x == nullptr || y == nullptr || z == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "model or output is null"); + if (joint >= value->joint_names.size() || value->neutral_joints.size() != value->joint_names.size() * 3U) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "joint index is out of range"); + *x = value->neutral_joints[static_cast(joint) * 3U]; + *y = value->neutral_joints[static_cast(joint) * 3U + 1U]; + *z = value->neutral_joints[static_cast(joint) * 3U + 2U]; + return MB_OK; + }); +} + +mb_status mb_style_load(const mb_model * model, const char * style_path, mb_style ** output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (output == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "output is null"); + *output = nullptr; + if (model == nullptr || style_path == nullptr || *style_path == '\0') + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "model or style path is null"); + auto style = std::make_unique(); + std::string reason; + const auto status = motionbricks::detail::load_style_file(style_path, *style, reason); + if (status != MB_OK) return fail(status, error, error_capacity, reason); + *output = style.release(); + return MB_OK; + }); +} + +void mb_style_free(mb_style * value) { delete value; } + +mb_status mb_style_get_name(const mb_style * value, const char ** output, char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "style or output is null"); + *output = value->name.c_str(); + return MB_OK; + }); +} + +mb_status mb_style_set_speed(mb_style * value, float speed, char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "style is null"); + if (!std::isfinite(speed) || speed < 0.0F) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "style speed must be finite and non-negative"); + value->speed = speed; + return MB_OK; + }); +} + +mb_status mb_style_get_speed(const mb_style * value, float * output, char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "style or output is null"); + *output = value->speed; + return MB_OK; + }); +} + +mb_status mb_agent_create(const mb_model * model, mb_agent ** output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (output == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "output is null"); + *output = nullptr; + if (model == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "model is null"); + auto agent = std::make_unique(); + agent->model = model; + *output = agent.release(); + return MB_OK; + }); +} + +void mb_agent_free(mb_agent * value) { delete value; } + +mb_status mb_agent_reset(mb_agent * value, const mb_style * style, char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || style == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "agent or style is null"); + std::string reason; + const auto status = motionbricks::detail::seed_agent_from_style(*value, *style, reason); + return status == MB_OK ? MB_OK : fail(status, error, error_capacity, reason); + }); +} + +mb_status mb_agent_set_context(mb_agent * value, const float * roots, const float * rotations, + uint64_t frames, uint64_t joints, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || roots == nullptr || rotations == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "agent or context data is null"); + if (frames < 4U || joints != 34U || frames > 1000000U) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "context must contain at least four frames and 34 joints"); + const auto root_count = static_cast(frames) * 3U; + const auto rotation_count = static_cast(frames) * 34U * 4U; + if (!std::all_of(roots, roots + root_count, [](float item) { return std::isfinite(item); }) || + !std::all_of(rotations, rotations + rotation_count, [](float item) { return std::isfinite(item); })) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "context contains non-finite values"); + value->context_root_xyz.assign(roots, roots + root_count); + value->context_local_rotations_xyzw.assign(rotations, rotations + rotation_count); + value->context_frames = frames; + value->current_motion.reset(); + value->current_frame = 0U; + return MB_OK; + }); +} + +mb_status mb_agent_plan(mb_agent * value, const mb_command * command, mb_motion ** output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (output == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "output is null"); + *output = nullptr; + if (value == nullptr || command == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "agent or command is null"); + auto motion = std::make_unique(); + std::string reason; + const auto status = motionbricks::detail::plan_agent(*value, *command, *motion, reason); + if (status != MB_OK) return fail(status, error, error_capacity, reason); + value->current_motion = std::make_unique(*motion); + value->current_frame = 0U; + *output = motion.release(); + return MB_OK; + }); +} + +mb_status mb_agent_advance(mb_agent * value, uint32_t frames, char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr) return fail(MB_INVALID_ARGUMENT, error, error_capacity, "agent is null"); + std::string reason; + const auto status = motionbricks::detail::advance_agent(*value, frames, reason); + return status == MB_OK ? MB_OK : fail(status, error, error_capacity, reason); + }); +} + +void mb_motion_free(mb_motion * value) { delete value; } + +mb_status mb_motion_get_frame_count(const mb_motion * value, uint64_t * output, char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "motion or output is null"); + *output = value->frames; + return MB_OK; + }); +} + +mb_status mb_motion_get_joint_count(const mb_motion * value, uint64_t * output, char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "motion or output is null"); + *output = value->joints; + return MB_OK; + }); +} + +mb_status mb_motion_get_root_translations(const mb_motion * value, const float ** output, uint64_t * values, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr || values == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "motion or output is null"); + *output = value->root_translations.data(); + *values = value->root_translations.size(); + return MB_OK; + }); +} + +mb_status mb_motion_get_local_rotations_xyzw(const mb_motion * value, const float ** output, uint64_t * values, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr || values == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "motion or output is null"); + *output = value->local_rotations_xyzw.data(); + *values = value->local_rotations_xyzw.size(); + return MB_OK; + }); +} + +mb_status mb_motion_get_target_frame_count(const mb_motion * value, uint64_t * output, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "motion or output is null"); + *output = value->target_frames; + return MB_OK; + }); +} + +mb_status mb_motion_get_target_root_translations(const mb_motion * value, const float ** output, uint64_t * values, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr || values == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "motion or output is null"); + *output = value->target_root_translations.data(); + *values = value->target_root_translations.size(); + return MB_OK; + }); +} + +mb_status mb_motion_get_target_local_rotations_xyzw(const mb_motion * value, const float ** output, uint64_t * values, + char * error, uint64_t error_capacity) { + return guard(error, error_capacity, [&]() -> mb_status { + if (value == nullptr || output == nullptr || values == nullptr) + return fail(MB_INVALID_ARGUMENT, error, error_capacity, "motion or output is null"); + *output = value->target_local_rotations_xyzw.data(); + *values = value->target_local_rotations_xyzw.size(); + return MB_OK; + }); +} + +} // extern "C" diff --git a/src/cli.cpp b/src/cli.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9b362a24807e58dfae1abbe92b261c6b7e25ee1c --- /dev/null +++ b/src/cli.cpp @@ -0,0 +1,58 @@ +#include + +#include +#include +#include +#include + +namespace { + +struct model_deleter { + void operator()(mb_model * value) const noexcept { mb_model_free(value); } +}; + +int inspect(const char * directory) { + std::array error{}; + mb_model * raw = nullptr; + const auto status = mb_model_load(directory, nullptr, &raw, error.data(), error.size()); + std::unique_ptr model(raw); + if (status != MB_OK) { + std::cerr << "model load failed (" << mb_status_string(status) << "): " << error.data() << '\n'; + return 1; + } + std::uint64_t parameters = 0; + std::uint32_t joints = 0; + if (mb_model_get_parameter_count(model.get(), ¶meters, error.data(), error.size()) != MB_OK || + mb_model_get_joint_count(model.get(), &joints, error.data(), error.size()) != MB_OK) { + std::cerr << "model inspection failed: " << error.data() << '\n'; + return 1; + } + std::cout << "architecture: motionbricks\n" + << "skeleton: g1skel34\n" + << "parameters: " << parameters << '\n' + << "joints: " << joints << '\n'; + for (std::uint32_t joint = 0; joint < joints; ++joint) { + const char * name = nullptr; + std::int32_t parent = -1; + if (mb_model_get_joint_name(model.get(), joint, &name, error.data(), error.size()) != MB_OK || + mb_model_get_joint_parent(model.get(), joint, &parent, error.data(), error.size()) != MB_OK) { + std::cerr << "joint inspection failed: " << error.data() << '\n'; + return 1; + } + std::cout << "joint[" << joint << "]: " << name << " parent=" << parent << '\n'; + } + return 0; +} + +} // namespace + +int main(int argc, char ** argv) { + if (argc == 2 && std::string_view(argv[1]) == "abi") { + std::cout << "motion-bricks.cpp ABI " << mb_abi_version() << '\n'; + return 0; + } + if (argc == 3 && std::string_view(argv[1]) == "inspect") return inspect(argv[2]); + + std::cerr << "usage: motionbricks-cli abi | inspect BUNDLE_DIRECTORY\n"; + return 2; +} diff --git a/src/decoder.cpp b/src/decoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..307180a48dc4fe7af2c0cf7abee1c8bb57973526 --- /dev/null +++ b/src/decoder.cpp @@ -0,0 +1,223 @@ +#include "decoder.hpp" + +#include "neural_runtime.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(MOTIONBRICKS_HAVE_GGML) +#include +#include +#endif + +namespace motionbricks::detail { +namespace { + +#if defined(MOTIONBRICKS_HAVE_GGML) + +struct context_deleter { void operator()(ggml_context * value) const noexcept { ggml_free(value); } }; +struct buffer_deleter { + void operator()(ggml_backend_buffer * value) const noexcept { ggml_backend_buffer_free(value); } +}; +using context_ptr = std::unique_ptr; +using buffer_ptr = std::unique_ptr; + +ggml_tensor * weight(const neural_runtime & runtime, const std::string & name, + std::string & reason) { + auto * result = neural_weight(runtime, "vq-decoder", name); + if (result == nullptr && reason.empty()) reason = "missing decoder weight: " + name; + return result; +} + +ggml_tensor * linear(ggml_context * context, ggml_tensor * input, + ggml_tensor * matrix, ggml_tensor * bias) { + return ggml_add(context, ggml_mul_mat(context, matrix, input), bias); +} + +ggml_tensor * conv(ggml_context * context, ggml_tensor * input, + ggml_tensor * kernel, ggml_tensor * bias, + int padding, int dilation) { + // ggml_conv_1d deliberately lowers ordinary F32 convolutions through an + // F16 im2col buffer. MotionBricks' residual decoder is sensitive enough + // for that loss to become visible, so retain F32 through the lowering. + auto * columns = ggml_im2col(context, kernel, input, 1, 0, padding, 0, + dilation, 0, false, GGML_TYPE_F32); + auto * output = ggml_mul_mat(context, + ggml_reshape_2d(context, columns, columns->ne[0], columns->ne[2] * columns->ne[1]), + ggml_reshape_2d(context, kernel, kernel->ne[0] * kernel->ne[1], kernel->ne[2])); + output = ggml_reshape_3d(context, output, columns->ne[1], kernel->ne[2], columns->ne[2]); + auto * shaped_bias = ggml_reshape_2d(context, bias, 1, bias->ne[0]); + return ggml_add(context, output, shaped_bias); +} + +ggml_tensor * channels_first_to_frames(ggml_context * context, ggml_tensor * input, + std::int64_t channels, std::int64_t frames) { + return ggml_cont_2d(context, ggml_transpose(context, input), channels, frames); +} + +ggml_tensor * frames_to_channels_first(ggml_context * context, ggml_tensor * input, + std::int64_t frames, std::int64_t channels) { + return ggml_cont_2d(context, ggml_transpose(context, input), frames, channels); +} + +ggml_tensor * residual_stack(ggml_context * context, const neural_runtime & runtime, + ggml_tensor * input, unsigned stage, std::string & reason, + std::vector> & traces) { + static constexpr std::array dilations{27, 9, 3, 1}; + auto * hidden = input; + for (unsigned block = 0; block < dilations.size(); ++block) { + const auto prefix = "decoder.model." + std::to_string(stage) + ".0.model." + + std::to_string(block) + "."; + auto * branch = ggml_relu(context, hidden); + branch = conv(context, branch, + weight(runtime, prefix + "conv1.weight", reason), + weight(runtime, prefix + "conv1.bias", reason), dilations[block], dilations[block]); + traces.emplace_back("model." + std::to_string(stage) + ".0.model." + + std::to_string(block) + ".conv1", branch); + branch = ggml_relu(context, branch); + branch = conv(context, branch, + weight(runtime, prefix + "conv2.weight", reason), + weight(runtime, prefix + "conv2.bias", reason), 0, 1); + traces.emplace_back("model." + std::to_string(stage) + ".0.model." + + std::to_string(block) + ".conv2", branch); + hidden = ggml_add(context, hidden, branch); + } + return hidden; +} + +#endif + +} // namespace + +mb_status run_vq_decoder(const neural_runtime & runtime, + std::span quantized, + std::span external_condition, + std::span target_condition, + std::span has_target_condition, + std::uint32_t positions, + std::vector & motion, + std::string & reason, + std::vector * output_traces) { +#if !defined(MOTIONBRICKS_HAVE_GGML) + (void)runtime; (void)quantized; (void)external_condition; (void)target_condition; + (void)has_target_condition; (void)positions; (void)motion; (void)output_traces; + reason = "this build has no GGML support"; + return MB_BACKEND_UNAVAILABLE; +#else + constexpr std::uint32_t latent_width = 256; + constexpr std::uint32_t external_width = 2; + constexpr std::uint32_t target_width = 304; + constexpr std::uint32_t output_width = 413; + const auto frames = positions * 4U; + if (positions < 1U || positions > 16U || + quantized.size() != static_cast(positions) * latent_width || + external_condition.size() != static_cast(frames) * external_width || + target_condition.size() != static_cast(frames) * target_width || + has_target_condition.size() != frames) { + reason = "VQ decoder input shape mismatch"; + return MB_INVALID_ARGUMENT; + } + constexpr std::size_t context_bytes = 32U * 1024U * 1024U; + context_ptr context(ggml_init({context_bytes, nullptr, true})); + if (!context) { + reason = "cannot allocate decoder graph metadata"; + return MB_OUT_OF_MEMORY; + } + auto * latent_input = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, positions, latent_width); + auto * external_input = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, external_width, frames); + auto * target_input = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, target_width, frames); + auto * target_mask = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, 1, frames); + ggml_set_input(latent_input); + ggml_set_input(external_input); + ggml_set_input(target_input); + ggml_set_input(target_mask); + + std::vector> traces; + auto * initial_conv = conv(context.get(), latent_input, + weight(runtime, "decoder.model.0.weight", reason), + weight(runtime, "decoder.model.0.bias", reason), 1, 1); + traces.emplace_back("model.0", initial_conv); + auto * hidden = ggml_relu(context.get(), initial_conv); + for (unsigned stage_index = 0; stage_index < 2U; ++stage_index) { + const unsigned stage = stage_index + 2U; + const auto frame_group = 1U << (2U - stage_index); + const auto stage_positions = positions * (1U << stage_index); + const auto stage_frames = stage_positions * frame_group; + + auto * target_embedding = linear(context.get(), target_input, + weight(runtime, "decoder.target_cond_blocks." + std::to_string(stage_index * 2U) + ".weight", reason), + weight(runtime, "decoder.target_cond_blocks." + std::to_string(stage_index * 2U) + ".bias", reason)); + traces.emplace_back("target_cond_blocks." + std::to_string(stage_index * 2U), target_embedding); + auto * hidden_frames = channels_first_to_frames(context.get(), hidden, 512, stage_positions); + hidden_frames = ggml_reshape_2d(context.get(), hidden_frames, 512 / frame_group, stage_frames); + hidden_frames = ggml_add(context.get(), hidden_frames, + ggml_mul(context.get(), ggml_sub(context.get(), ggml_relu(context.get(), target_embedding), hidden_frames), + target_mask)); + hidden_frames = ggml_reshape_2d(context.get(), hidden_frames, 512, stage_positions); + + auto * external_grouped = ggml_reshape_2d( + context.get(), external_input, external_width * frame_group, stage_positions); + auto * fused = ggml_concat(context.get(), hidden_frames, external_grouped, 0); + auto * fused_linear = linear(context.get(), fused, + weight(runtime, "decoder.external_cond_blocks." + std::to_string(stage_index * 2U) + ".weight", reason), + weight(runtime, "decoder.external_cond_blocks." + std::to_string(stage_index * 2U) + ".bias", reason)); + traces.emplace_back("external_cond_blocks." + std::to_string(stage_index * 2U), fused_linear); + fused = ggml_relu(context.get(), fused_linear); + hidden = frames_to_channels_first(context.get(), fused, stage_positions, 512); + hidden = residual_stack(context.get(), runtime, hidden, stage, reason, traces); + hidden = ggml_interpolate(context.get(), hidden, hidden->ne[0] * 2, hidden->ne[1], + hidden->ne[2], hidden->ne[3], GGML_SCALE_MODE_NEAREST); + hidden = conv(context.get(), hidden, + weight(runtime, "decoder.model." + std::to_string(stage) + ".2.weight", reason), + weight(runtime, "decoder.model." + std::to_string(stage) + ".2.bias", reason), 1, 1); + traces.emplace_back("model." + std::to_string(stage) + ".2", hidden); + } + auto * post_conv = conv(context.get(), hidden, + weight(runtime, "decoder.model.4.weight", reason), + weight(runtime, "decoder.model.4.bias", reason), 1, 1); + traces.emplace_back("model.4", post_conv); + hidden = ggml_relu(context.get(), post_conv); + hidden = conv(context.get(), hidden, + weight(runtime, "decoder.model.6.weight", reason), + weight(runtime, "decoder.model.6.bias", reason), 1, 1); + traces.emplace_back("model.6", hidden); + auto * output = channels_first_to_frames(context.get(), hidden, output_width, frames); + if (!reason.empty()) return MB_INCOMPATIBLE_MODEL; + auto * graph = ggml_new_graph_custom(context.get(), 2048, false); + ggml_build_forward_expand(graph, output); + buffer_ptr buffer(ggml_backend_alloc_ctx_tensors(context.get(), neural_backend(runtime))); + if (!buffer) { + reason = "cannot allocate decoder compute buffer"; + return MB_OUT_OF_MEMORY; + } + std::vector mask(has_target_condition.begin(), has_target_condition.end()); + ggml_backend_tensor_set(latent_input, quantized.data(), 0, ggml_nbytes(latent_input)); + ggml_backend_tensor_set(external_input, external_condition.data(), 0, ggml_nbytes(external_input)); + ggml_backend_tensor_set(target_input, target_condition.data(), 0, ggml_nbytes(target_input)); + ggml_backend_tensor_set(target_mask, mask.data(), 0, ggml_nbytes(target_mask)); + const auto status = ggml_backend_graph_compute(neural_backend(runtime), graph); + if (status != GGML_STATUS_SUCCESS) { + reason = std::string("decoder graph failed: ") + ggml_status_to_string(status); + return MB_COMPUTE_FAILED; + } + motion.resize(static_cast(frames) * output_width); + ggml_backend_tensor_get(output, motion.data(), 0, motion.size() * sizeof(float)); + if (output_traces != nullptr) { + output_traces->clear(); + output_traces->reserve(traces.size()); + for (const auto & [name, tensor] : traces) { + decoder_trace trace{name, std::vector(static_cast(ggml_nelements(tensor)))}; + ggml_backend_tensor_get(tensor, trace.values.data(), 0, trace.values.size() * sizeof(float)); + output_traces->push_back(std::move(trace)); + } + } + return MB_OK; +#endif +} + +} // namespace motionbricks::detail diff --git a/src/decoder.hpp b/src/decoder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b0c2eef9268c24a55d14576e60aafcd7853765c5 --- /dev/null +++ b/src/decoder.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace motionbricks::detail { + +class neural_runtime; + +struct decoder_trace { + std::string name; + std::vector values; +}; + +mb_status run_vq_decoder(const neural_runtime & runtime, + std::span quantized, + std::span external_condition, + std::span target_condition, + std::span has_target_condition, + std::uint32_t positions, + std::vector & motion, + std::string & reason, + std::vector * traces = nullptr); + +} // namespace motionbricks::detail diff --git a/src/error.cpp b/src/error.cpp new file mode 100644 index 0000000000000000000000000000000000000000..60db1309a8068be379a660862bce1973df3bc55a --- /dev/null +++ b/src/error.cpp @@ -0,0 +1,25 @@ +#include "error.hpp" + +#include +#include +#include + +namespace motionbricks::detail { + +void write_error(char * output, std::uint64_t capacity, std::string_view message) noexcept { + if (output == nullptr || capacity == 0) return; + const auto usable = capacity - 1; + const auto bounded = std::min(usable, + static_cast(std::numeric_limits::max())); + const auto count = std::min(message.size(), static_cast(bounded)); + if (count != 0) std::memcpy(output, message.data(), count); + output[count] = '\0'; +} + +mb_status fail(mb_status status, char * error, std::uint64_t error_capacity, + std::string_view message) noexcept { + write_error(error, error_capacity, message); + return status; +} + +} // namespace motionbricks::detail diff --git a/src/error.hpp b/src/error.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4916da3ffc2af3fb51a0b786183c326dc563f0ea --- /dev/null +++ b/src/error.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace motionbricks::detail { + +void write_error(char * output, std::uint64_t capacity, std::string_view message) noexcept; + +template +mb_status guard(char * error, std::uint64_t error_capacity, Function && function) noexcept { + try { + write_error(error, error_capacity, {}); + return function(); + } catch (const std::bad_alloc &) { + write_error(error, error_capacity, "allocation failed"); + return MB_OUT_OF_MEMORY; + } catch (const std::exception & exception) { + write_error(error, error_capacity, exception.what()); + return MB_INTERNAL_ERROR; + } catch (...) { + write_error(error, error_capacity, "unknown internal error"); + return MB_INTERNAL_ERROR; + } +} + +mb_status fail(mb_status status, char * error, std::uint64_t error_capacity, + std::string_view message) noexcept; + +} // namespace motionbricks::detail diff --git a/src/handles.hpp b/src/handles.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c9424a5746327f25121ccf3e2fb328bf0e65be30 --- /dev/null +++ b/src/handles.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace motionbricks::detail { +class neural_runtime; +} + +struct mb_runtime_options { + mb_device device = MB_DEVICE_AUTO; + std::uint32_t threads = 0; + std::string backend_directory; +}; + +struct mb_model { + std::string bundle_directory; + std::uint64_t parameter_count = 0; + std::vector joint_names; + std::vector joint_parents; + std::vector neutral_joints; + std::vector motion_mean; + std::vector motion_std; + std::shared_ptr runtime; +}; + +struct mb_style { + std::string name; + float speed = 0.0F; + std::uint32_t frames = 0; + std::vector global_joint_positions; + std::vector global_joint_rotations; + std::vector global_root_positions; + std::vector global_headings; + std::array allowed_tokens{}; +}; + +struct mb_command { + const mb_style * style = nullptr; + std::array movement_direction{0.0F, 0.0F, 0.0F}; + std::array facing_direction{0.0F, 0.0F, 1.0F}; + float target_speed = -1.0F; + std::array world_target{0.0F, 0.0F, 0.0F}; + float world_target_heading = 0.0F; + std::uint32_t has_world_target = 0; + std::uint64_t seed = 0; +}; + +struct mb_agent { + const mb_model * model = nullptr; + const mb_style * initial_style = nullptr; + std::vector context_root_xyz; + std::vector context_local_rotations_xyzw; + std::uint64_t context_frames = 0; + std::unique_ptr current_motion; + std::uint32_t current_frame = 0; +}; + +struct mb_motion { + std::uint64_t frames = 0; + std::uint64_t joints = 0; + std::vector root_translations; + std::vector local_rotations_xyzw; + std::uint64_t target_frames = 0; + std::vector target_root_translations; + std::vector target_local_rotations_xyzw; +}; diff --git a/src/model.cpp b/src/model.cpp new file mode 100644 index 0000000000000000000000000000000000000000..500b6e350ee19ef5d9026f70cc9ab8cb7af6ff4c --- /dev/null +++ b/src/model.cpp @@ -0,0 +1,288 @@ +#include "model.hpp" + +#include "handles.hpp" +#include "neural_runtime.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(MOTIONBRICKS_HAVE_GGML) +#include +#include +#endif + +namespace motionbricks::detail { +namespace { + +#if defined(MOTIONBRICKS_HAVE_GGML) + +constexpr std::string_view upstream_revision = "a0732b642c0333077e127a2f56ab0014c196bca4"; +constexpr std::uint64_t inference_parameters = UINT64_C(183148382); + +struct component_spec { + const char * name; + const char * filename; + const char * source_hash; + std::int64_t tensors; + std::uint64_t parameters; + bool load_data; +}; + +constexpr std::array components{ + component_spec{"pose", "pose.gguf", + "01327768be7413111dc927947a95cfb3e9ee7c52acfa35a054e8c2f3b838b888", + 209, UINT64_C(136588272), false}, + component_spec{"root", "root.gguf", + "d1529a8c9da915cb7dd0499272baba61db480660c0aae92b67fbe69828e83c5a", + 150, UINT64_C(34122833), false}, + component_spec{"vq-decoder", "vq-decoder.gguf", + "544782e605ed96d60bf999243ef8f44640ea021a5655ef20af2d2853c3e18b5b", + 51, UINT64_C(12437277), false}, + component_spec{"support", "support.gguf", + "229b764411652b2ab0f824481d6daf897f701a97223029759444fc5bc241ea22", + 4, UINT64_C(972), true}, +}; + +struct ggml_deleter { + void operator()(ggml_context * value) const noexcept { ggml_free(value); } +}; + +struct gguf_deleter { + void operator()(gguf_context * value) const noexcept { gguf_free(value); } +}; + +using ggml_ptr = std::unique_ptr; +using gguf_ptr = std::unique_ptr; + +bool metadata_string(const gguf_context * context, const char * key, + std::string_view expected, std::string & reason) { + const auto index = gguf_find_key(context, key); + if (index < 0 || gguf_get_kv_type(context, index) != GGUF_TYPE_STRING) { + reason = std::string("missing string metadata: ") + key; + return false; + } + if (std::string_view(gguf_get_val_str(context, index)) != expected) { + reason = std::string("incompatible metadata value: ") + key; + return false; + } + return true; +} + +bool metadata_u32(const gguf_context * context, const char * key, + std::uint32_t expected, std::string & reason) { + const auto index = gguf_find_key(context, key); + if (index < 0 || gguf_get_kv_type(context, index) != GGUF_TYPE_UINT32) { + reason = std::string("missing uint32 metadata: ") + key; + return false; + } + if (gguf_get_val_u32(context, index) != expected) { + reason = std::string("incompatible metadata value: ") + key; + return false; + } + return true; +} + +bool metadata_u64(const gguf_context * context, const char * key, + std::uint64_t expected, std::string & reason) { + const auto index = gguf_find_key(context, key); + if (index < 0 || gguf_get_kv_type(context, index) != GGUF_TYPE_UINT64) { + reason = std::string("missing uint64 metadata: ") + key; + return false; + } + if (gguf_get_val_u64(context, index) != expected) { + reason = std::string("incompatible metadata value: ") + key; + return false; + } + return true; +} + +bool tensor_shape(const gguf_context * context, const char * name, + std::array expected, ggml_type type, + std::string & reason) { + const auto index = gguf_find_tensor(context, name); + if (index < 0) { + reason = std::string("missing tensor: ") + name; + return false; + } + if (gguf_get_tensor_type(context, index) != type) { + reason = std::string("incorrect tensor type: ") + name; + return false; + } + const auto * dimensions = gguf_get_tensor_ne(context, index); + if (!std::equal(expected.begin(), expected.end(), dimensions)) { + reason = std::string("incorrect tensor shape: ") + name; + return false; + } + return true; +} + +std::vector split_names(std::string_view value) { + std::vector result; + while (!value.empty()) { + const auto comma = value.find(','); + result.emplace_back(value.substr(0, comma)); + if (comma == std::string_view::npos) break; + value.remove_prefix(comma + 1); + } + return result; +} + +mb_status load_component(const std::filesystem::path & path, const component_spec & spec, + mb_model & output, std::string & reason) { + if (!std::filesystem::is_regular_file(path)) { + reason = "missing GGUF component: " + path.string(); + return MB_IO_ERROR; + } + + ggml_context * raw_ggml = nullptr; + const gguf_init_params params{!spec.load_data, &raw_ggml}; + gguf_ptr gguf(gguf_init_from_file(path.string().c_str(), params)); + ggml_ptr ggml(raw_ggml); + if (!gguf || !ggml) { + reason = "could not parse GGUF component: " + path.string(); + return MB_INVALID_FORMAT; + } + if (gguf_get_version(gguf.get()) != 3U) { + reason = "unsupported GGUF version in " + path.string(); + return MB_INVALID_FORMAT; + } + if (!metadata_string(gguf.get(), "general.architecture", "motionbricks", reason) || + !metadata_u32(gguf.get(), "motionbricks.format_version", 1U, reason) || + !metadata_string(gguf.get(), "motionbricks.component", spec.name, reason) || + !metadata_string(gguf.get(), "motionbricks.skeleton", "g1skel34", reason) || + !metadata_string(gguf.get(), "motionbricks.upstream_revision", upstream_revision, reason) || + !metadata_string(gguf.get(), "motionbricks.source_sha256", spec.source_hash, reason) || + !metadata_u64(gguf.get(), "motionbricks.parameter_count", spec.parameters, reason)) { + reason += " (" + path.string() + ")"; + return MB_INCOMPATIBLE_MODEL; + } + if (gguf_get_n_tensors(gguf.get()) != spec.tensors) { + reason = "incorrect tensor count in " + path.string(); + return MB_INCOMPATIBLE_MODEL; + } + + std::unordered_set names; + for (std::int64_t index = 0; index < spec.tensors; ++index) { + const std::string_view name = gguf_get_tensor_name(gguf.get(), index); + if (name.empty() || !names.insert(name).second) { + reason = "invalid or duplicate tensor name in " + path.string(); + return MB_INVALID_FORMAT; + } + } + + if (std::string_view(spec.name) == "pose") { + if (!tensor_shape(gguf.get(), "_pose_token_emb.weight", {32, 88, 1, 1}, GGML_TYPE_F32, reason) || + !tensor_shape(gguf.get(), "_position_emb.embed", {1024, 1, 16, 1}, GGML_TYPE_F32, reason) || + !tensor_shape(gguf.get(), "_proj_pose_output_logit.weight", {1024, 80, 1, 1}, GGML_TYPE_F32, reason)) + return MB_INCOMPATIBLE_MODEL; + } else if (std::string_view(spec.name) == "root") { + if (!tensor_shape(gguf.get(), "_position_emb.embed", {512, 1, 16, 1}, GGML_TYPE_F32, reason) || + !tensor_shape(gguf.get(), "_proj_num_token_output_logit.weight", {512, 12, 1, 1}, GGML_TYPE_F32, reason) || + !tensor_shape(gguf.get(), "_conv_output.model.6.weight", {3, 512, 5, 1}, GGML_TYPE_F32, reason)) + return MB_INCOMPATIBLE_MODEL; + } else if (std::string_view(spec.name) == "vq-decoder") { + if (!tensor_shape(gguf.get(), "quantizer.vq._codebook.embed", {32, 10, 8, 1}, GGML_TYPE_F32, reason) || + !tensor_shape(gguf.get(), "decoder.model.6.weight", {3, 512, 413, 1}, GGML_TYPE_F32, reason)) + return MB_INCOMPATIBLE_MODEL; + } else { + if (!tensor_shape(gguf.get(), "neutral_joints", {3, 34, 1, 1}, GGML_TYPE_F32, reason) || + !tensor_shape(gguf.get(), "joint_parents", {34, 1, 1, 1}, GGML_TYPE_I32, reason) || + !tensor_shape(gguf.get(), "motion_mean", {418, 1, 1, 1}, GGML_TYPE_F32, reason) || + !tensor_shape(gguf.get(), "motion_std", {418, 1, 1, 1}, GGML_TYPE_F32, reason)) + return MB_INCOMPATIBLE_MODEL; + + const auto joint_names_key = gguf_find_key(gguf.get(), "motionbricks.joint_names"); + if (joint_names_key < 0 || gguf_get_kv_type(gguf.get(), joint_names_key) != GGUF_TYPE_STRING) { + reason = "missing joint names in support component"; + return MB_INCOMPATIBLE_MODEL; + } + output.joint_names = split_names(gguf_get_val_str(gguf.get(), joint_names_key)); + if (output.joint_names.size() != 34U || + std::unordered_set(output.joint_names.begin(), output.joint_names.end()).size() != 34U) { + reason = "invalid joint names in support component"; + return MB_INCOMPATIBLE_MODEL; + } + const auto * parents_tensor = ggml_get_tensor(ggml.get(), "joint_parents"); + const auto * parents = static_cast(parents_tensor->data); + output.joint_parents.assign(parents, parents + 34); + const auto copy_f32 = [&](const char * name, std::size_t count, + std::vector & destination) { + const auto * tensor = ggml_get_tensor(ggml.get(), name); + const auto * values = static_cast(tensor->data); + destination.assign(values, values + count); + }; + copy_f32("neutral_joints", 34U * 3U, output.neutral_joints); + copy_f32("motion_mean", 418U, output.motion_mean); + copy_f32("motion_std", 418U, output.motion_std); + for (std::size_t index = 0; index < output.joint_parents.size(); ++index) { + const auto parent = output.joint_parents[index]; + if ((index == 0U && parent != -1) || + (index != 0U && (parent < 0 || static_cast(parent) >= index))) { + reason = "invalid parent topology in support component"; + return MB_INCOMPATIBLE_MODEL; + } + } + if (std::any_of(output.neutral_joints.begin(), output.neutral_joints.end(), + [](float value) { return !std::isfinite(value); }) || + std::any_of(output.motion_mean.begin(), output.motion_mean.end(), + [](float value) { return !std::isfinite(value); }) || + std::any_of(output.motion_std.begin(), output.motion_std.end(), + [](float value) { return !std::isfinite(value) || value < 0.0F; })) { + reason = "invalid support values"; + return MB_INCOMPATIBLE_MODEL; + } + } + return MB_OK; +} + +#endif + +} // namespace + +mb_status load_model_bundle(const std::filesystem::path & directory, + const mb_runtime_options * options, + mb_model & output, std::string & reason) { +#if !defined(MOTIONBRICKS_HAVE_GGML) + (void)directory; + (void)output; + reason = "this build has no GGML support"; + return MB_BACKEND_UNAVAILABLE; +#else + std::error_code error; + if (!std::filesystem::is_directory(directory, error)) { + reason = error ? "cannot access model bundle: " + error.message() + : "model bundle is not a directory: " + directory.string(); + return MB_IO_ERROR; + } + mb_model temporary; + temporary.bundle_directory = std::filesystem::absolute(directory, error).lexically_normal().string(); + if (error) { + reason = "cannot resolve model bundle: " + error.message(); + return MB_IO_ERROR; + } + for (const auto & component : components) { + const auto status = load_component(directory / component.filename, component, temporary, reason); + if (status != MB_OK) return status; + } + temporary.parameter_count = inference_parameters; + const auto status = create_neural_runtime( + directory, options != nullptr ? options->device : MB_DEVICE_CPU, + options != nullptr ? options->threads : 0U, + options != nullptr ? options->backend_directory : std::string{}, + temporary.runtime, reason); + if (status != MB_OK) return status; + output = std::move(temporary); + return MB_OK; +#endif +} + +} // namespace motionbricks::detail diff --git a/src/model.hpp b/src/model.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c89a9da9bf885213b62e19c5837df36f4b52204d --- /dev/null +++ b/src/model.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include +#include + +struct mb_model; + +namespace motionbricks::detail { + +mb_status load_model_bundle(const std::filesystem::path & directory, + const mb_runtime_options * options, + mb_model & output, std::string & reason); + +} // namespace motionbricks::detail diff --git a/src/motion_rep.cpp b/src/motion_rep.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2ae89bfa3dd2faaefb70e2eaad88e20169c53bed --- /dev/null +++ b/src/motion_rep.cpp @@ -0,0 +1,294 @@ +#include "motion_rep.hpp" + +#include "handles.hpp" + +#include +#include +#include +#include +#include + +namespace motionbricks::detail { +namespace { + +constexpr float fps = 30.0F; +constexpr float stats_epsilon = 1.0e-5F; + +using vec3 = std::array; +using quat = std::array; // wxyz internally +using mat3 = std::array; + +float wrap_angle(float value) { + constexpr float pi = 3.14159265358979323846F; + return std::remainder(value, 2.0F * pi); +} + +mat3 quaternion_matrix(quat q) { + const float norm = q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]; + if (!(norm > 1.0e-12F) || !std::isfinite(norm)) return {}; + const float s = 2.0F / norm; + const float w = q[0], x = q[1], y = q[2], z = q[3]; + return { + 1.0F - s * (y*y + z*z), s * (x*y - z*w), s * (x*z + y*w), + s * (x*y + z*w), 1.0F - s * (x*x + z*z), s * (y*z - x*w), + s * (x*z - y*w), s * (y*z + x*w), 1.0F - s * (x*x + y*y), + }; +} + +mat3 multiply(const mat3 & a, const mat3 & b) { + mat3 result{}; + for (unsigned row = 0; row < 3U; ++row) + for (unsigned column = 0; column < 3U; ++column) + for (unsigned k = 0; k < 3U; ++k) + result[row * 3U + column] += a[row * 3U + k] * b[k * 3U + column]; + return result; +} + +mat3 transpose(const mat3 & value) { + return {value[0], value[3], value[6], value[1], value[4], value[7], + value[2], value[5], value[8]}; +} + +vec3 transform(const mat3 & matrix, const vec3 & value) { + return { + matrix[0]*value[0] + matrix[1]*value[1] + matrix[2]*value[2], + matrix[3]*value[0] + matrix[4]*value[1] + matrix[5]*value[2], + matrix[6]*value[0] + matrix[7]*value[1] + matrix[8]*value[2], + }; +} + +quat matrix_quaternion(const mat3 & matrix) { + quat result{}; + const float trace = matrix[0] + matrix[4] + matrix[8]; + if (trace > 0.0F) { + const float scale = 2.0F * std::sqrt(std::max(0.0F, trace + 1.0F)); + result = {0.25F * scale, (matrix[7] - matrix[5]) / scale, + (matrix[2] - matrix[6]) / scale, (matrix[3] - matrix[1]) / scale}; + } else if (matrix[0] > matrix[4] && matrix[0] > matrix[8]) { + const float scale = 2.0F * std::sqrt(std::max(0.0F, 1.0F + matrix[0] - matrix[4] - matrix[8])); + result = {(matrix[7] - matrix[5]) / scale, 0.25F * scale, + (matrix[1] + matrix[3]) / scale, (matrix[2] + matrix[6]) / scale}; + } else if (matrix[4] > matrix[8]) { + const float scale = 2.0F * std::sqrt(std::max(0.0F, 1.0F + matrix[4] - matrix[0] - matrix[8])); + result = {(matrix[2] - matrix[6]) / scale, (matrix[1] + matrix[3]) / scale, + 0.25F * scale, (matrix[5] + matrix[7]) / scale}; + } else { + const float scale = 2.0F * std::sqrt(std::max(0.0F, 1.0F + matrix[8] - matrix[0] - matrix[4])); + result = {(matrix[3] - matrix[1]) / scale, (matrix[2] + matrix[6]) / scale, + (matrix[5] + matrix[7]) / scale, 0.25F * scale}; + } + const float norm = std::sqrt(result[0]*result[0] + result[1]*result[1] + + result[2]*result[2] + result[3]*result[3]); + if (norm > 0.0F) for (float & value : result) value /= norm; + if (result[0] < 0.0F) for (float & value : result) value = -value; + return result; +} + +mat3 cont6d_matrix(const float * value) { + vec3 x{value[0], value[1], value[2]}; + const vec3 y_raw{value[3], value[4], value[5]}; + const auto normalize = [](vec3 & vector) { + const float length = std::sqrt(vector[0]*vector[0] + vector[1]*vector[1] + vector[2]*vector[2]); + if (length > 1.0e-12F) for (float & item : vector) item /= length; + }; + normalize(x); + vec3 z{x[1]*y_raw[2] - x[2]*y_raw[1], + x[2]*y_raw[0] - x[0]*y_raw[2], + x[0]*y_raw[1] - x[1]*y_raw[0]}; + normalize(z); + const vec3 y{z[1]*x[2] - z[2]*x[1], z[2]*x[0] - z[0]*x[2], + z[0]*x[1] - z[1]*x[0]}; + return {x[0], y[0], z[0], x[1], y[1], z[1], x[2], y[2], z[2]}; +} + +bool finite_values(std::span values) { + return std::all_of(values.begin(), values.end(), [](float value) { return std::isfinite(value); }); +} + +} // namespace + +float normalize_feature(const mb_model & model, float value, std::uint32_t dual_index) { + const float stddev = model.motion_std[dual_index]; + return (value - model.motion_mean[dual_index]) / + std::sqrt(stddev * stddev + stats_epsilon); +} + +float unnormalize_feature(const mb_model & model, float value, std::uint32_t dual_index) { + const float stddev = model.motion_std[dual_index]; + return value * std::sqrt(stddev * stddev + stats_epsilon) + model.motion_mean[dual_index]; +} + +mb_status encode_global_frames(std::span joint_positions, + std::span joint_rotations, + bool repeat_last_velocity, + encoded_frames & output, + std::string & reason) { + constexpr std::size_t position_count = boundary_frame_count * g1_joint_count * 3U; + constexpr std::size_t rotation_count = boundary_frame_count * g1_joint_count * 9U; + if (joint_positions.size() != position_count || joint_rotations.size() != rotation_count) { + reason = "global frame input shape mismatch"; + return MB_INVALID_ARGUMENT; + } + if (!finite_values(joint_positions) || !finite_values(joint_rotations)) { + reason = "global frames contain non-finite values"; + return MB_INVALID_ARGUMENT; + } + std::array headings{}; + for (std::uint32_t frame = 0; frame < boundary_frame_count; ++frame) { + const auto position_base = static_cast(frame) * g1_joint_count * 3U; + const auto rotation_base = static_cast(frame) * g1_joint_count * 9U; + const float * root = joint_positions.data() + position_base; + const float * root_rotation = joint_rotations.data() + rotation_base; + headings[frame] = std::atan2(root_rotation[2], root_rotation[8]); + auto * global = output.global_root.data() + static_cast(frame) * global_root_width; + global[0] = root[0]; global[1] = root[1]; global[2] = root[2]; + global[3] = std::cos(headings[frame]); global[4] = std::sin(headings[frame]); + + auto * pose = output.poses.data() + static_cast(frame) * external_pose_width; + for (std::uint32_t joint = 1; joint < g1_joint_count; ++joint) { + const float * position = joint_positions.data() + position_base + joint * 3U; + const auto destination = static_cast(joint - 1U) * 3U; + pose[destination] = position[0] - root[0]; + pose[destination + 1U] = position[1]; + pose[destination + 2U] = position[2] - root[2]; + } + constexpr std::size_t rotation_offset = (g1_joint_count - 1U) * 3U; + for (std::uint32_t joint = 0; joint < g1_joint_count; ++joint) { + const float * matrix = joint_rotations.data() + rotation_base + joint * 9U; + float * six = pose + rotation_offset + joint * 6U; + six[0] = matrix[0]; six[1] = matrix[3]; six[2] = matrix[6]; + six[3] = matrix[1]; six[4] = matrix[4]; six[5] = matrix[7]; + } + } + for (std::uint32_t frame = 0; frame + 1U < boundary_frame_count; ++frame) { + auto * local = output.local_root.data() + static_cast(frame) * local_root_width; + const auto * current = output.global_root.data() + static_cast(frame) * global_root_width; + const auto * next = current + global_root_width; + local[0] = wrap_angle(headings[frame + 1U] - headings[frame]) * fps; + local[1] = (next[0] - current[0]) * fps; + local[2] = (next[2] - current[2]) * fps; + local[3] = current[1]; + } + if (repeat_last_velocity) { + auto * last = output.local_root.data() + 3U * local_root_width; + std::copy_n(last - local_root_width, local_root_width, last); + last[3] = output.global_root[3U * global_root_width + 1U]; + } + return MB_OK; +} + +mb_status encode_context(const mb_model & model, + std::span root_xyz, + std::span local_rotation_xyzw, + std::uint64_t frames, + encoded_frames & output, + std::string & reason) { + if (frames < boundary_frame_count || root_xyz.size() != frames * 3U || + local_rotation_xyzw.size() != frames * g1_joint_count * 4U) { + reason = "context input shape mismatch"; + return MB_INVALID_ARGUMENT; + } + if (model.neutral_joints.size() != g1_joint_count * 3U || + model.joint_parents.size() != g1_joint_count) { + reason = "model has incomplete skeleton support data"; + return MB_INCOMPATIBLE_MODEL; + } + if (!finite_values(root_xyz) || !finite_values(local_rotation_xyzw)) { + reason = "context contains non-finite values"; + return MB_INVALID_ARGUMENT; + } + std::vector positions(boundary_frame_count * g1_joint_count * 3U); + std::vector rotations(boundary_frame_count * g1_joint_count * 9U); + const auto first = frames - boundary_frame_count; + for (std::uint32_t frame = 0; frame < boundary_frame_count; ++frame) { + const auto source_frame = first + frame; + const float * root = root_xyz.data() + source_frame * 3U; + std::array global{}; + for (std::uint32_t joint = 0; joint < g1_joint_count; ++joint) { + const float * xyzw = local_rotation_xyzw.data() + + (source_frame * g1_joint_count + joint) * 4U; + const mat3 local = quaternion_matrix({xyzw[3], xyzw[0], xyzw[1], xyzw[2]}); + if (local == mat3{}) { + reason = "context contains a zero rotation quaternion"; + return MB_INVALID_ARGUMENT; + } + const auto parent = model.joint_parents[joint]; + global[joint] = parent < 0 ? local : multiply(global[static_cast(parent)], local); + float * matrix = rotations.data() + + (static_cast(frame) * g1_joint_count + joint) * 9U; + std::copy(global[joint].begin(), global[joint].end(), matrix); + float * position = positions.data() + + (static_cast(frame) * g1_joint_count + joint) * 3U; + if (parent < 0) { + std::copy_n(root, 3, position); + } else { + const auto parent_index = static_cast(parent); + const float * parent_position = positions.data() + + (static_cast(frame) * g1_joint_count + parent_index) * 3U; + const vec3 rest{ + model.neutral_joints[joint * 3U] - model.neutral_joints[parent_index * 3U], + model.neutral_joints[joint * 3U + 1U] - model.neutral_joints[parent_index * 3U + 1U], + model.neutral_joints[joint * 3U + 2U] - model.neutral_joints[parent_index * 3U + 2U], + }; + const vec3 offset = transform(global[parent_index], rest); + for (unsigned axis = 0; axis < 3U; ++axis) position[axis] = parent_position[axis] + offset[axis]; + } + } + } + return encode_global_frames(positions, rotations, false, output, reason); +} + +mb_status decode_motion(const mb_model & model, + std::span normalized_local_motion, + std::uint32_t frames, + float initial_x, float initial_z, + float initial_heading, + mb_motion & output, + std::string & reason) { + if (frames == 0U || normalized_local_motion.size() != + static_cast(frames) * local_motion_width) { + reason = "decoded local motion shape mismatch"; + return MB_INVALID_ARGUMENT; + } + std::vector raw(normalized_local_motion.size()); + for (std::uint32_t frame = 0; frame < frames; ++frame) { + for (std::uint32_t feature = 0; feature < local_motion_width; ++feature) { + const std::uint32_t dual_index = feature < 4U ? 5U + feature : 9U + feature - 4U; + raw[static_cast(frame) * local_motion_width + feature] = + unnormalize_feature(model, + normalized_local_motion[static_cast(frame) * local_motion_width + feature], + dual_index); + } + } + output.frames = frames; + output.joints = g1_joint_count; + output.root_translations.assign(static_cast(frames) * 3U, 0.0F); + output.local_rotations_xyzw.resize(static_cast(frames) * g1_joint_count * 4U); + float x = initial_x, z = initial_z; + for (std::uint32_t frame = 0; frame < frames; ++frame) { + const float * features = raw.data() + static_cast(frame) * local_motion_width; + float * root = output.root_translations.data() + static_cast(frame) * 3U; + root[0] = x; root[1] = features[3]; root[2] = z; + if (frame + 1U < frames) { + x += features[1] / fps; + z += features[2] / fps; + } + std::array global{}; + constexpr std::size_t rotation_offset = 4U + 99U; + for (std::uint32_t joint = 0; joint < g1_joint_count; ++joint) + global[joint] = cont6d_matrix(features + rotation_offset + joint * 6U); + for (std::uint32_t joint = 0; joint < g1_joint_count; ++joint) { + const auto parent = model.joint_parents[joint]; + const mat3 local = parent < 0 ? global[joint] + : multiply(transpose(global[static_cast(parent)]), global[joint]); + const quat q = matrix_quaternion(local); + float * xyzw = output.local_rotations_xyzw.data() + + (static_cast(frame) * g1_joint_count + joint) * 4U; + xyzw[0] = q[1]; xyzw[1] = q[2]; xyzw[2] = q[3]; xyzw[3] = q[0]; + } + } + (void)initial_heading; // Body rotations retain world heading in this released representation. + return MB_OK; +} + +} // namespace motionbricks::detail diff --git a/src/motion_rep.hpp b/src/motion_rep.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3fbfeefd99239f85e2078bff4bfc8026c15dbce1 --- /dev/null +++ b/src/motion_rep.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +struct mb_model; +struct mb_motion; + +namespace motionbricks::detail { + +constexpr std::uint32_t g1_joint_count = 34U; +constexpr std::uint32_t boundary_frame_count = 4U; +constexpr std::uint32_t global_root_width = 5U; +constexpr std::uint32_t local_root_width = 4U; +constexpr std::uint32_t external_pose_width = 303U; +constexpr std::uint32_t internal_pose_width = 304U; +constexpr std::uint32_t local_motion_width = 413U; + +struct encoded_frames { + std::array global_root{}; + std::array local_root{}; + std::array poses{}; +}; + +/* Converts public root positions and local XYZW rotations into the exact raw + sparse features used by the released demo. The last four input frames are + used when a longer history is supplied. */ +mb_status encode_context(const mb_model & model, + std::span root_xyz, + std::span local_rotation_xyzw, + std::uint64_t frames, + encoded_frames & output, + std::string & reason); + +/* Encodes four already-global frames. Rotations are row-major 3x3 matrices; + positions include the root and are in world space. */ +mb_status encode_global_frames(std::span joint_positions, + std::span joint_rotations, + bool repeat_last_velocity, + encoded_frames & output, + std::string & reason); + +float normalize_feature(const mb_model & model, float value, std::uint32_t dual_index); +float unnormalize_feature(const mb_model & model, float value, std::uint32_t dual_index); + +/* Converts the decoder's normalized local representation to the public + animation boundary. */ +mb_status decode_motion(const mb_model & model, + std::span normalized_local_motion, + std::uint32_t frames, + float initial_x, float initial_z, + float initial_heading, + mb_motion & output, + std::string & reason); + +} // namespace motionbricks::detail diff --git a/src/neural_runtime.cpp b/src/neural_runtime.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bfd664c457d0b0b532a524d99947c6ab8f5fc6a7 --- /dev/null +++ b/src/neural_runtime.cpp @@ -0,0 +1,224 @@ +#include "neural_runtime.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(MOTIONBRICKS_HAVE_GGML) +#include +#include +#if defined(MOTIONBRICKS_HAVE_VULKAN) +#include +#endif +#endif + +namespace motionbricks::detail { + +#if defined(MOTIONBRICKS_HAVE_GGML) +namespace { + +struct ggml_context_deleter { + void operator()(ggml_context * value) const noexcept { ggml_free(value); } +}; +struct gguf_context_deleter { + void operator()(gguf_context * value) const noexcept { gguf_free(value); } +}; +struct backend_deleter { + void operator()(ggml_backend * value) const noexcept { ggml_backend_free(value); } +}; +struct buffer_deleter { + void operator()(ggml_backend_buffer * value) const noexcept { ggml_backend_buffer_free(value); } +}; + +using context_ptr = std::unique_ptr; +using gguf_ptr = std::unique_ptr; +using backend_ptr = std::unique_ptr; +using buffer_ptr = std::unique_ptr; + +bool copy_gguf_data(const std::filesystem::path & path, ggml_context * tensors, + const gguf_context * metadata, std::string & reason) { + std::unique_ptr file( + std::fopen(path.string().c_str(), "rb"), &std::fclose); + if (!file) { + reason = "cannot open GGUF weights: " + path.string(); + return false; + } + std::vector staging(4U * 1024U * 1024U); + const auto count = gguf_get_n_tensors(metadata); + for (std::int64_t index = 0; index < count; ++index) { + const char * name = gguf_get_tensor_name(metadata, index); + auto * tensor = ggml_get_tensor(tensors, name); + if (tensor == nullptr) { + reason = std::string("missing allocated tensor: ") + name; + return false; + } + const auto offset = gguf_get_data_offset(metadata) + gguf_get_tensor_offset(metadata, index); + if (offset > static_cast(std::numeric_limits::max()) || + std::fseek(file.get(), static_cast(offset), SEEK_SET) != 0) { + reason = std::string("cannot seek to tensor data: ") + name; + return false; + } + const auto bytes = ggml_nbytes(tensor); + for (std::size_t position = 0; position < bytes; position += staging.size()) { + const auto amount = std::min(staging.size(), bytes - position); + if (std::fread(staging.data(), 1, amount, file.get()) != amount) { + reason = std::string("truncated tensor data: ") + name; + return false; + } + ggml_backend_tensor_set(tensor, staging.data(), position, amount); + } + } + return true; +} + +} // namespace + +class neural_runtime { +public: + struct component { + std::string name; + context_ptr context; + buffer_ptr buffer; + }; + + backend_ptr backend; + std::vector components; +}; + +namespace { + +mb_status open_component(neural_runtime & runtime, const std::filesystem::path & path, + std::string name, std::string & reason) { + ggml_context * raw_context = nullptr; + const gguf_init_params params{true, &raw_context}; + gguf_ptr metadata(gguf_init_from_file(path.string().c_str(), params)); + context_ptr context(raw_context); + if (!metadata || !context) { + reason = "cannot initialize GGUF weights: " + path.string(); + return MB_INVALID_FORMAT; + } + buffer_ptr buffer(ggml_backend_alloc_ctx_tensors(context.get(), runtime.backend.get())); + if (!buffer) { + reason = "cannot allocate backend weights for " + path.string(); + return MB_OUT_OF_MEMORY; + } + if (!copy_gguf_data(path, context.get(), metadata.get(), reason)) return MB_IO_ERROR; + runtime.components.push_back({std::move(name), std::move(context), std::move(buffer)}); + return MB_OK; +} + +} // namespace + +mb_status create_neural_runtime(const std::filesystem::path & bundle, + mb_device device, std::uint32_t threads, + const std::string & backend_directory, + std::shared_ptr & output, + std::string & reason) { + output.reset(); + if (!backend_directory.empty()) ggml_backend_load_all_from_path(backend_directory.c_str()); + auto runtime = std::make_shared(); + const auto selected = device == MB_DEVICE_AUTO ? MB_DEVICE_CPU : device; + if (selected == MB_DEVICE_CPU) { + runtime->backend.reset(ggml_backend_cpu_init()); + if (runtime->backend) { + const auto hardware = std::max(1U, std::thread::hardware_concurrency()); + const auto count = threads == 0U ? hardware : threads; + ggml_backend_cpu_set_n_threads(runtime->backend.get(), static_cast(count)); + } + } else if (selected == MB_DEVICE_VULKAN) { +#if defined(MOTIONBRICKS_HAVE_VULKAN) + // GGML's Vulkan F16/cooperative-matrix fast paths may be selected even + // for F32 weights. The released MotionBricks baseline is explicitly + // F32, and those paths create material decoder drift after many + // residual convolutions. Keep the parity runtime strictly F32. +#if defined(_WIN32) + _putenv_s("GGML_VK_DISABLE_F16", "1"); + _putenv_s("GGML_VK_DISABLE_COOPMAT", "1"); + _putenv_s("GGML_VK_DISABLE_COOPMAT2", "1"); +#else + setenv("GGML_VK_DISABLE_F16", "1", 0); + setenv("GGML_VK_DISABLE_COOPMAT", "1", 0); + setenv("GGML_VK_DISABLE_COOPMAT2", "1", 0); +#endif + runtime->backend.reset(ggml_backend_vk_init(0)); +#else + reason = "this build has no Vulkan backend"; + return MB_BACKEND_UNAVAILABLE; +#endif + } + if (!runtime->backend) { + reason = selected == MB_DEVICE_VULKAN ? "cannot initialize Vulkan backend" + : "cannot initialize CPU backend"; + return MB_BACKEND_UNAVAILABLE; + } + constexpr std::array files{ + std::pair{"pose", "pose.gguf"}, std::pair{"root", "root.gguf"}, + std::pair{"vq-decoder", "vq-decoder.gguf"}, std::pair{"support", "support.gguf"}, + }; + for (const auto & [name, filename] : files) { + const auto status = open_component(*runtime, bundle / filename, name, reason); + if (status != MB_OK) return status; + } + output = std::move(runtime); + return MB_OK; +} + +ggml_backend_t neural_backend(const neural_runtime & runtime) noexcept { + return runtime.backend.get(); +} + +ggml_tensor * neural_weight(const neural_runtime & runtime, + std::string_view component, std::string_view name) noexcept { + const auto found = std::find_if(runtime.components.begin(), runtime.components.end(), + [&](const neural_runtime::component & item) { return item.name == component; }); + if (found == runtime.components.end()) return nullptr; + std::string owned(name); + if (auto * direct = ggml_get_tensor(found->context.get(), owned.c_str())) return direct; + if (owned.size() >= 64U) { + const std::string needle = "self_attn."; + if (const auto position = owned.find(needle); position != std::string::npos) + owned.replace(position, needle.size(), "attn."); + } + return ggml_get_tensor(found->context.get(), owned.c_str()); +} + +bool neural_copy_f32(const neural_runtime & runtime, + std::string_view component, std::string_view name, + std::vector & output, std::string & reason) { + auto * tensor = neural_weight(runtime, component, name); + if (tensor == nullptr) { + reason = "missing neural tensor: " + std::string(component) + ":" + std::string(name); + return false; + } + if (tensor->type != GGML_TYPE_F32) { + reason = "neural tensor is not F32: " + std::string(component) + ":" + std::string(name); + return false; + } + output.resize(static_cast(ggml_nelements(tensor))); + ggml_backend_tensor_get(tensor, output.data(), 0, output.size() * sizeof(float)); + return true; +} + +#else + +class neural_runtime {}; + +mb_status create_neural_runtime(const std::filesystem::path &, mb_device, std::uint32_t, + const std::string &, std::shared_ptr & output, + std::string & reason) { + output.reset(); + reason = "this build has no GGML support"; + return MB_BACKEND_UNAVAILABLE; +} + +#endif + +} // namespace motionbricks::detail diff --git a/src/neural_runtime.hpp b/src/neural_runtime.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0dd9b7280ef09e24d6c048f36b00ae861f4437e7 --- /dev/null +++ b/src/neural_runtime.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#if defined(MOTIONBRICKS_HAVE_GGML) +#include +#include +#endif + +namespace motionbricks::detail { + +class neural_runtime; + +mb_status create_neural_runtime(const std::filesystem::path & bundle, + mb_device device, std::uint32_t threads, + const std::string & backend_directory, + std::shared_ptr & output, + std::string & reason); + +#if defined(MOTIONBRICKS_HAVE_GGML) +ggml_backend_t neural_backend(const neural_runtime & runtime) noexcept; +ggml_tensor * neural_weight(const neural_runtime & runtime, + std::string_view component, std::string_view name) noexcept; +bool neural_copy_f32(const neural_runtime & runtime, + std::string_view component, std::string_view name, + std::vector & output, std::string & reason); +#endif + +} // namespace motionbricks::detail diff --git a/src/planner.cpp b/src/planner.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c4e963acfff60f4d0feb72080f675fdfd4e28c97 --- /dev/null +++ b/src/planner.cpp @@ -0,0 +1,169 @@ +#include "planner.hpp" + +#include "decoder.hpp" +#include "handles.hpp" +#include "neural_runtime.hpp" +#include "pose.hpp" +#include "root.hpp" + +#include +#include +#include +#include +#include + +namespace motionbricks::detail { +namespace { + +float wrapped(float value) { + constexpr float pi = 3.14159265358979323846F; + return std::remainder(value, 2.0F * pi); +} + +} // namespace + +mb_status run_transition(const mb_model & model, + const transition_constraints & constraints, + mb_motion & output, + std::uint32_t * selected_tokens, + std::string & reason) { + if (!model.runtime || model.motion_mean.size() != 418U || model.motion_std.size() != 418U) { + reason = "model is missing neural or normalization data"; + return MB_INCOMPATIBLE_MODEL; + } + const float initial_x = constraints.global_root[0]; + const float initial_z = constraints.global_root[2]; + const float initial_heading = std::atan2(constraints.global_root[4], constraints.global_root[3]); + + std::vector global(8U * global_root_width); + std::vector local(8U * local_root_width); + std::vector poses(8U * internal_pose_width); + for (std::uint32_t frame = 0; frame < 8U; ++frame) { + for (std::uint32_t feature = 0; feature < global_root_width; ++feature) { + float value = constraints.global_root[frame * global_root_width + feature]; + if (feature == 0U) value -= initial_x; + if (feature == 2U) value -= initial_z; + global[frame * global_root_width + feature] = normalize_feature(model, value, feature); + } + for (std::uint32_t feature = 0; feature < local_root_width; ++feature) + local[frame * local_root_width + feature] = normalize_feature( + model, constraints.local_root[frame * local_root_width + feature], 5U + feature); + + poses[frame * internal_pose_width] = normalize_feature( + model, constraints.global_root[frame * global_root_width + 1U], 8U); + for (std::uint32_t feature = 0; feature < external_pose_width; ++feature) + poses[frame * internal_pose_width + 1U + feature] = normalize_feature( + model, constraints.poses[frame * external_pose_width + feature], 9U + feature); + } + + root_result root; + auto status = run_root_planner_auto_probe(*model.runtime, global, constraints.has_global_root, + local, constraints.has_local_root, poses, constraints.has_poses, 6U, root, reason); + if (status != MB_OK) return status; + std::uint32_t tokens = 0U; + float best = -std::numeric_limits::infinity(); + for (std::uint32_t index = 0; index < 11U; ++index) { + if (constraints.allowed_tokens[index] != 0U && root.duration_logits[index] > best) { + best = root.duration_logits[index]; + tokens = 6U + index; + } + } + if (tokens == 0U) { + reason = "style disallows every reachable duration"; + return MB_INVALID_ARGUMENT; + } + if (tokens != 6U) { + status = run_root_planner_auto_probe(*model.runtime, global, constraints.has_global_root, + local, constraints.has_local_root, poses, constraints.has_poses, tokens, root, reason); + if (status != MB_OK) return status; + } + const std::uint32_t frames = tokens * 4U; + + std::vector predicted_local(static_cast(frames) * local_root_width); + std::vector raw_global(static_cast(frames) * global_root_width); + std::vector heading(frames); + for (std::uint32_t frame = 0; frame < frames; ++frame) { + for (std::uint32_t feature = 0; feature < global_root_width; ++feature) + raw_global[frame * global_root_width + feature] = unnormalize_feature( + model, root.global_root_values[frame * global_root_width + feature], feature); + heading[frame] = std::atan2(raw_global[frame * global_root_width + 4U], + raw_global[frame * global_root_width + 3U]); + } + for (std::uint32_t frame = 0; frame < frames; ++frame) { + const auto next = std::min(frame + 1U, frames - 1U); + const auto velocity_from = frame + 1U < frames ? frame : frame - 1U; + const auto velocity_to = frame + 1U < frames ? next : frame; + const float angular_velocity = wrapped(heading[velocity_to] - heading[velocity_from]) * 30.0F; + const float x_velocity = (raw_global[velocity_to * global_root_width] - + raw_global[velocity_from * global_root_width]) * 30.0F; + const float z_velocity = (raw_global[velocity_to * global_root_width + 2U] - + raw_global[velocity_from * global_root_width + 2U]) * 30.0F; + const float raw_values[4]{angular_velocity, x_velocity, z_velocity, + raw_global[frame * global_root_width + 1U]}; + for (std::uint32_t feature = 0; feature < local_root_width; ++feature) + predicted_local[frame * local_root_width + feature] = + normalize_feature(model, raw_values[feature], 5U + feature); + } + if (constraints.has_local_root[7] != 0U) + std::copy_n(local.data() + 7U * local_root_width, local_root_width, + predicted_local.data() + (frames - 1U) * local_root_width); + + std::vector pose_condition(static_cast(frames) * internal_pose_width, 0.0F); + std::vector has_pose_condition(frames, 0U); + for (std::uint32_t boundary = 0; boundary < 8U; ++boundary) { + const std::uint32_t frame = boundary < 4U ? boundary : frames - 8U + boundary; + std::copy_n(poses.data() + boundary * internal_pose_width, internal_pose_width, + pose_condition.data() + static_cast(frame) * internal_pose_width); + has_pose_condition[frame] = constraints.has_poses[boundary]; + } + std::vector pose_root(static_cast(frames) * 4U); + for (std::uint32_t frame = 0; frame < frames; ++frame) { + const float * source = root.global_root_values.data() + frame * global_root_width; + float * destination = pose_root.data() + frame * 4U; + destination[0] = source[0]; destination[1] = source[2]; + destination[2] = source[3]; destination[3] = source[4]; + } + std::vector pose_tokens(static_cast(tokens) * 8U, 10); + std::vector logits; + status = run_pose_planner(*model.runtime, pose_tokens, pose_root, pose_condition, + has_pose_condition, tokens, tokens, logits, reason); + if (status != MB_OK) return status; + for (std::size_t item = 0; item < pose_tokens.size(); ++item) { + const auto begin = logits.begin() + static_cast(item * 10U); + pose_tokens[item] = static_cast(std::max_element(begin, begin + 10) - begin); + } + + std::vector codebook; + if (!neural_copy_f32(*model.runtime, "vq-decoder", "quantizer.vq._codebook.embed", + codebook, reason)) return MB_INCOMPATIBLE_MODEL; + if (codebook.size() != 8U * 10U * 32U) { + reason = "VQ codebook shape mismatch"; + return MB_INCOMPATIBLE_MODEL; + } + std::vector quantized(static_cast(tokens) * 256U); + for (std::uint32_t position = 0; position < tokens; ++position) + for (std::uint32_t head = 0; head < 8U; ++head) + for (std::uint32_t dimension = 0; dimension < 32U; ++dimension) + quantized[(head * 32U + dimension) * tokens + position] = + codebook[(head * 10U + static_cast( + pose_tokens[position * 8U + head])) * 32U + dimension]; + + std::vector external(static_cast(frames) * 2U); + for (std::uint32_t frame = 0; frame < frames; ++frame) { + external[frame * 2U] = predicted_local[frame * local_root_width + 1U]; + external[frame * 2U + 1U] = predicted_local[frame * local_root_width + 2U]; + } + std::vector decoder_mask(frames, 0U); + for (std::uint32_t frame = 0; frame < 4U; ++frame) + decoder_mask[frame] = has_pose_condition[frame]; + std::vector decoded; + status = run_vq_decoder(*model.runtime, quantized, external, pose_condition, + decoder_mask, tokens, decoded, reason); + if (status != MB_OK) return status; + status = decode_motion(model, decoded, frames, initial_x, initial_z, + initial_heading, output, reason); + if (status == MB_OK && selected_tokens != nullptr) *selected_tokens = tokens; + return status; +} + +} // namespace motionbricks::detail diff --git a/src/planner.hpp b/src/planner.hpp new file mode 100644 index 0000000000000000000000000000000000000000..25a38c5601baab27435928b2eece35a93fc7e9bc --- /dev/null +++ b/src/planner.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "motion_rep.hpp" + +#include + +#include +#include +#include + +struct mb_model; +struct mb_motion; + +namespace motionbricks::detail { + +struct transition_constraints { + std::array global_root{}; + std::array local_root{}; + std::array poses{}; + std::array has_global_root{1,1,1,1,1,1,1,1}; + std::array has_local_root{1,1,1,0,1,1,1,1}; + std::array has_poses{1,1,1,1,1,1,1,1}; + std::array allowed_tokens{1,1,1,1,1,1,1,1,1,1,1}; + std::array target_root_translations{}; + std::array target_local_rotations_xyzw{}; +}; + +mb_status run_transition(const mb_model & model, + const transition_constraints & constraints, + mb_motion & output, + std::uint32_t * selected_tokens, + std::string & reason); + +} // namespace motionbricks::detail diff --git a/src/pose.cpp b/src/pose.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d3bf9eb8f08a4086670aacb6fd75527dcd5a373a --- /dev/null +++ b/src/pose.cpp @@ -0,0 +1,235 @@ +#include "pose.hpp" + +#include "neural_runtime.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(MOTIONBRICKS_HAVE_GGML) +#include +#include +#endif + +namespace motionbricks::detail { +namespace { + +#if defined(MOTIONBRICKS_HAVE_GGML) + +struct context_deleter { + void operator()(ggml_context * value) const noexcept { ggml_free(value); } +}; +struct buffer_deleter { + void operator()(ggml_backend_buffer * value) const noexcept { ggml_backend_buffer_free(value); } +}; +using context_ptr = std::unique_ptr; +using buffer_ptr = std::unique_ptr; + +ggml_tensor * weight(const neural_runtime & runtime, const std::string & name, + std::string & reason) { + auto * result = neural_weight(runtime, "pose", name); + if (result == nullptr && reason.empty()) reason = "missing pose weight: " + name; + return result; +} + +ggml_tensor * linear(ggml_context * context, ggml_tensor * input, + ggml_tensor * matrix, ggml_tensor * bias) { + return ggml_add(context, ggml_mul_mat(context, matrix, input), bias); +} + +ggml_tensor * layer_norm(ggml_context * context, ggml_tensor * input, + ggml_tensor * scale, ggml_tensor * bias) { + return ggml_add(context, ggml_mul(context, ggml_norm(context, input, 1.0e-5F), scale), bias); +} + +ggml_tensor * transformer_layer(ggml_context * context, const neural_runtime & runtime, + ggml_tensor * input, unsigned layer, std::uint32_t positions, + std::string & reason) { + constexpr std::int64_t embedding = 1024; + constexpr std::int64_t heads = 16; + constexpr std::int64_t head_width = embedding / heads; + const auto prefix = "_transformer_model.layers." + std::to_string(layer) + "."; + auto * qkv_weight = weight(runtime, prefix + "self_attn.in_proj_weight", reason); + auto * qkv_bias = weight(runtime, prefix + "self_attn.in_proj_bias", reason); + auto * out_weight = weight(runtime, prefix + "self_attn.out_proj.weight", reason); + auto * out_bias = weight(runtime, prefix + "self_attn.out_proj.bias", reason); + auto * norm1_weight = weight(runtime, prefix + "norm1.weight", reason); + auto * norm1_bias = weight(runtime, prefix + "norm1.bias", reason); + auto * linear1_weight = weight(runtime, prefix + "linear1.weight", reason); + auto * linear1_bias = weight(runtime, prefix + "linear1.bias", reason); + auto * linear2_weight = weight(runtime, prefix + "linear2.weight", reason); + auto * linear2_bias = weight(runtime, prefix + "linear2.bias", reason); + auto * norm2_weight = weight(runtime, prefix + "norm2.weight", reason); + auto * norm2_bias = weight(runtime, prefix + "norm2.bias", reason); + if (!reason.empty()) return nullptr; + + auto * qkv = linear(context, input, qkv_weight, qkv_bias); + const auto stride = qkv->nb[1]; + auto * query_values = ggml_view_2d(context, qkv, embedding, positions, stride, 0); + auto * key_values = ggml_view_2d(context, qkv, embedding, positions, stride, + static_cast(embedding) * sizeof(float)); + auto * value_values = ggml_view_2d(context, qkv, embedding, positions, stride, + static_cast(embedding * 2) * sizeof(float)); + auto * query = ggml_permute(context, + ggml_cont_3d(context, query_values, head_width, heads, positions), 0, 2, 1, 3); + auto * key = ggml_permute(context, + ggml_cont_3d(context, key_values, head_width, heads, positions), 0, 2, 1, 3); + auto * scores = ggml_mul_mat(context, key, query); + scores = ggml_scale(context, scores, 1.0F / std::sqrt(static_cast(head_width))); + scores = ggml_soft_max(context, scores); + auto * value_transposed = ggml_cont_3d(context, + ggml_permute(context, + ggml_cont_3d(context, value_values, head_width, heads, positions), 1, 2, 0, 3), + positions, head_width, heads); + auto * attended = ggml_mul_mat(context, value_transposed, scores); + auto * merged = ggml_cont_2d(context, ggml_permute(context, attended, 0, 2, 1, 3), + embedding, positions); + auto * attention_output = linear(context, merged, out_weight, out_bias); + auto * normalized_attention = layer_norm( + context, ggml_add(context, input, attention_output), norm1_weight, norm1_bias); + auto * feed_forward = ggml_relu(context, + linear(context, normalized_attention, linear1_weight, linear1_bias)); + feed_forward = linear(context, feed_forward, linear2_weight, linear2_bias); + return layer_norm(context, ggml_add(context, normalized_attention, feed_forward), + norm2_weight, norm2_bias); +} + +#endif + +} // namespace + +mb_status run_pose_planner(const neural_runtime & runtime, + std::span pose_tokens, + std::span local_root_values, + std::span pose_condition, + std::span has_pose_condition, + std::uint32_t positions, + std::uint32_t num_tokens, + std::vector & logits, + std::string & reason) { +#if !defined(MOTIONBRICKS_HAVE_GGML) + (void)runtime; (void)pose_tokens; (void)local_root_values; (void)pose_condition; + (void)has_pose_condition; (void)positions; (void)num_tokens; (void)logits; + reason = "this build has no GGML support"; + return MB_BACKEND_UNAVAILABLE; +#else + constexpr std::uint32_t pose_heads = 8; + constexpr std::uint32_t frames_per_position = 4; + constexpr std::uint32_t root_width = 4; + constexpr std::uint32_t pose_width = 304; + if (positions < 6U || positions > 16U || num_tokens < 6U || num_tokens > positions) { + reason = "pose planner token count is outside the released model range"; + return MB_INVALID_ARGUMENT; + } + const auto frames = positions * frames_per_position; + if (pose_tokens.size() != static_cast(positions) * pose_heads || + local_root_values.size() != static_cast(frames) * root_width || + pose_condition.size() != static_cast(frames) * pose_width || + has_pose_condition.size() != frames) { + reason = "pose planner input shape mismatch"; + return MB_INVALID_ARGUMENT; + } + + constexpr std::size_t context_bytes = 32U * 1024U * 1024U; + context_ptr context(ggml_init({context_bytes, nullptr, true})); + if (!context) { + reason = "cannot allocate pose graph metadata"; + return MB_OUT_OF_MEMORY; + } + auto * token_input = ggml_new_tensor_1d(context.get(), GGML_TYPE_I32, + static_cast(pose_heads) * positions); + auto * root_input = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, root_width, frames); + auto * condition_input = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, pose_width, frames); + auto * mask_input = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, 1, frames); + auto * duration_input = ggml_new_tensor_1d(context.get(), GGML_TYPE_I32, 1); + ggml_set_input(token_input); + ggml_set_input(root_input); + ggml_set_input(condition_input); + ggml_set_input(mask_input); + ggml_set_input(duration_input); + + auto * token_embedding_weight = weight(runtime, "_pose_token_emb.weight", reason); + auto * token_embedding = ggml_get_rows(context.get(), token_embedding_weight, token_input); + token_embedding = ggml_reshape_2d(context.get(), token_embedding, 256, positions); + token_embedding = ggml_leaky_relu(context.get(), linear(context.get(), token_embedding, + weight(runtime, "_proj_pose_token_emb.fc_layers.0.weight", reason), + weight(runtime, "_proj_pose_token_emb.fc_layers.0.bias", reason)), 0.01F, false); + token_embedding = ggml_leaky_relu(context.get(), linear(context.get(), token_embedding, + weight(runtime, "_proj_pose_token_emb.fc_layers.1.weight", reason), + weight(runtime, "_proj_pose_token_emb.fc_layers.1.bias", reason)), 0.01F, false); + token_embedding = linear(context.get(), token_embedding, + weight(runtime, "_proj_pose_token_emb.forward_projection.weight", reason), + weight(runtime, "_proj_pose_token_emb.forward_projection.bias", reason)); + token_embedding = ggml_reshape_2d(context.get(), token_embedding, 160, frames); + + auto * condition_embedding = linear(context.get(), condition_input, + weight(runtime, "_proj_local_pose.weight", reason), + weight(runtime, "_proj_local_pose.bias", reason)); + auto * pose_embedding = ggml_add(context.get(), token_embedding, + ggml_mul(context.get(), ggml_sub(context.get(), condition_embedding, token_embedding), + mask_input)); + pose_embedding = ggml_reshape_2d(context.get(), pose_embedding, 640, positions); + + auto * root_embedding = linear(context.get(), + ggml_reshape_2d(context.get(), root_input, 16, positions), + weight(runtime, "_proj_local_root_values.weight", reason), + weight(runtime, "_proj_local_root_values.bias", reason)); + auto * duration_embedding = ggml_get_rows(context.get(), + weight(runtime, "_proj_num_valid_positions.weight", reason), duration_input); + duration_embedding = ggml_repeat(context.get(), duration_embedding, + ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, 128, positions)); + auto * combined = ggml_concat(context.get(), pose_embedding, root_embedding, 0); + combined = ggml_concat(context.get(), combined, duration_embedding, 0); + auto * hidden = ggml_relu(context.get(), linear(context.get(), combined, + weight(runtime, "_proj_input.0.weight", reason), + weight(runtime, "_proj_input.0.bias", reason))); + auto * position_weight = weight(runtime, "_position_emb.embed", reason); + auto * position_matrix = ggml_reshape_2d(context.get(), position_weight, 1024, 16); + auto * position_view = ggml_view_2d(context.get(), position_matrix, 1024, positions, + position_matrix->nb[1], 0); + hidden = ggml_add(context.get(), hidden, position_view); + if (!reason.empty()) return MB_INCOMPATIBLE_MODEL; + for (unsigned layer = 0; layer < 16U; ++layer) { + hidden = transformer_layer(context.get(), runtime, hidden, layer, positions, reason); + if (hidden == nullptr) return MB_INCOMPATIBLE_MODEL; + } + auto * output = linear(context.get(), hidden, + weight(runtime, "_proj_pose_output_logit.weight", reason), + weight(runtime, "_proj_pose_output_logit.bias", reason)); + if (!reason.empty()) return MB_INCOMPATIBLE_MODEL; + + auto * graph = ggml_new_graph_custom(context.get(), 2048, false); + ggml_build_forward_expand(graph, output); + buffer_ptr buffer(ggml_backend_alloc_ctx_tensors(context.get(), neural_backend(runtime))); + if (!buffer) { + reason = "cannot allocate pose compute buffer"; + return MB_OUT_OF_MEMORY; + } + std::vector offset_tokens(pose_tokens.begin(), pose_tokens.end()); + for (std::uint32_t position = 0; position < positions; ++position) + for (std::uint32_t head = 0; head < pose_heads; ++head) + offset_tokens[static_cast(position) * pose_heads + head] += + static_cast(head * 11U); + std::vector mask(has_pose_condition.begin(), has_pose_condition.end()); + const std::int32_t duration_index = static_cast(num_tokens - 6U); + ggml_backend_tensor_set(token_input, offset_tokens.data(), 0, ggml_nbytes(token_input)); + ggml_backend_tensor_set(root_input, local_root_values.data(), 0, ggml_nbytes(root_input)); + ggml_backend_tensor_set(condition_input, pose_condition.data(), 0, ggml_nbytes(condition_input)); + ggml_backend_tensor_set(mask_input, mask.data(), 0, ggml_nbytes(mask_input)); + ggml_backend_tensor_set(duration_input, &duration_index, 0, sizeof(duration_index)); + const auto status = ggml_backend_graph_compute(neural_backend(runtime), graph); + if (status != GGML_STATUS_SUCCESS) { + reason = std::string("pose graph failed: ") + ggml_status_to_string(status); + return MB_COMPUTE_FAILED; + } + logits.resize(static_cast(positions) * 80U); + ggml_backend_tensor_get(output, logits.data(), 0, logits.size() * sizeof(float)); + return MB_OK; +#endif +} + +} // namespace motionbricks::detail diff --git a/src/pose.hpp b/src/pose.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a168e740418d7dbc7e0e3d26bb2f26dfc6513109 --- /dev/null +++ b/src/pose.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace motionbricks::detail { + +class neural_runtime; + +mb_status run_pose_planner(const neural_runtime & runtime, + std::span pose_tokens, + std::span local_root_values, + std::span pose_condition, + std::span has_pose_condition, + std::uint32_t positions, + std::uint32_t num_tokens, + std::vector & logits, + std::string & reason); + +} // namespace motionbricks::detail diff --git a/src/root.cpp b/src/root.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4f0e09e57185a31a7f53d66d35b152e3d1c0e002 --- /dev/null +++ b/src/root.cpp @@ -0,0 +1,372 @@ +#include "root.hpp" + +#include "neural_runtime.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(MOTIONBRICKS_HAVE_GGML) +#include +#include +#endif + +namespace motionbricks::detail { +namespace { +#if defined(MOTIONBRICKS_HAVE_GGML) + +struct context_deleter { void operator()(ggml_context * value) const noexcept { ggml_free(value); } }; +struct buffer_deleter { + void operator()(ggml_backend_buffer * value) const noexcept { ggml_backend_buffer_free(value); } +}; +using context_ptr = std::unique_ptr; +using buffer_ptr = std::unique_ptr; + +ggml_tensor * weight(const neural_runtime & runtime, const std::string & name, + std::string & reason) { + auto * result = neural_weight(runtime, "root", name); + if (result == nullptr && reason.empty()) reason = "missing root weight: " + name; + return result; +} + +ggml_tensor * linear(ggml_context * context, ggml_tensor * input, + ggml_tensor * matrix, ggml_tensor * bias) { + return ggml_add(context, ggml_mul_mat(context, matrix, input), bias); +} + +ggml_tensor * leaky_mlp(ggml_context * context, const neural_runtime & runtime, + ggml_tensor * input, const std::string & prefix, + std::string & reason) { + auto * hidden = input; + for (unsigned layer = 0; layer < 2U; ++layer) { + const auto stem = prefix + ".fc_layers." + std::to_string(layer) + "."; + hidden = ggml_leaky_relu(context, linear(context, hidden, + weight(runtime, stem + "weight", reason), weight(runtime, stem + "bias", reason)), + 0.01F, false); + } + return linear(context, hidden, weight(runtime, prefix + ".forward_projection.weight", reason), + weight(runtime, prefix + ".forward_projection.bias", reason)); +} + +ggml_tensor * layer_norm(ggml_context * context, ggml_tensor * input, + ggml_tensor * scale, ggml_tensor * bias) { + return ggml_add(context, ggml_mul(context, ggml_norm(context, input, 1.0e-5F), scale), bias); +} + +ggml_tensor * transformer(ggml_context * context, const neural_runtime & runtime, + ggml_tensor * input, const std::string & model_name, + unsigned layers, std::uint32_t sequence, std::string & reason) { + constexpr std::int64_t embedding = 512, heads = 16, head_width = 32; + auto * hidden = input; + for (unsigned layer = 0; layer < layers; ++layer) { + const auto prefix = model_name + ".layers." + std::to_string(layer) + "."; + auto * qkv = linear(context, hidden, + weight(runtime, prefix + "self_attn.in_proj_weight", reason), + weight(runtime, prefix + "self_attn.in_proj_bias", reason)); + const auto stride = qkv->nb[1]; + auto * q_values = ggml_view_2d(context, qkv, embedding, sequence, stride, 0); + auto * k_values = ggml_view_2d(context, qkv, embedding, sequence, stride, + static_cast(embedding) * sizeof(float)); + auto * v_values = ggml_view_2d(context, qkv, embedding, sequence, stride, + static_cast(embedding * 2) * sizeof(float)); + auto * query = ggml_permute(context, + ggml_cont_3d(context, q_values, head_width, heads, sequence), 0, 2, 1, 3); + auto * key = ggml_permute(context, + ggml_cont_3d(context, k_values, head_width, heads, sequence), 0, 2, 1, 3); + auto * scores = ggml_soft_max(context, ggml_scale(context, + ggml_mul_mat(context, key, query), 1.0F / std::sqrt(static_cast(head_width)))); + auto * value_transposed = ggml_cont_3d(context, + ggml_permute(context, + ggml_cont_3d(context, v_values, head_width, heads, sequence), 1, 2, 0, 3), + sequence, head_width, heads); + auto * attended = ggml_mul_mat(context, value_transposed, scores); + auto * merged = ggml_cont_2d(context, ggml_permute(context, attended, 0, 2, 1, 3), + embedding, sequence); + auto * attention = linear(context, merged, + weight(runtime, prefix + "self_attn.out_proj.weight", reason), + weight(runtime, prefix + "self_attn.out_proj.bias", reason)); + hidden = layer_norm(context, ggml_add(context, hidden, attention), + weight(runtime, prefix + "norm1.weight", reason), + weight(runtime, prefix + "norm1.bias", reason)); + auto * feed = ggml_relu(context, linear(context, hidden, + weight(runtime, prefix + "linear1.weight", reason), + weight(runtime, prefix + "linear1.bias", reason))); + feed = linear(context, feed, weight(runtime, prefix + "linear2.weight", reason), + weight(runtime, prefix + "linear2.bias", reason)); + hidden = layer_norm(context, ggml_add(context, hidden, feed), + weight(runtime, prefix + "norm2.weight", reason), + weight(runtime, prefix + "norm2.bias", reason)); + } + return hidden; +} + +ggml_tensor * conv(ggml_context * context, ggml_tensor * input, + ggml_tensor * kernel, ggml_tensor * bias, int padding, int dilation) { + auto * columns = ggml_im2col(context, kernel, input, 1, 0, padding, 0, + dilation, 0, false, GGML_TYPE_F32); + auto * output = ggml_mul_mat(context, + ggml_reshape_2d(context, columns, columns->ne[0], columns->ne[2] * columns->ne[1]), + ggml_reshape_2d(context, kernel, kernel->ne[0] * kernel->ne[1], kernel->ne[2])); + output = ggml_reshape_3d(context, output, columns->ne[1], kernel->ne[2], columns->ne[2]); + return ggml_add(context, output, ggml_reshape_2d(context, bias, 1, bias->ne[0])); +} + +ggml_tensor * residuals(ggml_context * context, const neural_runtime & runtime, + ggml_tensor * input, unsigned stage, std::string & reason) { + static constexpr std::array dilations{27, 9, 3, 1}; + auto * hidden = input; + for (unsigned block = 0; block < dilations.size(); ++block) { + const auto prefix = "_conv_output.model." + std::to_string(stage) + ".0.model." + + std::to_string(block) + "."; + auto * branch = conv(context, ggml_relu(context, hidden), + weight(runtime, prefix + "conv1.weight", reason), + weight(runtime, prefix + "conv1.bias", reason), dilations[block], dilations[block]); + branch = conv(context, ggml_relu(context, branch), + weight(runtime, prefix + "conv2.weight", reason), + weight(runtime, prefix + "conv2.bias", reason), 0, 1); + hidden = ggml_add(context, hidden, branch); + } + return hidden; +} + +ggml_tensor * transpose_contiguous(ggml_context * context, ggml_tensor * input, + std::int64_t first, std::int64_t second) { + return ggml_cont_2d(context, ggml_transpose(context, input), first, second); +} + +ggml_tensor * root_decoder(ggml_context * context, const neural_runtime & runtime, + ggml_tensor * tokens, ggml_tensor * external, + ggml_tensor * target, ggml_tensor * target_mask, + std::uint32_t positions, std::string & reason) { + const auto frames = positions * 4U; + auto * hidden = ggml_relu(context, conv(context, tokens, + weight(runtime, "_conv_output.model.0.weight", reason), + weight(runtime, "_conv_output.model.0.bias", reason), 1, 1)); + for (unsigned stage_index = 0; stage_index < 2U; ++stage_index) { + const unsigned stage = stage_index + 2U; + const auto group = 1U << (2U - stage_index); + const auto stage_positions = positions * (1U << stage_index); + auto * target_emb = ggml_relu(context, linear(context, target, + weight(runtime, "_conv_output.target_cond_blocks." + std::to_string(stage_index * 2U) + ".weight", reason), + weight(runtime, "_conv_output.target_cond_blocks." + std::to_string(stage_index * 2U) + ".bias", reason))); + auto * hidden_frames = transpose_contiguous(context, hidden, 512, stage_positions); + hidden_frames = ggml_reshape_2d(context, hidden_frames, 512 / group, frames); + hidden_frames = ggml_add(context, hidden_frames, + ggml_mul(context, ggml_sub(context, target_emb, hidden_frames), target_mask)); + hidden_frames = ggml_reshape_2d(context, hidden_frames, 512, stage_positions); + auto * external_grouped = ggml_reshape_2d(context, external, 512 * group, stage_positions); + auto * fused = ggml_relu(context, linear(context, + ggml_concat(context, hidden_frames, external_grouped, 0), + weight(runtime, "_conv_output.external_cond_blocks." + std::to_string(stage_index * 2U) + ".weight", reason), + weight(runtime, "_conv_output.external_cond_blocks." + std::to_string(stage_index * 2U) + ".bias", reason))); + hidden = transpose_contiguous(context, fused, stage_positions, 512); + hidden = residuals(context, runtime, hidden, stage, reason); + hidden = ggml_interpolate(context, hidden, hidden->ne[0] * 2, hidden->ne[1], + hidden->ne[2], hidden->ne[3], GGML_SCALE_MODE_NEAREST); + hidden = conv(context, hidden, + weight(runtime, "_conv_output.model." + std::to_string(stage) + ".2.weight", reason), + weight(runtime, "_conv_output.model." + std::to_string(stage) + ".2.bias", reason), 1, 1); + } + hidden = ggml_relu(context, conv(context, hidden, + weight(runtime, "_conv_output.model.4.weight", reason), + weight(runtime, "_conv_output.model.4.bias", reason), 1, 1)); + hidden = conv(context, hidden, weight(runtime, "_conv_output.model.6.weight", reason), + weight(runtime, "_conv_output.model.6.bias", reason), 1, 1); + return transpose_contiguous(context, hidden, 5, frames); +} + +#endif +} // namespace + +namespace { + +mb_status run_root_planner_impl(const neural_runtime & runtime, + std::span global_root_values, + std::span has_global_root_values, + std::span local_root_values, + std::span has_local_root_values, + std::span poses, + std::span has_poses, + std::uint32_t requested_tokens, + std::uint32_t duration_input_tokens, + root_result & output, + std::string & reason) { +#if !defined(MOTIONBRICKS_HAVE_GGML) + (void)runtime; (void)global_root_values; (void)has_global_root_values; + (void)local_root_values; (void)has_local_root_values; (void)poses; (void)has_poses; + (void)requested_tokens; (void)duration_input_tokens; (void)output; + reason = "this build has no GGML support"; + return MB_BACKEND_UNAVAILABLE; +#else + constexpr std::uint32_t boundary_frames = 8U; + if (requested_tokens < 6U || requested_tokens > 16U || + (duration_input_tokens != 18U && + (duration_input_tokens < 6U || duration_input_tokens > 16U)) || + global_root_values.size() != 40U || + local_root_values.size() != 32U || poses.size() != 2432U || + has_global_root_values.size() != boundary_frames || + has_local_root_values.size() != boundary_frames || has_poses.size() != boundary_frames) { + reason = "root planner input shape mismatch"; + return MB_INVALID_ARGUMENT; + } + const auto frames = requested_tokens * 4U; + context_ptr context(ggml_init({48U * 1024U * 1024U, nullptr, true})); + if (!context) { reason = "cannot allocate root graph metadata"; return MB_OUT_OF_MEMORY; } + auto * global_input = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, 5, boundary_frames); + auto * local_input = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, 4, boundary_frames); + auto * pose_input = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, 304, boundary_frames); + auto * global_mask = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, 1, boundary_frames); + auto * local_mask = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, 1, boundary_frames); + auto * pose_mask = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, 1, boundary_frames); + auto * dense_target = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, 5, frames); + auto * dense_target_mask = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, 1, frames); + auto * duration_input = ggml_new_tensor_1d(context.get(), GGML_TYPE_I32, 1); + auto * planned_duration_input = ggml_new_tensor_1d(context.get(), GGML_TYPE_I32, 1); + for (auto * tensor : {global_input, local_input, pose_input, global_mask, local_mask, pose_mask, + dense_target, dense_target_mask, duration_input, planned_duration_input}) + ggml_set_input(tensor); + + auto blend = [&](ggml_tensor * projected, const char * absent_name, ggml_tensor * mask) { + auto * absent = weight(runtime, absent_name, reason); + return ggml_add(context.get(), + ggml_mul(context.get(), ggml_sub(context.get(), projected, absent), mask), absent); + }; + auto * pose_emb = blend(linear(context.get(), pose_input, + weight(runtime, "_proj_local_pose.weight", reason), weight(runtime, "_proj_local_pose.bias", reason)), + "_no_local_pose_emb", pose_mask); + auto * local_emb = blend(linear(context.get(), local_input, + weight(runtime, "_proj_local_root_value.weight", reason), weight(runtime, "_proj_local_root_value.bias", reason)), + "_no_local_root_emb", local_mask); + auto * global_emb = blend(linear(context.get(), global_input, + weight(runtime, "_proj_global_root_value.weight", reason), weight(runtime, "_proj_global_root_value.bias", reason)), + "_no_global_root_emb", global_mask); + auto * boundary = ggml_concat(context.get(), ggml_concat(context.get(), pose_emb, local_emb, 0), global_emb, 0); + auto * start = ggml_view_2d(context.get(), boundary, 384, 4, boundary->nb[1], 0); + auto * end = ggml_view_2d(context.get(), boundary, 384, 4, boundary->nb[1], 4U * boundary->nb[1]); + auto * start_frame = leaky_mlp(context.get(), runtime, start, "_proj_start_input", reason); + auto * end_frame = leaky_mlp(context.get(), runtime, end, "_proj_end_input", reason); + auto * frame_emb = ggml_concat(context.get(), start_frame, end_frame, 1); + auto * positioned = ggml_add(context.get(), frame_emb, weight(runtime, "_input_position_emb.weight", reason)); + auto * duration_emb = ggml_get_rows(context.get(), + weight(runtime, "_proj_input_num_tokens.weight", reason), duration_input); + auto * first_input = ggml_concat(context.get(), duration_emb, positioned, 1); + auto * first_output = transformer(context.get(), runtime, first_input, + "_shared_transformer_model", 3, 9, reason); + auto * first_token = ggml_view_2d(context.get(), first_output, 512, 1, first_output->nb[1], 0); + auto * duration_logits = linear(context.get(), first_token, + weight(runtime, "_proj_num_token_output_logit.weight", reason), + weight(runtime, "_proj_num_token_output_logit.bias", reason)); + + auto * middle_emb = ggml_get_rows(context.get(), weight(runtime, "_middle_token_emb.weight", reason), + planned_duration_input); + auto * positions_all = ggml_reshape_2d(context.get(), weight(runtime, "_position_emb.embed", reason), 512, 16); + auto * positions_view = ggml_view_2d(context.get(), positions_all, 512, requested_tokens, + positions_all->nb[1], 0); + auto * second_input = ggml_concat(context.get(), middle_emb, positioned, 1); + second_input = ggml_concat(context.get(), second_input, positions_view, 1); + auto * second_output = transformer(context.get(), runtime, second_input, + "_root_token_transformer_model", 3, 9U + requested_tokens, reason); + auto * root_tokens = ggml_view_2d(context.get(), second_output, 512, requested_tokens, + second_output->nb[1], 9U * second_output->nb[1]); + auto * decoder_tokens = transpose_contiguous(context.get(), root_tokens, requested_tokens, 512); + + auto * has_frame = ggml_clamp(context.get(), ggml_add(context.get(), + ggml_add(context.get(), global_mask, local_mask), pose_mask), 0.0F, 1.0F); + auto * absent_frame = weight(runtime, "_conv_no_frame_emb", reason); + auto * masked_frames = ggml_add(context.get(), + ggml_mul(context.get(), ggml_sub(context.get(), frame_emb, absent_frame), has_frame), absent_frame); + auto * first_frames = ggml_view_2d(context.get(), masked_frames, 512, 4, masked_frames->nb[1], 0); + auto * last_frames = ggml_view_2d(context.get(), masked_frames, 512, 4, masked_frames->nb[1], + 4U * masked_frames->nb[1]); + auto * middle_shape = ggml_new_tensor_2d(context.get(), GGML_TYPE_F32, 512, frames - 8U); + auto * middle_frames = ggml_repeat(context.get(), absent_frame, middle_shape); + auto * dense_frame = ggml_concat(context.get(), first_frames, middle_frames, 1); + dense_frame = ggml_concat(context.get(), dense_frame, last_frames, 1); + auto * root_values = root_decoder(context.get(), runtime, decoder_tokens, dense_frame, + dense_target, dense_target_mask, requested_tokens, reason); + if (!reason.empty()) return MB_INCOMPATIBLE_MODEL; + auto * graph = ggml_new_graph_custom(context.get(), 4096, false); + ggml_build_forward_expand(graph, duration_logits); + ggml_build_forward_expand(graph, root_values); + buffer_ptr buffer(ggml_backend_alloc_ctx_tensors(context.get(), neural_backend(runtime))); + if (!buffer) { reason = "cannot allocate root compute buffer"; return MB_OUT_OF_MEMORY; } + + std::vector global_mask_f(has_global_root_values.begin(), has_global_root_values.end()); + std::vector local_mask_f(has_local_root_values.begin(), has_local_root_values.end()); + std::vector pose_mask_f(has_poses.begin(), has_poses.end()); + std::vector dense_values(static_cast(frames) * 5U, 0.0F); + std::vector dense_mask_values(frames, 0.0F); + for (std::uint32_t boundary_index = 0; boundary_index < 8U; ++boundary_index) { + const auto frame = boundary_index < 4U ? boundary_index : frames - 8U + boundary_index; + std::copy_n(global_root_values.data() + static_cast(boundary_index) * 5U, 5, + dense_values.data() + static_cast(frame) * 5U); + dense_mask_values[frame] = has_global_root_values[boundary_index] != 0U ? 1.0F : 0.0F; + } + const std::int32_t duration_index = static_cast(duration_input_tokens - 6U); + const std::int32_t planned_duration_index = static_cast(requested_tokens - 6U); + ggml_backend_tensor_set(global_input, global_root_values.data(), 0, ggml_nbytes(global_input)); + ggml_backend_tensor_set(local_input, local_root_values.data(), 0, ggml_nbytes(local_input)); + ggml_backend_tensor_set(pose_input, poses.data(), 0, ggml_nbytes(pose_input)); + ggml_backend_tensor_set(global_mask, global_mask_f.data(), 0, ggml_nbytes(global_mask)); + ggml_backend_tensor_set(local_mask, local_mask_f.data(), 0, ggml_nbytes(local_mask)); + ggml_backend_tensor_set(pose_mask, pose_mask_f.data(), 0, ggml_nbytes(pose_mask)); + ggml_backend_tensor_set(dense_target, dense_values.data(), 0, ggml_nbytes(dense_target)); + ggml_backend_tensor_set(dense_target_mask, dense_mask_values.data(), 0, ggml_nbytes(dense_target_mask)); + ggml_backend_tensor_set(duration_input, &duration_index, 0, sizeof(duration_index)); + ggml_backend_tensor_set(planned_duration_input, &planned_duration_index, 0, + sizeof(planned_duration_index)); + const auto status = ggml_backend_graph_compute(neural_backend(runtime), graph); + if (status != GGML_STATUS_SUCCESS) { + reason = std::string("root graph failed: ") + ggml_status_to_string(status); + return MB_COMPUTE_FAILED; + } + output.tokens = requested_tokens; + output.duration_logits.resize(12); + output.global_root_values.resize(static_cast(frames) * 5U); + ggml_backend_tensor_get(duration_logits, output.duration_logits.data(), 0, + output.duration_logits.size() * sizeof(float)); + ggml_backend_tensor_get(root_values, output.global_root_values.data(), 0, + output.global_root_values.size() * sizeof(float)); + return MB_OK; +#endif +} + +} // namespace + +mb_status run_root_planner(const neural_runtime & runtime, + std::span global_root_values, + std::span has_global_root_values, + std::span local_root_values, + std::span has_local_root_values, + std::span poses, + std::span has_poses, + std::uint32_t requested_tokens, + root_result & output, + std::string & reason) { + return run_root_planner_impl(runtime, global_root_values, has_global_root_values, + local_root_values, has_local_root_values, poses, has_poses, + requested_tokens, requested_tokens, output, reason); +} + +mb_status run_root_planner_auto_probe(const neural_runtime & runtime, + std::span global_root_values, + std::span has_global_root_values, + std::span local_root_values, + std::span has_local_root_values, + std::span poses, + std::span has_poses, + std::uint32_t candidate_tokens, + root_result & output, + std::string & reason) { + return run_root_planner_impl(runtime, global_root_values, has_global_root_values, + local_root_values, has_local_root_values, poses, has_poses, + candidate_tokens, 18U, output, reason); +} + +} // namespace motionbricks::detail diff --git a/src/root.hpp b/src/root.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e1533cb77654b01f358bf7b97e067c26dd3ec214 --- /dev/null +++ b/src/root.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace motionbricks::detail { + +class neural_runtime; + +struct root_result { + std::uint32_t tokens = 0; + std::vector duration_logits; + std::vector global_root_values; +}; + +mb_status run_root_planner(const neural_runtime & runtime, + std::span global_root_values, + std::span has_global_root_values, + std::span local_root_values, + std::span has_local_root_values, + std::span poses, + std::span has_poses, + std::uint32_t requested_tokens, + root_result & output, + std::string & reason); + +/* Uses the masked-duration embedding (upstream token value 18) while building + a graph for candidate_tokens. This is the first pass of auto-duration. */ +mb_status run_root_planner_auto_probe(const neural_runtime & runtime, + std::span global_root_values, + std::span has_global_root_values, + std::span local_root_values, + std::span has_local_root_values, + std::span poses, + std::span has_poses, + std::uint32_t candidate_tokens, + root_result & output, + std::string & reason); + +} // namespace motionbricks::detail diff --git a/src/style.cpp b/src/style.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a4ebee8701b6a203d42de14ebfa4edc7e34960a8 --- /dev/null +++ b/src/style.cpp @@ -0,0 +1,159 @@ +#include "style.hpp" + +#include "handles.hpp" + +#include +#include +#include +#include +#include +#include + +#if defined(MOTIONBRICKS_HAVE_GGML) +#include +#include +#endif + +namespace motionbricks::detail { +namespace { +#if defined(MOTIONBRICKS_HAVE_GGML) + +constexpr std::string_view revision = "a0732b642c0333077e127a2f56ab0014c196bca4"; +struct gguf_deleter { void operator()(gguf_context * value) const { gguf_free(value); } }; +struct ggml_deleter { void operator()(ggml_context * value) const { ggml_free(value); } }; + +bool string_value(const gguf_context * context, const char * key, + std::string_view expected, std::string & reason) { + const auto index = gguf_find_key(context, key); + if (index < 0 || gguf_get_kv_type(context, index) != GGUF_TYPE_STRING || + std::string_view(gguf_get_val_str(context, index)) != expected) { + reason = std::string("invalid style metadata: ") + key; + return false; + } + return true; +} + +bool shape(const gguf_context * context, const char * name, ggml_type type, + std::initializer_list expected, std::string & reason) { + const auto index = gguf_find_tensor(context, name); + if (index < 0 || gguf_get_tensor_type(context, index) != type) { + reason = std::string("missing or incorrectly typed style tensor: ") + name; + return false; + } + const auto * dimensions = gguf_get_tensor_ne(context, index); + std::size_t axis = 0; + for (const auto value : expected) if (dimensions[axis++] != value) { + reason = std::string("incorrect style tensor shape: ") + name; + return false; + } + for (; axis < 4U; ++axis) if (dimensions[axis] != 1) { + reason = std::string("incorrect style tensor rank: ") + name; + return false; + } + return true; +} + +template +void copy_tensor(ggml_context * context, const char * name, std::size_t count, + std::vector & output) { + const auto * tensor = ggml_get_tensor(context, name); + const auto * values = static_cast(tensor->data); + output.assign(values, values + count); +} + +#endif +} // namespace + +mb_status load_style_file(const std::filesystem::path & path, + mb_style & output, std::string & reason) { +#if !defined(MOTIONBRICKS_HAVE_GGML) + (void)path; (void)output; + reason = "this build has no GGML support"; + return MB_BACKEND_UNAVAILABLE; +#else + if (!std::filesystem::is_regular_file(path)) { + reason = "style file does not exist: " + path.string(); + return MB_IO_ERROR; + } + ggml_context * raw = nullptr; + std::unique_ptr metadata( + gguf_init_from_file(path.string().c_str(), {false, &raw})); + std::unique_ptr tensors(raw); + if (!metadata || !tensors || gguf_get_version(metadata.get()) != 3U) { + reason = "could not parse style GGUF: " + path.string(); + return MB_INVALID_FORMAT; + } + if (!string_value(metadata.get(), "general.architecture", "motionbricks", reason) || + !string_value(metadata.get(), "motionbricks.component", "style", reason) || + !string_value(metadata.get(), "motionbricks.skeleton", "g1skel34", reason) || + !string_value(metadata.get(), "motionbricks.upstream_revision", revision, reason)) + return MB_INCOMPATIBLE_MODEL; + const auto source_key = gguf_find_key(metadata.get(), "motionbricks.source_sha256"); + if (source_key < 0 || gguf_get_kv_type(metadata.get(), source_key) != GGUF_TYPE_STRING) { + reason = "style source identity is missing"; + return MB_INVALID_FORMAT; + } + const std::string_view source_hash = gguf_get_val_str(metadata.get(), source_key); + if (source_hash.size() != 64U || + !std::all_of(source_hash.begin(), source_hash.end(), [](char value) { + return (value >= '0' && value <= '9') || (value >= 'a' && value <= 'f'); + })) { + reason = "style source identity is not a lowercase SHA-256"; + return MB_INVALID_FORMAT; + } + const auto name_key = gguf_find_key(metadata.get(), "motionbricks.style_name"); + const auto speed_key = gguf_find_key(metadata.get(), "motionbricks.style_speed"); + const auto frames_key = gguf_find_key(metadata.get(), "motionbricks.style_frames"); + if (name_key < 0 || gguf_get_kv_type(metadata.get(), name_key) != GGUF_TYPE_STRING || + speed_key < 0 || gguf_get_kv_type(metadata.get(), speed_key) != GGUF_TYPE_FLOAT32 || + frames_key < 0 || gguf_get_kv_type(metadata.get(), frames_key) != GGUF_TYPE_UINT32) { + reason = "style metadata is incomplete"; + return MB_INVALID_FORMAT; + } + mb_style temporary; + temporary.name = gguf_get_val_str(metadata.get(), name_key); + temporary.speed = gguf_get_val_f32(metadata.get(), speed_key); + temporary.frames = gguf_get_val_u32(metadata.get(), frames_key); + if (temporary.name.empty() || temporary.frames < 4U || temporary.frames > 100000U || + !std::isfinite(temporary.speed) || temporary.speed < 0.0F || + gguf_get_n_tensors(metadata.get()) != 5) { + reason = "invalid style metadata values"; + return MB_INVALID_FORMAT; + } + const auto frames = static_cast(temporary.frames); + if (!shape(metadata.get(), "global_joint_positions", GGML_TYPE_F32, {3,34,frames}, reason) || + !shape(metadata.get(), "global_joint_rotations", GGML_TYPE_F32, {9,34,frames}, reason) || + !shape(metadata.get(), "global_root_positions", GGML_TYPE_F32, {3,frames}, reason) || + !shape(metadata.get(), "global_headings", GGML_TYPE_F32, {frames}, reason) || + !shape(metadata.get(), "allowed_tokens", GGML_TYPE_I32, {11}, reason)) + return MB_INVALID_FORMAT; + copy_tensor(tensors.get(), "global_joint_positions", temporary.frames * 34U * 3U, + temporary.global_joint_positions); + copy_tensor(tensors.get(), "global_joint_rotations", temporary.frames * 34U * 9U, + temporary.global_joint_rotations); + copy_tensor(tensors.get(), "global_root_positions", temporary.frames * 3U, + temporary.global_root_positions); + copy_tensor(tensors.get(), "global_headings", temporary.frames, temporary.global_headings); + std::vector allowed; + copy_tensor(tensors.get(), "allowed_tokens", 11U, allowed); + for (std::size_t index = 0; index < allowed.size(); ++index) { + if (allowed[index] != 0 && allowed[index] != 1) { + reason = "style allowed-duration mask is not binary"; + return MB_INVALID_FORMAT; + } + temporary.allowed_tokens[index] = static_cast(allowed[index]); + } + const auto finite = [](const std::vector & values) { + return std::all_of(values.begin(), values.end(), [](float value) { return std::isfinite(value); }); + }; + if (!finite(temporary.global_joint_positions) || !finite(temporary.global_joint_rotations) || + !finite(temporary.global_root_positions) || !finite(temporary.global_headings)) { + reason = "style contains non-finite values"; + return MB_INVALID_FORMAT; + } + output = std::move(temporary); + return MB_OK; +#endif +} + +} // namespace motionbricks::detail diff --git a/src/style.hpp b/src/style.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9c6864ef2dcf1e66cf67b121f5b5fe371928cb92 --- /dev/null +++ b/src/style.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include +#include + +struct mb_style; + +namespace motionbricks::detail { + +mb_status load_style_file(const std::filesystem::path & path, + mb_style & output, std::string & reason); + +} // namespace motionbricks::detail diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..43738e41dd993c63e1a3d958a983028985cb0e40 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,105 @@ +add_executable(motionbricks-c-api-test c_api_test.c) +target_link_libraries(motionbricks-c-api-test PRIVATE motionbricks::motionbricks) +add_test(NAME motionbricks-c-api COMMAND motionbricks-c-api-test) + +add_executable(motionbricks-cpp-wrapper-test cpp_wrapper_test.cpp) +target_link_libraries(motionbricks-cpp-wrapper-test PRIVATE motionbricks::motionbricks) +add_test(NAME motionbricks-cpp-wrapper COMMAND motionbricks-cpp-wrapper-test) + +set(MOTIONBRICKS_REFERENCE_FIXTURES "${PROJECT_SOURCE_DIR}/generated/fixtures-gguf" CACHE PATH + "Optional local GGUF reference fixtures for parity tests") +if(MOTIONBRICKS_ENABLE_GGML AND + EXISTS "${MOTIONBRICKS_REFERENCE_BUNDLE}/pose.gguf" AND + EXISTS "${MOTIONBRICKS_REFERENCE_FIXTURES}/pose.gguf") + add_executable(motionbricks-pose-parity-test pose_parity_test.cpp) + target_include_directories(motionbricks-pose-parity-test PRIVATE + "${PROJECT_SOURCE_DIR}/src" "${MOTIONBRICKS_GGML_SOURCE_DIR}/include") + target_compile_definitions(motionbricks-pose-parity-test PRIVATE + MOTIONBRICKS_HAVE_GGML=1 + MOTIONBRICKS_REFERENCE_BUNDLE="${MOTIONBRICKS_REFERENCE_BUNDLE}" + MOTIONBRICKS_REFERENCE_FIXTURES="${MOTIONBRICKS_REFERENCE_FIXTURES}") + target_link_libraries(motionbricks-pose-parity-test PRIVATE motionbricks_static ggml) + add_test(NAME motionbricks-pose-parity COMMAND motionbricks-pose-parity-test) + + add_executable(motionbricks-decoder-parity-test decoder_parity_test.cpp) + target_include_directories(motionbricks-decoder-parity-test PRIVATE + "${PROJECT_SOURCE_DIR}/src" "${MOTIONBRICKS_GGML_SOURCE_DIR}/include") + target_compile_definitions(motionbricks-decoder-parity-test PRIVATE + MOTIONBRICKS_HAVE_GGML=1 + MOTIONBRICKS_REFERENCE_BUNDLE="${MOTIONBRICKS_REFERENCE_BUNDLE}" + MOTIONBRICKS_REFERENCE_FIXTURES="${MOTIONBRICKS_REFERENCE_FIXTURES}") + target_link_libraries(motionbricks-decoder-parity-test PRIVATE motionbricks_static ggml) + add_test(NAME motionbricks-decoder-parity COMMAND motionbricks-decoder-parity-test) + + add_executable(motionbricks-root-parity-test root_parity_test.cpp) + target_include_directories(motionbricks-root-parity-test PRIVATE + "${PROJECT_SOURCE_DIR}/src" "${MOTIONBRICKS_GGML_SOURCE_DIR}/include") + target_compile_definitions(motionbricks-root-parity-test PRIVATE + MOTIONBRICKS_HAVE_GGML=1 + MOTIONBRICKS_REFERENCE_BUNDLE="${MOTIONBRICKS_REFERENCE_BUNDLE}" + MOTIONBRICKS_REFERENCE_FIXTURES="${MOTIONBRICKS_REFERENCE_FIXTURES}") + target_link_libraries(motionbricks-root-parity-test PRIVATE motionbricks_static ggml) + add_test(NAME motionbricks-root-parity COMMAND motionbricks-root-parity-test) + + if(EXISTS "${MOTIONBRICKS_REFERENCE_STYLES}/walk.mbstyle") + add_executable(motionbricks-agent-integration-test agent_integration_test.cpp) + target_compile_definitions(motionbricks-agent-integration-test PRIVATE + MOTIONBRICKS_REFERENCE_BUNDLE="${MOTIONBRICKS_REFERENCE_BUNDLE}" + MOTIONBRICKS_REFERENCE_STYLES="${MOTIONBRICKS_REFERENCE_STYLES}") + target_link_libraries(motionbricks-agent-integration-test PRIVATE motionbricks_static) + add_test(NAME motionbricks-agent-integration COMMAND motionbricks-agent-integration-test) + + add_executable(motionbricks-agent-backend-parity-test agent_backend_parity_test.cpp) + target_compile_definitions(motionbricks-agent-backend-parity-test PRIVATE + MOTIONBRICKS_REFERENCE_BUNDLE="${MOTIONBRICKS_REFERENCE_BUNDLE}" + MOTIONBRICKS_REFERENCE_STYLES="${MOTIONBRICKS_REFERENCE_STYLES}") + target_link_libraries(motionbricks-agent-backend-parity-test PRIVATE motionbricks_static) + endif() +endif() + +find_program(MOTIONBRICKS_GO_EXECUTABLE go) +if(MOTIONBRICKS_GO_EXECUTABLE AND NOT MOTIONBRICKS_SANITIZE) + add_test(NAME motionbricks-purego-abi + COMMAND "${CMAKE_COMMAND}" -E env + "CGO_ENABLED=0" + "MOTIONBRICKS_LIB=$" + "${MOTIONBRICKS_GO_EXECUTABLE}" test ./...) + set_tests_properties(motionbricks-purego-abi PROPERTIES + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../bindings/go") + + find_program(MOTIONBRICKS_CHROMIUM_EXECUTABLE NAMES chromium chromium-browser google-chrome) + if(MOTIONBRICKS_CHROMIUM_EXECUTABLE AND + MOTIONBRICKS_ENABLE_GGML AND + EXISTS "${MOTIONBRICKS_REFERENCE_BUNDLE}/pose.gguf" AND + EXISTS "${MOTIONBRICKS_REFERENCE_STYLES}/walk.mbstyle") + add_test(NAME motionbricks-go-demo + COMMAND "${CMAKE_COMMAND}" -E env + "CGO_ENABLED=0" + "MOTIONBRICKS_LIB=$" + "MOTIONBRICKS_MODEL=${MOTIONBRICKS_REFERENCE_BUNDLE}" + "MOTIONBRICKS_STYLES=${MOTIONBRICKS_REFERENCE_STYLES}" + "MOTIONBRICKS_CHROME=${MOTIONBRICKS_CHROMIUM_EXECUTABLE}" + "${MOTIONBRICKS_GO_EXECUTABLE}" test -v ./...) + set_tests_properties(motionbricks-go-demo PROPERTIES + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../demo" + RUN_SERIAL TRUE + TIMEOUT 60) + endif() +endif() + +if(MOTIONBRICKS_SANITIZE) + foreach(target + motionbricks-c-api-test + motionbricks-cpp-wrapper-test + motionbricks-pose-parity-test + motionbricks-decoder-parity-test + motionbricks-root-parity-test + motionbricks-agent-integration-test + motionbricks-agent-backend-parity-test + motionbricks-cli) + if(TARGET ${target}) + target_compile_options(${target} PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer) + target_link_options(${target} PRIVATE -fsanitize=address,undefined) + endif() + endforeach() +endif() diff --git a/tests/agent_backend_parity_test.cpp b/tests/agent_backend_parity_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cba06258c834c898001f2c754ce7d0aea0a93424 --- /dev/null +++ b/tests/agent_backend_parity_test.cpp @@ -0,0 +1,72 @@ +#include + +#include +#include +#include +#include +#include +#include + +#ifndef MOTIONBRICKS_REFERENCE_BUNDLE +#error MOTIONBRICKS_REFERENCE_BUNDLE is required +#endif +#ifndef MOTIONBRICKS_REFERENCE_STYLES +#error MOTIONBRICKS_REFERENCE_STYLES is required +#endif + +namespace { +struct snapshot { std::uint64_t frames{}; std::vector roots, rotations; }; + +bool plan(mb_device device, snapshot & output) { + char error[1024]{}; + mb_runtime_options * options = nullptr; mb_model * model = nullptr; + mb_style * style = nullptr; mb_agent * agent = nullptr; + mb_command * command = nullptr; mb_motion * motion = nullptr; + auto ok = [&](mb_status status) { + if (status == MB_OK) return true; + std::cerr << mb_status_string(status) << ": " << error << '\n'; return false; + }; + bool valid = ok(mb_runtime_options_create(&options, error, sizeof error)) && + ok(mb_runtime_options_set_device(options, device, error, sizeof error)) && + ok(mb_model_load(MOTIONBRICKS_REFERENCE_BUNDLE, options, &model, error, sizeof error)) && + ok(mb_style_load(model, MOTIONBRICKS_REFERENCE_STYLES "/walk.mbstyle", &style, error, sizeof error)) && + ok(mb_agent_create(model, &agent, error, sizeof error)) && + ok(mb_agent_reset(agent, style, error, sizeof error)) && + ok(mb_command_create(&command, error, sizeof error)) && + ok(mb_command_set_style(command, style, error, sizeof error)) && + ok(mb_command_set_movement_direction(command, 0.0F, 0.0F, 1.0F, error, sizeof error)) && + ok(mb_command_set_facing_direction(command, 0.0F, 0.0F, 1.0F, error, sizeof error)) && + ok(mb_command_set_seed(command, 23U, error, sizeof error)) && + ok(mb_agent_plan(agent, command, &motion, error, sizeof error)); + if (valid) { + const float * roots = nullptr, * rotations = nullptr; + std::uint64_t root_count = 0, rotation_count = 0; + valid = ok(mb_motion_get_frame_count(motion, &output.frames, error, sizeof error)) && + ok(mb_motion_get_root_translations(motion, &roots, &root_count, error, sizeof error)) && + ok(mb_motion_get_local_rotations_xyzw(motion, &rotations, &rotation_count, error, sizeof error)); + if (valid) { output.roots.assign(roots, roots + root_count); output.rotations.assign(rotations, rotations + rotation_count); } + } + mb_motion_free(motion); mb_command_free(command); mb_agent_free(agent); + mb_style_free(style); mb_model_free(model); mb_runtime_options_free(options); + return valid; +} + +float maximum_error(const std::vector & left, const std::vector & right) { + if (left.size() != right.size()) return INFINITY; + float result = 0.0F; + for (std::size_t index = 0; index < left.size(); ++index) + result = std::max(result, std::abs(left[index] - right[index])); + return result; +} +} + +int main() { + snapshot cpu, vulkan; + if (!plan(MB_DEVICE_CPU, cpu) || !plan(MB_DEVICE_VULKAN, vulkan)) return 1; + const float root_error = maximum_error(cpu.roots, vulkan.roots); + const float rotation_error = maximum_error(cpu.rotations, vulkan.rotations); + std::cout << "agent backend parity frames=" << cpu.frames + << " root_max_abs=" << root_error + << " rotation_max_abs=" << rotation_error << '\n'; + return cpu.frames == vulkan.frames && root_error <= 2.0e-3F && rotation_error <= 2.0e-3F ? 0 : 1; +} diff --git a/tests/agent_integration_test.cpp b/tests/agent_integration_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0475ec0c86538d5ff2868070d122b99f33b0a939 --- /dev/null +++ b/tests/agent_integration_test.cpp @@ -0,0 +1,107 @@ +#include + +#include +#include +#include +#include +#include +#include + +#ifndef MOTIONBRICKS_REFERENCE_BUNDLE +#error MOTIONBRICKS_REFERENCE_BUNDLE is required +#endif +#ifndef MOTIONBRICKS_REFERENCE_STYLES +#error MOTIONBRICKS_REFERENCE_STYLES is required +#endif + +namespace { +bool check(mb_status status, const char * operation, const char * error) { + if (status == MB_OK) return true; + std::cerr << operation << ": " << mb_status_string(status) << ": " << error << '\n'; + return false; +} +} + +int main(int argc, char ** argv) { + const bool vulkan = argc == 2 && std::string_view(argv[1]) == "vulkan"; + char error[1024]{}; + mb_runtime_options * options = nullptr; + mb_model * model = nullptr; + mb_style * style = nullptr; + mb_agent * agent = nullptr; + mb_command * command = nullptr; + mb_motion * motion = nullptr; + if (!check(mb_runtime_options_create(&options, error, sizeof error), "options", error) || + !check(mb_runtime_options_set_device(options, vulkan ? MB_DEVICE_VULKAN : MB_DEVICE_CPU, + error, sizeof error), "device", error) || + !check(mb_model_load(MOTIONBRICKS_REFERENCE_BUNDLE, options, &model, error, sizeof error), + "model", error) || + !check(mb_style_load(model, MOTIONBRICKS_REFERENCE_STYLES "/walk.mbstyle", &style, + error, sizeof error), "style", error) || + !check(mb_agent_create(model, &agent, error, sizeof error), "agent", error) || + !check(mb_agent_reset(agent, style, error, sizeof error), "reset", error) || + !check(mb_command_create(&command, error, sizeof error), "command", error) || + !check(mb_command_set_style(command, style, error, sizeof error), "set style", error) || + !check(mb_command_set_movement_direction(command, 0.0F, 0.0F, 1.0F, + error, sizeof error), "movement", error) || + !check(mb_command_set_facing_direction(command, 0.0F, 0.0F, 1.0F, + error, sizeof error), "facing", error) || + !check(mb_command_set_seed(command, 23U, error, sizeof error), "seed", error) || + !check(mb_agent_plan(agent, command, &motion, error, sizeof error), "plan", error)) + return 1; + std::uint64_t frames = 0, joints = 0, root_values = 0, rotation_values = 0; + std::uint64_t target_frames = 0, target_root_values = 0, target_rotation_values = 0; + const float * roots = nullptr; + const float * rotations = nullptr; + const float * target_roots = nullptr; + const float * target_rotations = nullptr; + if (!check(mb_motion_get_frame_count(motion, &frames, error, sizeof error), "frames", error) || + !check(mb_motion_get_joint_count(motion, &joints, error, sizeof error), "joints", error) || + !check(mb_motion_get_root_translations(motion, &roots, &root_values, error, sizeof error), + "roots", error) || + !check(mb_motion_get_local_rotations_xyzw(motion, &rotations, &rotation_values, + error, sizeof error), "rotations", error) || + !check(mb_motion_get_target_frame_count(motion, &target_frames, error, sizeof error), + "target frames", error) || + !check(mb_motion_get_target_root_translations(motion, &target_roots, &target_root_values, + error, sizeof error), "target roots", error) || + !check(mb_motion_get_target_local_rotations_xyzw(motion, &target_rotations, + &target_rotation_values, + error, sizeof error), "target rotations", error)) + return 1; + bool valid = frames >= 24U && frames <= 64U && frames % 4U == 0U && joints == 34U && + root_values == frames * 3U && rotation_values == frames * joints * 4U && + target_frames == 4U && target_root_values == target_frames * 3U && + target_rotation_values == target_frames * joints * 4U; + valid = valid && std::all_of(roots, roots + root_values, + [](float value) { return std::isfinite(value); }); + valid = valid && std::all_of(rotations, rotations + rotation_values, + [](float value) { return std::isfinite(value); }); + valid = valid && std::all_of(target_roots, target_roots + target_root_values, + [](float value) { return std::isfinite(value); }); + valid = valid && std::all_of(target_rotations, target_rotations + target_rotation_values, + [](float value) { return std::isfinite(value); }); + float maximum_norm_error = 0.0F; + for (std::uint64_t index = 0; index < frames * joints; ++index) { + const float * q = rotations + index * 4U; + maximum_norm_error = std::max(maximum_norm_error, + std::abs(std::sqrt(q[0]*q[0]+q[1]*q[1]+q[2]*q[2]+q[3]*q[3]) - 1.0F)); + } + valid = valid && maximum_norm_error < 2.0e-4F; + for (std::uint64_t index = 0; index < target_frames * joints; ++index) { + const float * q = target_rotations + index * 4U; + maximum_norm_error = std::max(maximum_norm_error, + std::abs(std::sqrt(q[0]*q[0]+q[1]*q[1]+q[2]*q[2]+q[3]*q[3]) - 1.0F)); + } + valid = valid && maximum_norm_error < 2.0e-4F; + std::cout << "agent " << (vulkan ? "vulkan" : "cpu") << " frames=" << frames + << " quaternion_norm_error=" << maximum_norm_error << '\n'; + if (valid) valid = check(mb_agent_advance(agent, 4U, error, sizeof error), "advance", error); + mb_motion_free(motion); + mb_command_free(command); + mb_agent_free(agent); + mb_style_free(style); + mb_model_free(model); + mb_runtime_options_free(options); + return valid ? 0 : 1; +} diff --git a/tests/c_api_test.c b/tests/c_api_test.c new file mode 100644 index 0000000000000000000000000000000000000000..16b20cd6b77f9adcb5159412ef2577a82fef67d7 --- /dev/null +++ b/tests/c_api_test.c @@ -0,0 +1,72 @@ +#include + +#include +#include +#include +#include + +int main(void) { + char error[128] = {0}; + mb_runtime_options * options = 0; + mb_command * command = 0; + + assert(mb_abi_version() == MB_ABI_VERSION); + assert(strcmp(mb_status_string(MB_OK), "ok") == 0); + assert(mb_runtime_options_create(&options, error, sizeof(error)) == MB_OK); + assert(options != 0 && error[0] == '\0'); + + mb_device device = UINT32_MAX; + uint32_t threads = UINT32_MAX; + const char * directory = 0; + assert(mb_runtime_options_get_device(options, &device, error, sizeof(error)) == MB_OK); + assert(device == MB_DEVICE_AUTO); + assert(mb_runtime_options_get_threads(options, &threads, error, sizeof(error)) == MB_OK); + assert(threads == 0); + assert(mb_runtime_options_set_device(options, MB_DEVICE_CPU, error, sizeof(error)) == MB_OK); + assert(mb_runtime_options_set_threads(options, 7, error, sizeof(error)) == MB_OK); + assert(mb_runtime_options_set_backend_directory(options, "/tmp/backends", error, sizeof(error)) == MB_OK); + assert(mb_runtime_options_get_backend_directory(options, &directory, error, sizeof(error)) == MB_OK); + assert(strcmp(directory, "/tmp/backends") == 0); + assert(mb_runtime_options_set_device(options, UINT32_C(999), error, sizeof(error)) == MB_INVALID_ARGUMENT); + assert(error[0] != '\0'); + + assert(mb_command_create(&command, error, sizeof(error)) == MB_OK); + assert(command != 0); + float x = 0.0F, y = 0.0F, z = 0.0F, speed = 0.0F, heading = 0.0F; + uint32_t enabled = UINT32_MAX; + uint64_t seed = UINT64_MAX; + assert(mb_command_get_facing_direction(command, &x, &y, &z, error, sizeof(error)) == MB_OK); + assert(x == 0.0F && y == 0.0F && z == 1.0F); + assert(mb_command_get_target_speed(command, &speed, error, sizeof(error)) == MB_OK); + assert(speed == -1.0F); + assert(mb_command_set_movement_direction(command, 1.0F, 0.0F, -0.5F, error, sizeof(error)) == MB_OK); + assert(mb_command_get_movement_direction(command, &x, &y, &z, error, sizeof(error)) == MB_OK); + assert(x == 1.0F && y == 0.0F && z == -0.5F); + assert(mb_command_set_facing_direction(command, 0.0F, 0.0F, 0.0F, error, sizeof(error)) == MB_INVALID_ARGUMENT); + assert(mb_command_set_movement_direction(command, NAN, 0.0F, 0.0F, error, sizeof(error)) == MB_INVALID_ARGUMENT); + assert(mb_command_set_target_speed(command, -1.01F, error, sizeof(error)) == MB_INVALID_ARGUMENT); + assert(mb_command_set_world_target(command, 2.0F, 1.0F, 3.0F, 0.25F, 1, error, sizeof(error)) == MB_OK); + assert(mb_command_get_world_target(command, &x, &y, &z, &heading, &enabled, error, sizeof(error)) == MB_OK); + assert(x == 2.0F && y == 1.0F && z == 3.0F && heading == 0.25F && enabled == 1); + assert(mb_command_set_seed(command, UINT64_C(42), error, sizeof(error)) == MB_OK); + assert(mb_command_get_seed(command, &seed, error, sizeof(error)) == MB_OK && seed == UINT64_C(42)); + + mb_model * model = (mb_model *)(uintptr_t)1; + assert(mb_model_load("fixture", options, &model, error, sizeof(error)) == MB_IO_ERROR); + assert(model == 0 && error[0] != '\0'); + assert(mb_model_get_parameter_count(0, &seed, error, sizeof(error)) == MB_INVALID_ARGUMENT); + assert(mb_model_get_neutral_joint_position(0, 0, &x, &y, &z, error, sizeof(error)) == MB_INVALID_ARGUMENT); + assert(mb_motion_get_target_frame_count(0, &seed, error, sizeof(error)) == MB_INVALID_ARGUMENT); + + { + char tiny[2] = {'x', 'x'}; + assert(mb_runtime_options_set_device(options, UINT32_C(999), tiny, sizeof(tiny)) == MB_INVALID_ARGUMENT); + assert(tiny[1] == '\0'); + } + + mb_command_free(command); + mb_runtime_options_free(options); + mb_command_free(0); + mb_runtime_options_free(0); + return 0; +} diff --git a/tests/cpp_wrapper_test.cpp b/tests/cpp_wrapper_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4fb43f272c44b481136e1677eee54aeb30e0dd80 --- /dev/null +++ b/tests/cpp_wrapper_test.cpp @@ -0,0 +1,13 @@ +#include + +#include +#include + +int main() { + motionbricks::runtime_options options; + assert(options.get() != nullptr); + motionbricks::runtime_options moved(std::move(options)); + assert(options.get() == nullptr); + assert(moved.get() != nullptr); + return 0; +} diff --git a/tests/decoder_parity_test.cpp b/tests/decoder_parity_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e22729389a0757165df1895bfb8fd9dde6f9e7a0 --- /dev/null +++ b/tests/decoder_parity_test.cpp @@ -0,0 +1,82 @@ +#include "decoder.hpp" +#include "handles.hpp" +#include "model.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MOTIONBRICKS_REFERENCE_BUNDLE +#error MOTIONBRICKS_REFERENCE_BUNDLE is required +#endif +#ifndef MOTIONBRICKS_REFERENCE_FIXTURES +#error MOTIONBRICKS_REFERENCE_FIXTURES is required +#endif + +namespace { +struct gguf_deleter { void operator()(gguf_context * value) const { gguf_free(value); } }; +struct ggml_deleter { void operator()(ggml_context * value) const { ggml_free(value); } }; +template std::vector tensor_values(ggml_context * context, const char * name) { + auto * tensor = ggml_get_tensor(context, name); + assert(tensor != nullptr); + std::vector values(static_cast(ggml_nelements(tensor))); + std::memcpy(values.data(), tensor->data, values.size() * sizeof(T)); + return values; +} +} + +int main(int argc, char ** argv) { + const bool vulkan = argc == 2 && std::string_view(argv[1]) == "vulkan"; + mb_model model; + mb_runtime_options options; + options.device = vulkan ? MB_DEVICE_VULKAN : MB_DEVICE_CPU; + std::string reason; + auto status = motionbricks::detail::load_model_bundle( + MOTIONBRICKS_REFERENCE_BUNDLE, &options, model, reason); + if (status != MB_OK) { std::cerr << reason << '\n'; return 1; } + ggml_context * raw = nullptr; + const std::string fixture_path = std::string(MOTIONBRICKS_REFERENCE_FIXTURES) + "/vq-decoder.gguf"; + std::unique_ptr fixture( + gguf_init_from_file(fixture_path.c_str(), {false, &raw})); + std::unique_ptr tensors(raw); + assert(fixture && tensors); + const auto quantized = tensor_values(tensors.get(), "input.quantized"); + const auto external = tensor_values(tensors.get(), "input.external_cond"); + const auto target = tensor_values(tensors.get(), "input.target_cond"); + const auto mask_i32 = tensor_values(tensors.get(), "input.has_target_cond"); + const auto expected = tensor_values(tensors.get(), "output.motion"); + std::vector mask(mask_i32.begin(), mask_i32.end()); + std::vector actual; + std::vector traces; + status = motionbricks::detail::run_vq_decoder( + *model.runtime, quantized, external, target, mask, 6, actual, reason, &traces); + if (status != MB_OK) { std::cerr << reason << '\n'; return 1; } + assert(actual.size() == expected.size()); + float max_absolute = 0.0F, max_relative = 0.0F; + for (std::size_t index = 0; index < actual.size(); ++index) { + max_absolute = std::max(max_absolute, std::abs(actual[index] - expected[index])); + max_relative = std::max(max_relative, + std::abs(actual[index] - expected[index]) / std::max(1.0e-6F, std::abs(expected[index]))); + } + std::cout << "decoder " << (vulkan ? "vulkan" : "cpu") + << " max_abs=" << max_absolute << " max_rel=" << max_relative << '\n'; + for (const auto & trace : traces) { + const auto reference = tensor_values( + tensors.get(), ("trace." + trace.name).c_str()); + float difference = 0.0F; + for (std::size_t index = 0; index < reference.size(); ++index) + difference = std::max(difference, std::abs(reference[index] - trace.values[index])); + std::cout << trace.name << " max_abs=" << difference << '\n'; + } + return max_absolute <= (vulkan ? 1.0e-4F : 3.0e-4F) ? 0 : 1; +} diff --git a/tests/pose_parity_test.cpp b/tests/pose_parity_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..695483f6f6d210484cca1047d3a602ea7b4b61a4 --- /dev/null +++ b/tests/pose_parity_test.cpp @@ -0,0 +1,97 @@ +#include "handles.hpp" +#include "model.hpp" +#include "pose.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MOTIONBRICKS_REFERENCE_BUNDLE +#error MOTIONBRICKS_REFERENCE_BUNDLE is required +#endif +#ifndef MOTIONBRICKS_REFERENCE_FIXTURES +#error MOTIONBRICKS_REFERENCE_FIXTURES is required +#endif + +namespace { + +struct gguf_deleter { void operator()(gguf_context * value) const { gguf_free(value); } }; +struct ggml_deleter { void operator()(ggml_context * value) const { ggml_free(value); } }; + +template +std::vector tensor_values(ggml_context * context, const char * name) { + auto * tensor = ggml_get_tensor(context, name); + assert(tensor != nullptr); + std::vector values(static_cast(ggml_nelements(tensor))); + std::memcpy(values.data(), tensor->data, values.size() * sizeof(T)); + return values; +} + +} // namespace + +int main(int argc, char ** argv) { + mb_model model; + std::string reason; + mb_runtime_options options; + const bool vulkan = argc == 2 && std::string_view(argv[1]) == "vulkan"; + options.device = vulkan ? MB_DEVICE_VULKAN : MB_DEVICE_CPU; + auto status = motionbricks::detail::load_model_bundle( + MOTIONBRICKS_REFERENCE_BUNDLE, &options, model, reason); + if (status != MB_OK) { + std::cerr << reason << '\n'; + return 1; + } + + ggml_context * raw = nullptr; + const std::string fixture_path = std::string(MOTIONBRICKS_REFERENCE_FIXTURES) + "/pose.gguf"; + std::unique_ptr fixture( + gguf_init_from_file(fixture_path.c_str(), {false, &raw})); + std::unique_ptr fixture_tensors(raw); + assert(fixture && fixture_tensors); + const auto tokens = tensor_values(fixture_tensors.get(), "input.pose_tokens"); + const auto root = tensor_values(fixture_tensors.get(), "input.local_root_values"); + const auto condition = tensor_values(fixture_tensors.get(), "input.pose_cond"); + const auto mask_i32 = tensor_values(fixture_tensors.get(), "input.has_pose_cond"); + const auto duration = tensor_values(fixture_tensors.get(), "input.num_tokens"); + const auto expected = tensor_values(fixture_tensors.get(), "output.pose_logits"); + std::vector mask(mask_i32.begin(), mask_i32.end()); + std::vector actual; + status = motionbricks::detail::run_pose_planner( + *model.runtime, tokens, root, condition, mask, 6, static_cast(duration[0]), + actual, reason); + if (status != MB_OK) { + std::cerr << reason << '\n'; + return 1; + } + assert(actual.size() == expected.size()); + float max_absolute = 0.0F; + float max_relative = 0.0F; + std::size_t token_mismatches = 0; + for (std::size_t index = 0; index < actual.size(); ++index) { + max_absolute = std::max(max_absolute, std::abs(actual[index] - expected[index])); + max_relative = std::max(max_relative, + std::abs(actual[index] - expected[index]) / std::max(1.0e-6F, std::abs(expected[index]))); + } + for (std::size_t head = 0; head < actual.size() / 10U; ++head) { + const auto actual_begin = actual.begin() + static_cast(head * 10U); + const auto expected_begin = expected.begin() + static_cast(head * 10U); + if (std::max_element(actual_begin, actual_begin + 10) - actual_begin != + std::max_element(expected_begin, expected_begin + 10) - expected_begin) + ++token_mismatches; + } + std::cout << "pose " << (vulkan ? "vulkan" : "cpu") + << " max_abs=" << max_absolute << " max_rel=" << max_relative + << " token_mismatches=" << token_mismatches << '\n'; + return max_absolute <= (vulkan ? 5.0e-2F : 3.0e-4F) && token_mismatches == 0U ? 0 : 1; +} diff --git a/tests/root_parity_test.cpp b/tests/root_parity_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ba02639e01a06a981da9ddffcd236aa49f19ee06 --- /dev/null +++ b/tests/root_parity_test.cpp @@ -0,0 +1,86 @@ +#include "handles.hpp" +#include "model.hpp" +#include "root.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MOTIONBRICKS_REFERENCE_BUNDLE +#error MOTIONBRICKS_REFERENCE_BUNDLE is required +#endif +#ifndef MOTIONBRICKS_REFERENCE_FIXTURES +#error MOTIONBRICKS_REFERENCE_FIXTURES is required +#endif + +namespace { +struct gguf_deleter { void operator()(gguf_context * value) const { gguf_free(value); } }; +struct ggml_deleter { void operator()(ggml_context * value) const { ggml_free(value); } }; +template std::vector values(ggml_context * context, const char * name) { + auto * tensor = ggml_get_tensor(context, name); assert(tensor != nullptr); + std::vector output(static_cast(ggml_nelements(tensor))); + std::memcpy(output.data(), tensor->data, output.size() * sizeof(T)); + return output; +} +float maximum_error(const std::vector & actual, const std::vector & expected) { + assert(actual.size() <= expected.size()); + float result = 0.0F; + for (std::size_t index = 0; index < actual.size(); ++index) + result = std::max(result, std::abs(actual[index] - expected[index])); + return result; +} +} + +int main(int argc, char ** argv) { + const bool vulkan = argc == 2 && std::string_view(argv[1]) == "vulkan"; + mb_model model; + mb_runtime_options options; + options.device = vulkan ? MB_DEVICE_VULKAN : MB_DEVICE_CPU; + std::string reason; + auto status = motionbricks::detail::load_model_bundle( + MOTIONBRICKS_REFERENCE_BUNDLE, &options, model, reason); + if (status != MB_OK) { std::cerr << reason << '\n'; return 1; } + ggml_context * raw = nullptr; + const std::string path = std::string(MOTIONBRICKS_REFERENCE_FIXTURES) + "/root.gguf"; + std::unique_ptr fixture(gguf_init_from_file(path.c_str(), {false, &raw})); + std::unique_ptr tensors(raw); + assert(fixture && tensors); + const auto global = values(tensors.get(), "input.global_root_values"); + const auto global_mask_i32 = values(tensors.get(), "input.has_global_root_values"); + const auto local = values(tensors.get(), "input.local_root_values"); + const auto local_mask_i32 = values(tensors.get(), "input.has_local_root_values"); + const auto poses = values(tensors.get(), "input.poses"); + const auto pose_mask_i32 = values(tensors.get(), "input.has_poses"); + const auto duration = values(tensors.get(), "input.num_tokens"); + const auto expected_logits = values(tensors.get(), "output.num_token_logits"); + const auto expected_root = values(tensors.get(), "output.pred_global_root_values"); + std::vector global_mask(global_mask_i32.begin(), global_mask_i32.end()); + std::vector local_mask(local_mask_i32.begin(), local_mask_i32.end()); + std::vector pose_mask(pose_mask_i32.begin(), pose_mask_i32.end()); + motionbricks::detail::root_result actual; + status = motionbricks::detail::run_root_planner( + *model.runtime, global, global_mask, local, local_mask, poses, pose_mask, + static_cast(duration[0]), actual, reason); + if (status != MB_OK) { std::cerr << reason << '\n'; return 1; } + const float logits_error = maximum_error(actual.duration_logits, expected_logits); + const float root_error = maximum_error(actual.global_root_values, expected_root); + const auto actual_duration = std::max_element(actual.duration_logits.begin(), actual.duration_logits.end()) - + actual.duration_logits.begin(); + const auto expected_duration = std::max_element(expected_logits.begin(), expected_logits.end()) - + expected_logits.begin(); + std::cout << "root " << (vulkan ? "vulkan" : "cpu") + << " logits_max_abs=" << logits_error << " motion_max_abs=" << root_error + << " duration_match=" << (actual_duration == expected_duration) << '\n'; + return std::max(logits_error, root_error) <= (vulkan ? 6.0e-3F : 4.0e-4F) && + actual_duration == expected_duration ? 0 : 1; +}