Repository: zeux/meshoptimizer Branch: master Commit: f6ab3ec097a8 Files: 108 Total size: 2.1 MB Directory structure: gitextract_gsw93301/ ├── .clang-format ├── .editorconfig ├── .git-blame-ignore-revs ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ └── feature_request.md │ ├── codecov.yml │ └── workflows/ │ ├── build.yml │ ├── cifuzz.yml │ └── release.yml ├── .gitignore ├── .prettierrc ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── demo/ │ ├── ansi.c │ ├── clusterlod.h │ ├── index.html │ ├── main.cpp │ ├── meshletdec.slang │ ├── nanite.cpp │ ├── pirate.glb │ ├── pirate.obj │ ├── simplify.html │ └── tests.cpp ├── extern/ │ ├── .clang-format │ ├── cgltf.h │ ├── fast_obj.h │ └── sdefl.h ├── gltf/ │ ├── README.md │ ├── animation.cpp │ ├── cli.js │ ├── encodebasis.cpp │ ├── encodewebp.cpp │ ├── fileio.cpp │ ├── fuzz.dict │ ├── fuzz.glb │ ├── gltfpack.cpp │ ├── gltfpack.h │ ├── gltfpack.manifest │ ├── image.cpp │ ├── json.cpp │ ├── library.js │ ├── material.cpp │ ├── mesh.cpp │ ├── node.cpp │ ├── package.json │ ├── parsegltf.cpp │ ├── parselib.cpp │ ├── parseobj.cpp │ ├── stream.cpp │ ├── wasistubs.cpp │ ├── wasistubs.txt │ └── write.cpp ├── js/ │ ├── README.md │ ├── benchmark.js │ ├── index.d.ts │ ├── index.js │ ├── meshopt_clusterizer.d.ts │ ├── meshopt_clusterizer.js │ ├── meshopt_clusterizer.test.js │ ├── meshopt_decoder.cjs │ ├── meshopt_decoder.d.ts │ ├── meshopt_decoder.mjs │ ├── meshopt_decoder.test.js │ ├── meshopt_decoder_reference.js │ ├── meshopt_encoder.d.ts │ ├── meshopt_encoder.js │ ├── meshopt_encoder.test.js │ ├── meshopt_simplifier.d.ts │ ├── meshopt_simplifier.js │ ├── meshopt_simplifier.test.js │ ├── package.json │ └── wasi_trace.js ├── src/ │ ├── allocator.cpp │ ├── clusterizer.cpp │ ├── indexanalyzer.cpp │ ├── indexcodec.cpp │ ├── indexgenerator.cpp │ ├── meshletcodec.cpp │ ├── meshletutils.cpp │ ├── meshoptimizer.h │ ├── opacitymap.cpp │ ├── overdrawoptimizer.cpp │ ├── partition.cpp │ ├── quantization.cpp │ ├── rasterizer.cpp │ ├── simplifier.cpp │ ├── spatialorder.cpp │ ├── stripifier.cpp │ ├── vcacheoptimizer.cpp │ ├── vertexcodec.cpp │ ├── vertexfilter.cpp │ └── vfetchoptimizer.cpp └── tools/ ├── bitmask.py ├── clusterfuzz.cpp ├── codecbench.cpp ├── codecfuzz.cpp ├── codectest.cpp ├── gltfbasis.py ├── objloader.cpp ├── simplifyfuzz.cpp ├── vcachetester.cpp ├── vcachetuner.cpp ├── wasmpack.py └── wasmstubs.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ Standard: Cpp03 UseTab: ForIndentation TabWidth: 4 IndentWidth: 4 AccessModifierOffset: -4 BreakBeforeBraces: Allman IndentCaseLabels: false ColumnLimit: 0 PointerAlignment: Left BreakConstructorInitializersBeforeComma: true NamespaceIndentation: None AlignEscapedNewlines: DontAlign AlignAfterOpenBracket: DontAlign IndentExternBlock: NoIndent Macros: [MESHOPTIMIZER_ALLOC_CALLCONV=&_&] ================================================ FILE: .editorconfig ================================================ # See https://editorconfig.org/ for more info [*] charset = utf-8 indent_style = tab indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true ================================================ FILE: .git-blame-ignore-revs ================================================ # This file contains a list of Git commit hashes that should be hidden from the # regular Git history. Typically, this includes commits involving mass auto-formatting # or other normalizations. Commit hashes *must* use the full 40-character notation. # To apply the ignore list in your local Git client, you must run: # # git config blame.ignoreRevsFile .git-blame-ignore-revs # # This file is automatically used by GitHub.com's blame view. # Convert CRLF to LF everywhere bb4aa0e1372751b74425e77c9a42f972971568bf # js: Reformat all sources with Prettier 3dea31b5c248594a62f49a3e41fc88d7ceae2de3 # Reformat workflow YAML files with Prettier 52e8e1f61712928a36b5257a894bb098a4a98b22 ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report if you've found a bug in this project; please use GitHub Discussions instead if you are uncertain or the bug may be in your code. title: '' labels: bug assignees: '' --- ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Questions and ideas url: https://github.com/zeux/meshoptimizer/discussions about: Please use GitHub Discussions if you have questions or ideas to discuss. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest a feature for this project; please use GitHub Discussions if your idea is not sufficiently concrete. title: '' labels: enhancement assignees: '' --- ================================================ FILE: .github/codecov.yml ================================================ comment: false coverage: status: project: off patch: off ignore: - demo - extern - tools ================================================ FILE: .github/workflows/build.yml ================================================ name: build on: push: branches: - 'master' paths-ignore: - '*.md' pull_request: paths-ignore: - '*.md' jobs: unix: strategy: matrix: os: [ { name: ubuntu, version: ubuntu-latest }, { name: ubuntu-arm, version: ubuntu-24.04-arm }, { name: ubuntu-clang, version: ubuntu-latest }, { name: macos, version: macos-latest }, ] name: ${{matrix.os.name}} runs-on: ${{matrix.os.version}} env: werror: 1 steps: - uses: actions/checkout@v4 - name: work around ASLR+ASAN compatibility run: sudo sysctl -w vm.mmap_rnd_bits=28 if: matrix.os.name == 'ubuntu' - name: enable clang run: echo CXX=clang++ >Makefile.config if: matrix.os.name == 'ubuntu-clang' - name: make test run: | make -j2 config=sanitize test make -j2 config=debug test make -j2 config=release test make -j2 config=coverage test make -j2 config=coverage-scalar test - name: make gltfpack run: make -j2 config=release gltfpack - name: upload coverage run: | find . -type f -name '*.gcno' -exec gcov -p {} + sed -i -e "s/#####\(.*\)\(\/\/ unreachable.*\)/ -\1\2/" *.gcov bash <(curl -s https://codecov.io/bash) -f './src*.gcov' -X search -t ${{secrets.CODECOV_TOKEN}} -B ${{github.ref}} if: matrix.os.name != 'ubuntu-clang' windows: strategy: matrix: arch: [ { name: windows, runner: windows-latest, arch: x64 }, { name: windows-x86, runner: windows-latest, arch: Win32 }, { name: windows-arm, runner: windows-11-arm, arch: ARM64 }, ] name: ${{matrix.arch.name}} runs-on: ${{matrix.arch.runner}} steps: - uses: actions/checkout@v4 - name: cmake configure run: cmake . -DMESHOPT_BUILD_DEMO=ON -DMESHOPT_BUILD_GLTFPACK=ON -DMESHOPT_WERROR=ON -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" -A ${{matrix.arch.arch}} - name: cmake test shell: bash # necessary for fail-fast run: | cmake --build . -- -property:Configuration=Debug -verbosity:minimal Debug/meshoptdemo.exe demo/pirate.obj cmake --build . -- -property:Configuration=Release -verbosity:minimal Release/meshoptdemo.exe demo/pirate.obj nodejs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '16' - name: test decoder run: node js/meshopt_decoder.test.js - name: test simd decoder run: node --experimental-wasm-simd js/meshopt_decoder.test.js - name: test encoder run: node js/meshopt_encoder.test.js - name: test simplifier run: node js/meshopt_simplifier.test.js - name: test clusterizer run: node js/meshopt_clusterizer.test.js - name: check es5 run: | npm install -g es-check@7.2.1 npx es-check --module es5 js/meshopt_decoder.mjs js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js npx es-check --module es2020 gltf/library.js gltfpack: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/checkout@v4 with: repository: KhronosGroup/glTF-Sample-Assets path: glTF-Sample-Assets - name: work around ASLR+ASAN compatibility run: sudo sysctl -w vm.mmap_rnd_bits=28 - name: make run: make -j2 config=sanitize gltfpack - name: test run: find glTF-Sample-Assets -name '*.gltf' -or -name '*.glb' | xargs -P 2 -L 16 -d '\n' ./gltfpack -cc -test - name: pack run: find glTF-Sample-Assets -name '*.gltf' | grep -v '\-Draco/' | xargs -P 2 -L 16 -d '\n' -I '{}' ./gltfpack -i '{}' -o '{}pack.gltf' - name: validate run: | curl -sL $VALIDATOR | tar xJ find glTF-Sample-Assets -name '*.gltfpack.gltf' | xargs -P 2 -L 1 -d '\n' ./gltf_validator -r -a env: VALIDATOR: https://github.com/KhronosGroup/glTF-Validator/releases/download/2.0.0-dev.3.10/gltf_validator-2.0.0-dev.3.10-linux64.tar.xz gltfpack-js: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '18' - name: install wasi run: | curl -sL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$VERSION/wasi-sdk-$VERSION.0-x86_64-linux.deb > wasi-sdk.deb sudo dpkg -i wasi-sdk.deb env: VERSION: 25 - name: build run: | make -j2 -B gltf/library.wasm js git status - name: test run: | node gltf/cli.js -i demo/pirate.obj -o pirate.glb -v node gltf/cli.js -i `pwd`/pirate.glb -o pirate-repack.glb -cc -v wc -c pirate.glb pirate-repack.glb node js/meshopt_decoder.test.js node js/meshopt_encoder.test.js node js/meshopt_simplifier.test.js node js/meshopt_clusterizer.test.js gltfpack-full: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/checkout@v4 with: repository: zeux/basis_universal ref: gltfpack path: basis_universal - uses: actions/checkout@v4 with: repository: webmproject/libwebp ref: 1.6.0 path: libwebp - name: cmake configure run: cmake . -DMESHOPT_BUILD_GLTFPACK=ON -DMESHOPT_GLTFPACK_BASISU_PATH=basis_universal -DMESHOPT_GLTFPACK_LIBWEBP_PATH=libwebp - name: cmake build run: cmake --build . --target gltfpack -j 4 gltfpack-coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/checkout@v4 with: repository: zeux/basis_universal ref: gltfpack path: basis_universal - uses: actions/checkout@v4 with: repository: KhronosGroup/glTF-Sample-Assets path: glTF-Sample-Assets - name: build basisu run: | cd basis_universal cmake . -D CMAKE_BUILD_TYPE=Debug make -j4 basisu_encoder - name: make run: make -j2 config=coverage BASISU=basis_universal gltfpack - name: test run: | find glTF-Sample-Assets -name '*.gltf' -or -name '*.glb' | xargs -P 2 -L 16 -d '\n' ./gltfpack -cc -test ./gltfpack -test demo/pirate.obj -si 0.5 ./gltfpack -test demo/pirate.obj -si 0.5 -slb -se 0.02 ./gltfpack -test demo/pirate.obj -si 0.1 -sa ./gltfpack -test demo/pirate.obj -noq ./gltfpack -test demo/pirate.obj -vp 16 -vt 16 -vn 16 -vc 16 ./gltfpack -test demo/pirate.obj -vpf -vtf -vnf ./gltfpack -test demo/pirate.obj -vi -c ./gltfpack -test glTF-Sample-Assets/Models/ABeautifulGame/glTF/ABeautifulGame.gltf -mi -c ./gltfpack -test glTF-Sample-Assets/Models/ABeautifulGame/glTF/ABeautifulGame.gltf -kn -km -ke ./gltfpack -test glTF-Sample-Assets/Models/ABeautifulGame/glTF/ABeautifulGame.gltf -si 0.1 -sp ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -vpf -vtf -c ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -vpf -vtf -cc ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tc ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tc -tq color 10 -tu normal,attrib -ts attrib 0.5 -tl color 512 ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tr ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tc -ts 0.5 -tl 64 ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tc color -tfy -tq 4 -tj 1 ./gltfpack -test glTF-Sample-Assets/Models/CesiumMan/glTF/CesiumMan.gltf -tu -ts 0.6 -tp -ar 0 ./gltfpack -test glTF-Sample-Assets/Models/PrimitiveModeNormalsTest/glTF/PrimitiveModeNormalsTest.gltf -si 0.5 ./gltfpack -test glTF-Sample-Assets/Models/VertexColorTest/glTF/VertexColorTest.gltf -si 0.5 -vc 12 ./gltfpack -test glTF-Sample-Assets/Models/SimpleMeshes/glTF/SimpleMeshes.gltf -mm ./gltfpack -test glTF-Sample-Assets/Models/SimpleMeshes/glTF/SimpleMeshes.gltf -kn echo newmtl Leather > demo/pirate.mtl echo map_Kd leather.jpg >> demo/pirate.mtl echo map_d leather.jpg >> demo/pirate.mtl ./gltfpack -test demo/pirate.obj - name: test output run: | ./gltfpack || true ./gltfpack -h || true ./gltfpack -i glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -o box.glb -vv -r box.json ./gltfpack -i glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -o box.gltf -cf - name: upload coverage run: | find . -type f -name '*.gcno' -exec gcov -p {} + sed -i -e "s/#####\(.*\)\(\/\/ unreachable.*\)/ -\1\2/" *.gcov bash <(curl -s https://codecov.io/bash) -f './gltf*.gcov' -X search -t ${{secrets.CODECOV_TOKEN}} -B ${{github.ref}} macos-iphone: runs-on: macos-14 steps: - uses: actions/checkout@v4 - name: make run: make -j2 config=iphone ================================================ FILE: .github/workflows/cifuzz.yml ================================================ name: CIFuzz on: push: branches: - 'master' paths-ignore: - '*.md' jobs: Fuzzing: runs-on: ubuntu-latest steps: - name: Build Fuzzers id: build uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master with: oss-fuzz-project-name: 'meshoptimizer' dry-run: false language: c++ - name: Run Fuzzers uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: oss-fuzz-project-name: 'meshoptimizer' fuzz-seconds: 30 dry-run: false language: c++ - name: Upload Crash uses: actions/upload-artifact@v4 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts ================================================ FILE: .github/workflows/release.yml ================================================ name: release on: push: branches: - 'master' paths-ignore: - '*.md' jobs: gltfpack: strategy: matrix: os: [ { name: windows, version: windows-latest }, { name: ubuntu, version: ubuntu-22.04 }, { name: macos, version: macos-14 }, { name: macos-intel, version: macos-14 }, ] name: gltfpack-${{matrix.os.name}} runs-on: ${{matrix.os.version}} steps: - uses: actions/checkout@v4 - uses: actions/checkout@v4 with: repository: zeux/basis_universal ref: gltfpack path: basis_universal - uses: actions/checkout@v4 with: repository: webmproject/libwebp ref: 1.6.0 path: libwebp - name: cmake configure run: cmake . -DMESHOPT_BUILD_GLTFPACK=ON -DMESHOPT_GLTFPACK_BASISU_PATH=basis_universal -D MESHOPT_GLTFPACK_LIBWEBP_PATH=libwebp -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded" -DCMAKE_BUILD_TYPE=Release - name: cmake configure x64 run: cmake . -DSSE=ON if: matrix.os.name == 'ubuntu' - name: cmake configure mac-x64 run: cmake . -DSSE=ON -DCMAKE_OSX_ARCHITECTURES=x86_64 if: matrix.os.name == 'macos-intel' - name: cmake build run: cmake --build . --target gltfpack --config Release -j 3 - uses: actions/upload-artifact@v4 with: name: gltfpack-windows path: Release/gltfpack.exe if: matrix.os.name == 'windows' - uses: actions/upload-artifact@v4 with: name: gltfpack-${{matrix.os.name}} path: gltfpack if: matrix.os.name != 'windows' nodejs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '18' - name: install wasi run: | curl -sL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$VERSION/wasi-sdk-$VERSION.0-x86_64-linux.deb > wasi-sdk.deb sudo dpkg -i wasi-sdk.deb env: VERSION: 25 - name: build run: | make -j2 -B gltf/library.wasm js git status - name: npm pack run: | cp LICENSE.md gltf/ cp LICENSE.md js/ cd gltf && npm pack && cd .. cd js && npm pack && cd .. - uses: actions/upload-artifact@v4 with: name: gltfpack-npm path: gltf/gltfpack-*.tgz - uses: actions/upload-artifact@v4 with: name: meshoptimizer-npm path: js/meshoptimizer-*.tgz ================================================ FILE: .gitignore ================================================ # IDE integrations /.cache/ /.idea/ /.vs/ /.vscode/ /.zed/ # Build files /build/ /cmake*/ /out/ /*.dSYM/ /gltf/library.wasm compile_commands.json # Test files /data/ /*.log /perf.data* ================================================ FILE: .prettierrc ================================================ { "useTabs": true, "tabWidth": 4, "semi": true, "singleQuote": true, "printWidth": 150, "trailingComma": "es5", "overrides": [ { "files": "*.yml", "options": { "tabWidth": 2 } }, ] } ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.5...3.30) if(POLICY CMP0077) cmake_policy(SET CMP0077 NEW) # Enables override of options from parent CMakeLists.txt endif() if(POLICY CMP0091) cmake_policy(SET CMP0091 NEW) # Enables use of MSVC_RUNTIME_LIBRARY endif() if(POLICY CMP0092) cmake_policy(SET CMP0092 NEW) # Enables clean /W4 override for MSVC endif() project(meshoptimizer VERSION 1.0 LANGUAGES CXX) option(MESHOPT_BUILD_DEMO "Build demo" OFF) option(MESHOPT_BUILD_GLTFPACK "Build gltfpack" OFF) option(MESHOPT_BUILD_SHARED_LIBS "Build shared libraries" OFF) option(MESHOPT_STABLE_EXPORTS "Only export stable APIs from shared library" OFF) option(MESHOPT_WERROR "Treat warnings as errors" OFF) option(MESHOPT_INSTALL "Install library" ON) # Optional gltfpack components for texture compression support set(MESHOPT_GLTFPACK_BASISU_PATH "" CACHE STRING "") # Basis Universal, https://github.com/BinomialLLC/basis_universal set(MESHOPT_GLTFPACK_LIBWEBP_PATH "" CACHE STRING "") # libwebp, https://github.com/webmproject/libwebp set(SOURCES src/meshoptimizer.h src/allocator.cpp src/clusterizer.cpp src/indexanalyzer.cpp src/indexcodec.cpp src/indexgenerator.cpp src/meshletcodec.cpp src/meshletutils.cpp src/opacitymap.cpp src/overdrawoptimizer.cpp src/partition.cpp src/quantization.cpp src/rasterizer.cpp src/simplifier.cpp src/spatialorder.cpp src/stripifier.cpp src/vcacheoptimizer.cpp src/vertexcodec.cpp src/vertexfilter.cpp src/vfetchoptimizer.cpp ) set(GLTF_SOURCES gltf/animation.cpp gltf/encodebasis.cpp gltf/encodewebp.cpp gltf/fileio.cpp gltf/gltfpack.cpp gltf/image.cpp gltf/json.cpp gltf/material.cpp gltf/mesh.cpp gltf/node.cpp gltf/parseobj.cpp gltf/parselib.cpp gltf/parsegltf.cpp gltf/stream.cpp gltf/write.cpp ) if(WIN32) list(APPEND GLTF_SOURCES gltf/gltfpack.manifest) endif() if(MSVC) add_compile_options(/W4) else() add_compile_options(-Wall -Wextra -Wshadow -Wno-missing-field-initializers) endif() if(MESHOPT_WERROR) if(MSVC) add_compile_options(/WX) else() add_compile_options(-Werror) endif() endif() if(MESHOPT_BUILD_SHARED_LIBS) add_library(meshoptimizer SHARED ${SOURCES}) else() add_library(meshoptimizer STATIC ${SOURCES}) endif() target_include_directories(meshoptimizer INTERFACE "$") if(MESHOPT_BUILD_SHARED_LIBS) set_target_properties(meshoptimizer PROPERTIES CXX_VISIBILITY_PRESET hidden) set_target_properties(meshoptimizer PROPERTIES VISIBILITY_INLINES_HIDDEN ON) # soversion may be requested via -DMESHOPT_SOVERSION=n; note that experimental APIs (marked with MESHOPTIMIZER_EXPERIMENTAL) are not ABI-stable if(MESHOPT_SOVERSION) set_target_properties(meshoptimizer PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${MESHOPT_SOVERSION}) endif() if(WIN32) target_compile_definitions(meshoptimizer INTERFACE "MESHOPTIMIZER_API=__declspec(dllimport)") target_compile_definitions(meshoptimizer PRIVATE "MESHOPTIMIZER_API=__declspec(dllexport)") else() target_compile_definitions(meshoptimizer PUBLIC "MESHOPTIMIZER_API=__attribute__((visibility(\"default\")))") endif() target_compile_definitions(meshoptimizer PUBLIC MESHOPTIMIZER_ALLOC_EXPORT) if(MESHOPT_STABLE_EXPORTS) target_compile_definitions(meshoptimizer PUBLIC "MESHOPTIMIZER_EXPERIMENTAL=") endif() endif() if(MESHOPT_BUILD_DEMO) add_executable(demo demo/main.cpp demo/nanite.cpp demo/tests.cpp tools/objloader.cpp) set_target_properties(demo PROPERTIES CXX_STANDARD 11) set_target_properties(demo PROPERTIES OUTPUT_NAME meshoptdemo) target_link_libraries(demo meshoptimizer) endif() if(MESHOPT_BUILD_GLTFPACK) add_executable(gltfpack ${GLTF_SOURCES}) set_target_properties(gltfpack PROPERTIES CXX_STANDARD 11) target_link_libraries(gltfpack meshoptimizer) if(MESHOPT_BUILD_SHARED_LIBS) string(CONCAT RPATH "$ORIGIN/../" ${CMAKE_INSTALL_LIBDIR}) set_target_properties(gltfpack PROPERTIES INSTALL_RPATH ${RPATH}) endif() if(NOT MESHOPT_GLTFPACK_BASISU_PATH STREQUAL "") get_filename_component(BASISU_PATH ${MESHOPT_GLTFPACK_BASISU_PATH} ABSOLUTE) if (NOT EXISTS ${BASISU_PATH}) message(FATAL_ERROR "Basis Universal path ${BASISU_PATH} not found") endif() if (NOT SSE AND NOT MSVC AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") message(WARNING "Building Basis Universal without SSE4.1 support; performance may be suboptimal") endif() add_subdirectory(${BASISU_PATH} ${CMAKE_CURRENT_BINARY_DIR}/basisu EXCLUDE_FROM_ALL) target_compile_definitions(gltfpack PRIVATE WITH_BASISU) target_link_libraries(gltfpack basisu_encoder) set_source_files_properties(gltf/encodebasis.cpp PROPERTIES INCLUDE_DIRECTORIES ${BASISU_PATH}) # necessary because basisu_encoder doesn't export include directories endif() if(NOT MESHOPT_GLTFPACK_LIBWEBP_PATH STREQUAL "") get_filename_component(LIBWEBP_PATH ${MESHOPT_GLTFPACK_LIBWEBP_PATH} ABSOLUTE) if (NOT EXISTS ${LIBWEBP_PATH}) message(FATAL_ERROR "libwebp path ${LIBWEBP_PATH} not found") endif() add_subdirectory(${LIBWEBP_PATH} ${CMAKE_CURRENT_BINARY_DIR}/libwebp EXCLUDE_FROM_ALL) target_compile_definitions(gltfpack PRIVATE WITH_LIBWEBP) target_link_libraries(gltfpack webp) # when gltfpack is built with Basis Universal, we use Basis image decoders for PNG/JPEG support for ease of distribution if(NOT MESHOPT_GLTFPACK_BASISU_PATH STREQUAL "") target_compile_definitions(gltfpack PRIVATE WITH_LIBWEBP_BASIS) else() target_link_libraries(gltfpack imagedec) endif() endif() if(NOT MESHOPT_GLTFPACK_BASISU_PATH STREQUAL "" OR NOT MESHOPT_GLTFPACK_LIBWEBP_PATH STREQUAL "") find_package(Threads REQUIRED) target_link_libraries(gltfpack Threads::Threads) endif() endif() if(MESHOPT_INSTALL) include(GNUInstallDirs) install(TARGETS meshoptimizer EXPORT meshoptimizerTargets COMPONENT meshoptimizer RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) if(MESHOPT_BUILD_GLTFPACK) install(TARGETS gltfpack RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() install(FILES src/meshoptimizer.h COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(EXPORT meshoptimizerTargets COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/meshoptimizer NAMESPACE meshoptimizer::) if(MSVC) set(PDB_TARGETS meshoptimizer) if(MESHOPT_BUILD_GLTFPACK) list(APPEND PDB_TARGETS gltfpack) endif() foreach(TARGET ${PDB_TARGETS}) get_target_property(TARGET_TYPE ${TARGET} TYPE) if(NOT ${TARGET_TYPE} STREQUAL "STATIC_LIBRARY") install(FILES $ COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) endif() endforeach(TARGET) endif() include(CMakePackageConfigHelpers) # CMake 3.18+ supports file(CONFIGURE OUTPUT file CONTENT content @ONLY) but we only require 3.5+ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfig.cmake "include(\"\${CMAKE_CURRENT_LIST_DIR}/meshoptimizerTargets.cmake\")\n") write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfigVersion.cmake COMPATIBILITY ExactVersion) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfigVersion.cmake COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/meshoptimizer) endif() ================================================ FILE: CONTRIBUTING.md ================================================ Thanks for deciding to contribute to meshoptimizer! These guidelines will try to help make the process painless and efficient. ## Questions If you have a question regarding the library usage, please [open a GitHub issue](https://github.com/zeux/meshoptimizer/issues/new). Some questions just need answers, but it's nice to keep them for future reference in case other people want to know the same thing. Some questions help improve the library interface or documentation by inspiring future changes. ## Bugs If the library doesn't compile on your system, compiles with warnings, doesn't seem to run correctly for your input data or if anything else is amiss, please [open a GitHub issue](https://github.com/zeux/meshoptimizer/issues/new). It helps if you note the version of the library this issue happens in, the version of your compiler for compilation issues, and a reproduction case for runtime bugs. Of course, feel free to [create a pull request](https://help.github.com/articles/about-pull-requests/) to fix the bug yourself. ## Features New algorithms and improvements to existing algorithms are always welcome; you can open an issue or make the change yourself and submit a pull request. For major features, consider opening an issue describing an improvement you'd like to see or make before opening a pull request. This will give us a chance to discuss the idea before implementing it - some algorithms may not be easy to integrate into existing programs, may not be robust to arbitrary meshes or may be expensive to run or implement/maintain, so a discussion helps make sure these don't block the algorithm development. ## Code style Contributions to this project are expected to follow the existing code style. `.clang-format` file mostly defines syntactic styling rules (you can run `make format` to format the code accordingly). As for naming conventions, this library uses `snake_case` for variables, `lowerCamelCase` for functions, `UpperCamelCase` for types, `kCamelCase` for global constants and `SCARY_CASE` for macros. All public functions/types must additionally have an extra `meshopt_` prefix to avoid symbol conflicts. ## Dependencies Please note that this library uses C89 interface for all APIs and a C++98 implementation - C++11 features can not be used. This choice is made to maximize compatibility to make sure that any toolchain, including legacy proprietary gaming console toolchains, can compile this code. Additionally, the library code has zero external dependencies, does not depend on STL and does not use RTTI or exceptions. This, again, maximizes compatibility and makes sure the library can be used in environments where STL use is discouraged or prohibited, as well as maximizing runtime performance and minimizing compilation times. The demo program uses STL since it serves as an example of usage and as a test harness, not as production-ready code. ## Testing All pull requests will run through a continuous integration pipeline using GitHub Actions that will run the built-in unit tests and integration tests on Windows, macOS and Linux with gcc, clang and msvc compilers. You can run the tests yourself using `make test` or building the demo program with `cmake -DBUILD_DEMO=ON` and running it. Unit tests can be found in `demo/tests.cpp` and functional tests - in `demo/main.cpp`; when making code changes please try to make sure they are covered by an existing test or add a new test accordingly. ## Documentation Documentation for this library resides in the `meshoptimizer.h` header, with examples as part of a usage manual available in `README.md`. Changes to documentation are always welcome and should use issues/pull requests as outlined above; please note that `README.md` only contains documentation for stable algorithms, as experimental algorithms may change the interface without concern for backwards compatibility. ## Sensitive communication If you prefer to not disclose the issues or information relevant to the issue such as reproduction case to the public, you can always contact the author via e-mail (arseny.kapoulkine@gmail.com). ## Contributor agreement Any code you submit will become part of the repository and be distributed under the [meshoptimizer license](https://github.com/zeux/meshoptimizer/blob/master/LICENSE.md). By submitting code to the project you agree that the code is your work and that you can give it to the project. You also agree by submitting your code that you grant all transferrable rights to the code to the project maintainer, including for example re-licensing the code, modifying the code, and distributing it in source or binary forms. Specifically, this includes a requirement that you assign copyright to the project maintainer. ================================================ FILE: LICENSE.md ================================================ MIT License Copyright (c) 2016-2026 Arseny Kapoulkine 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. ================================================ FILE: Makefile ================================================ MAKEFLAGS+=-r -j config=debug files=demo/pirate.obj BUILD=build/$(config) LIBRARY_SOURCES=$(wildcard src/*.cpp) LIBRARY_OBJECTS=$(LIBRARY_SOURCES:%=$(BUILD)/%.o) DEMO_SOURCES=$(wildcard demo/*.c demo/*.cpp) tools/objloader.cpp DEMO_OBJECTS=$(DEMO_SOURCES:%=$(BUILD)/%.o) GLTFPACK_SOURCES=$(wildcard gltf/*.cpp) GLTFPACK_OBJECTS=$(GLTFPACK_SOURCES:%=$(BUILD)/%.o) OBJECTS=$(LIBRARY_OBJECTS) $(DEMO_OBJECTS) $(GLTFPACK_OBJECTS) LIBRARY=$(BUILD)/libmeshoptimizer.a DEMO=$(BUILD)/meshoptdemo CFLAGS=-g -Wall -Wextra -std=c89 CXXFLAGS=-g -Wall -Wextra -Wshadow -Wno-missing-field-initializers LDFLAGS= $(LIBRARY_OBJECTS): CXXFLAGS+=-std=gnu++98 $(DEMO_OBJECTS): CXXFLAGS+=-std=c++11 $(GLTFPACK_OBJECTS): CXXFLAGS+=-std=c++11 ifdef BASISU $(GLTFPACK_OBJECTS): CXXFLAGS+=-DWITH_BASISU $(BUILD)/gltf/encodebasis.cpp.o: CXXFLAGS+=-I$(BASISU) gltfpack: LDFLAGS+=-lpthread $(BASISU)/libbasisu_encoder.a endif WASI_SDK?=/opt/wasi-sdk WASMCC?=$(WASI_SDK)/bin/clang++ WASIROOT?=$(WASI_SDK)/share/wasi-sysroot WASM_FLAGS=--target=wasm32-wasi --sysroot=$(WASIROOT) WASM_FLAGS+=-Wall -Wextra WASM_FLAGS+=-O3 -DNDEBUG -nostartfiles -nostdlib -Wl,--no-entry -Wl,-s WASM_FLAGS+=-mcpu=mvp # make sure clang doesn't use post-MVP features like sign extension WASM_FLAGS+=-fno-slp-vectorize -fno-vectorize -fno-unroll-loops WASM_FLAGS+=-Wl,-z -Wl,stack-size=36864 -Wl,--initial-memory=65536 WASM_EXPORT_PREFIX=-Wl,--export WASM_DECODER_SOURCES=src/vertexcodec.cpp src/indexcodec.cpp src/vertexfilter.cpp tools/wasmstubs.cpp WASM_DECODER_EXPORTS=meshopt_decodeVertexBuffer meshopt_decodeIndexBuffer meshopt_decodeIndexSequence meshopt_decodeFilterOct meshopt_decodeFilterQuat meshopt_decodeFilterExp meshopt_decodeFilterColor sbrk __wasm_call_ctors WASM_ENCODER_SOURCES=src/vertexcodec.cpp src/indexcodec.cpp src/vertexfilter.cpp src/vcacheoptimizer.cpp src/vfetchoptimizer.cpp src/spatialorder.cpp tools/wasmstubs.cpp WASM_ENCODER_EXPORTS=meshopt_encodeVertexBuffer meshopt_encodeVertexBufferBound meshopt_encodeVertexBufferLevel meshopt_encodeIndexBuffer meshopt_encodeIndexBufferBound meshopt_encodeIndexSequence meshopt_encodeIndexSequenceBound meshopt_encodeVertexVersion meshopt_encodeIndexVersion meshopt_encodeFilterOct meshopt_encodeFilterQuat meshopt_encodeFilterExp meshopt_encodeFilterColor meshopt_optimizeVertexCache meshopt_optimizeVertexCacheStrip meshopt_optimizeVertexFetchRemap meshopt_spatialSortRemap sbrk __wasm_call_ctors WASM_SIMPLIFIER_SOURCES=src/simplifier.cpp src/vfetchoptimizer.cpp src/indexgenerator.cpp tools/wasmstubs.cpp WASM_SIMPLIFIER_EXPORTS=meshopt_simplify meshopt_simplifyWithAttributes meshopt_simplifyWithUpdate meshopt_simplifyScale meshopt_simplifyPoints meshopt_simplifySloppy meshopt_simplifyPrune meshopt_optimizeVertexFetchRemap meshopt_generatePositionRemap sbrk __wasm_call_ctors WASM_CLUSTERIZER_SOURCES=src/clusterizer.cpp src/meshletutils.cpp tools/wasmstubs.cpp WASM_CLUSTERIZER_EXPORTS=meshopt_buildMeshletsBound meshopt_buildMeshletsFlex meshopt_buildMeshletsSpatial meshopt_computeClusterBounds meshopt_computeMeshletBounds meshopt_computeSphereBounds meshopt_optimizeMeshlet sbrk __wasm_call_ctors ifneq ($(werror),) CFLAGS+=-Werror CXXFLAGS+=-Werror endif ifeq ($(config),iphone) IPHONESDK=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk CFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK) CXXFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK) -stdlib=libc++ LDFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK) -L $(IPHONESDK)/usr/lib -mios-version-min=7.0 endif ifeq ($(config),trace) CXXFLAGS+=-DTRACE=1 endif ifeq ($(config),tracev) CXXFLAGS+=-DTRACE=2 endif ifeq ($(config),release) CXXFLAGS+=-O3 -DNDEBUG endif ifeq ($(config),coverage) CXXFLAGS+=-coverage LDFLAGS+=-coverage endif ifeq ($(config),release-avx) CXXFLAGS+=-O3 -DNDEBUG -mavx endif ifeq ($(config),release-avx512) CXXFLAGS+=-O3 -DNDEBUG -mavx512vl -mavx512vbmi -mavx512vbmi2 endif ifeq ($(config),release-scalar) CXXFLAGS+=-O3 -DNDEBUG -DMESHOPTIMIZER_NO_SIMD endif ifeq ($(config),coverage-scalar) CXXFLAGS+=-coverage -DMESHOPTIMIZER_NO_SIMD LDFLAGS+=-coverage endif ifeq ($(config),sanitize) CXXFLAGS+=-fsanitize=address,undefined -fsanitize-undefined-trap-on-error LDFLAGS+=-fsanitize=address,undefined endif ifeq ($(config),analyze) CXXFLAGS+=--analyze endif ifeq ($(config),fuzz) CXXFLAGS+=-O1 -fsanitize=address,fuzzer LDFLAGS+=-fsanitize=address,fuzzer $(GLTFPACK_OBJECTS): CXXFLAGS+=-DGLTFFUZZ endif -include Makefile.config all: $(DEMO) test: $(DEMO) $(DEMO) $(files) check: $(DEMO) $(DEMO) dev: $(DEMO) $(DEMO) -d $(files) nanite: $(DEMO) $(DEMO) -n $(files) format: clang-format -i src/*.h src/*.cpp demo/*.cpp gltf/*.h gltf/*.cpp formatjs: prettier -w js/*.js gltf/*.js demo/*.html js/*.ts js: js/meshopt_decoder.cjs js/meshopt_decoder.mjs js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js symbols: $(BUILD)/amalgamated.so nm $< -U -g gltfpack: $(BUILD)/gltfpack ln -fs $^ $@ ifeq ($(config),fuzz) gltffuzz: $(BUILD)/gltfpack cp $^ $@ mkdir -p /tmp/gltffuzz cp gltf/fuzz.glb /tmp/gltffuzz/ ./gltffuzz /tmp/gltffuzz -fork=16 -dict=gltf/fuzz.dict -ignore_crashes=1 -max_len=32768 endif $(BUILD)/gltfpack: $(GLTFPACK_OBJECTS) $(LIBRARY) $(CXX) $^ $(LDFLAGS) -o $@ gltfpack.wasm: gltf/library.wasm gltf/library.wasm: $(LIBRARY_SOURCES) $(GLTFPACK_SOURCES) $(WASMCC) $^ -o $@ -Wall -Os -DNDEBUG --target=wasm32-wasi --sysroot=$(WASIROOT) -nostartfiles -Wl,--no-entry -Wl,--export=pack -Wl,--export=malloc -Wl,--export=free -Wl,--export=__wasm_call_ctors -Wl,-s -Wl,--allow-undefined-file=gltf/wasistubs.txt build/decoder_base.wasm: $(WASM_DECODER_SOURCES) @mkdir -p build $(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_DECODER_EXPORTS)) -o $@ build/decoder_simd.wasm: $(WASM_DECODER_SOURCES) @mkdir -p build $(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_DECODER_EXPORTS)) -o $@ -msimd128 -mbulk-memory build/encoder.wasm: $(WASM_ENCODER_SOURCES) @mkdir -p build $(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_ENCODER_EXPORTS)) -lc -o $@ build/simplifier.wasm: $(WASM_SIMPLIFIER_SOURCES) @mkdir -p build $(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_SIMPLIFIER_EXPORTS)) -lc -o $@ build/clusterizer.wasm: $(WASM_CLUSTERIZER_SOURCES) @mkdir -p build $(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_CLUSTERIZER_EXPORTS)) -lc -o $@ js/meshopt_decoder.mjs: build/decoder_base.wasm build/decoder_simd.wasm tools/wasmpack.py sed -i "s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1 | sed 's/\s\+(.*//')#" $@ sed -i "s#Built from meshoptimizer .*#Built from meshoptimizer $$(cat src/meshoptimizer.h | grep -Po '(?<=version )[0-9.]+')#" $@ python3 tools/wasmpack.py patch $@ base $@ echo "if (typeof exports === 'object' && typeof module === 'object') module.exports = MeshoptDecoder;" >>$@ echo "else if (typeof define === 'function' && define['amd']) define([], function () { return MeshoptDecoder; });" >>$@ echo "else if (typeof exports === 'object') exports['MeshoptDecoder'] = MeshoptDecoder;" >>$@ echo "else (typeof self !== 'undefined' ? self : this).MeshoptDecoder = MeshoptDecoder;" >>$@ $(DEMO): $(DEMO_OBJECTS) $(LIBRARY) $(CXX) $^ $(LDFLAGS) -o $@ vcachetuner: tools/vcachetuner.cpp tools/objloader.cpp $(LIBRARY) $(CXX) $^ -fopenmp $(CXXFLAGS) -std=c++11 $(LDFLAGS) -o $@ codecbench: tools/codecbench.cpp $(LIBRARY) $(CXX) $^ $(CXXFLAGS) $(LDFLAGS) -o $@ codecbench.js: tools/codecbench.cpp $(LIBRARY_SOURCES) emcc $^ -O3 -g -DNDEBUG -s TOTAL_MEMORY=268435456 -s SINGLE_FILE=1 -o $@ codecbench-simd.js: tools/codecbench.cpp $(LIBRARY_SOURCES) emcc $^ -O3 -g -DNDEBUG -s TOTAL_MEMORY=268435456 -s SINGLE_FILE=1 -msimd128 -o $@ codecbench.wasm: tools/codecbench.cpp $(LIBRARY_SOURCES) $(WASMCC) $^ -fno-exceptions --target=wasm32-wasi --sysroot=$(WASIROOT) -lc++ -lc++abi -O3 -g -DNDEBUG -o $@ codecbench-simd.wasm: tools/codecbench.cpp $(LIBRARY_SOURCES) $(WASMCC) $^ -fno-exceptions --target=wasm32-wasi --sysroot=$(WASIROOT) -lc++ -lc++abi -O3 -g -DNDEBUG -msimd128 -o $@ codectest: tools/codectest.cpp $(LIBRARY) $(CXX) $^ $(CXXFLAGS) $(LDFLAGS) -o $@ codecfuzz: tools/codecfuzz.cpp src/vertexcodec.cpp src/indexcodec.cpp src/meshletcodec.cpp $(CXX) $^ -fsanitize=fuzzer,address,undefined -O1 -g -o $@ clusterfuzz: tools/clusterfuzz.cpp src/clusterizer.cpp src/partition.cpp $(CXX) $^ -fsanitize=fuzzer,address,undefined -O1 -g -o $@ simplifyfuzz: tools/simplifyfuzz.cpp src/simplifier.cpp $(CXX) $^ -fsanitize=fuzzer,address,undefined -O1 -g -o $@ $(LIBRARY): $(LIBRARY_OBJECTS) ar rcs $@ $^ $(BUILD)/amalgamated.so: $(LIBRARY_SOURCES) @mkdir -p $(dir $@) cat $^ | $(CXX) $(CXXFLAGS) -x c++ - -I src/ -o $@ -shared -fPIC $(BUILD)/%.cpp.o: %.cpp @mkdir -p $(dir $@) $(CXX) $< $(CXXFLAGS) -c -MMD -MP -o $@ $(BUILD)/%.c.o: %.c @mkdir -p $(dir $@) $(CC) $< $(CFLAGS) -c -MMD -MP -o $@ -include $(OBJECTS:.o=.d) clean: rm -rf $(BUILD) .PHONY: all clean format js ================================================ FILE: README.md ================================================ # 🐇 meshoptimizer [![Actions Status](https://github.com/zeux/meshoptimizer/workflows/build/badge.svg)](https://github.com/zeux/meshoptimizer/actions) [![codecov.io](https://codecov.io/github/zeux/meshoptimizer/coverage.svg?branch=master)](https://codecov.io/github/zeux/meshoptimizer?branch=master) [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.md) [![GitHub](https://img.shields.io/badge/repo-github-green.svg)](https://github.com/zeux/meshoptimizer) ## Purpose When a GPU renders triangle meshes, various stages of the GPU pipeline have to process vertex and index data. The efficiency of these stages depends on the data you feed to them; this library provides algorithms to help optimize meshes for these stages, as well as algorithms to reduce the mesh complexity and storage overhead. The library provides a C and C++ interface for all algorithms; you can use it from C/C++ or from other languages via FFI (such as P/Invoke). If you want to use this library from Rust, you should use [meshopt crate](https://crates.io/crates/meshopt). JavaScript interface for some algorithms is available through [meshoptimizer.js](https://www.npmjs.com/package/meshoptimizer). Two companion projects are developed and distributed alongside the library: [gltfpack](./gltf/README.md), a command-line tool that automatically optimizes glTF files, and [clusterlod.h](./demo/clusterlod.h), a single-header C/C++ library for continuous level of detail using clustered simplification. ## Installing meshoptimizer is hosted on GitHub; you can download the latest release using git: ``` git clone -b v1.0 https://github.com/zeux/meshoptimizer.git ``` Alternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v1.0.zip). The library is also available as a Linux package in several distributions ([ArchLinux](https://aur.archlinux.org/packages/meshoptimizer/), [Debian](https://packages.debian.org/libmeshoptimizer), [FreeBSD](https://www.freshports.org/misc/meshoptimizer/), [Nix](https://mynixos.com/nixpkgs/package/meshoptimizer), [Ubuntu](https://packages.ubuntu.com/libmeshoptimizer)), as well as a [Vcpkg port](https://github.com/microsoft/vcpkg/tree/master/ports/meshoptimizer) (see [installation instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started)) and a [Conan package](https://conan.io/center/recipes/meshoptimizer). [gltfpack](./gltf/README.md) is available as a pre-built binary on [Releases page](https://github.com/zeux/meshoptimizer/releases) or via [npm package](https://www.npmjs.com/package/gltfpack). Native binaries are recommended since they are more efficient and support texture compression. ## Building meshoptimizer is distributed as a C/C++ header (`src/meshoptimizer.h`) and a set of C++ source files (`src/*.cpp`). To include it in your project, you can use one of two options: * Use CMake to build the library (either as a standalone project or as part of your project) * Add source files to your project's build system The source files are organized in such a way that you don't need to change your build-system settings, and you only need to add the source files for the algorithms you use. They should build without warnings or special compilation options on all major compilers. If you prefer amalgamated builds, you can also concatenate the source files into a single `.cpp` file and build that instead. To use meshoptimizer functions, simply `#include` the header `meshoptimizer.h`; the library source is C++, but the header is C-compatible. ## Core pipeline When optimizing a mesh, to maximize rendering efficiency you should typically feed it through a set of optimizations (the order is important!): 1. Indexing 2. Vertex cache optimization 3. (optional) Overdraw optimization 4. Vertex fetch optimization 5. Vertex quantization 6. (optional) Shadow indexing ### Indexing Most algorithms in this library assume that a mesh has a vertex buffer and an index buffer. For algorithms to work well and also for GPU to render your mesh efficiently, the vertex buffer has to have no redundant vertices; you can generate an index buffer from an unindexed vertex buffer or reindex an existing (potentially redundant) index buffer as follows: > Note: meshoptimizer generally works with 32-bit (`unsigned int`) indices, however when using C++ APIs you can use any integer type for index data by using the provided template overloads. By convention, remap tables always use `unsigned int`. First, generate a remap table from your existing vertex (and, optionally, index) data: ```c++ size_t index_count = face_count * 3; size_t unindexed_vertex_count = face_count * 3; std::vector remap(unindexed_vertex_count); // temporary remap table size_t vertex_count = meshopt_generateVertexRemap(&remap[0], NULL, index_count, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex)); ``` Note that in this case we only have an unindexed vertex buffer; when input mesh has an index buffer, it will need to be passed to `meshopt_generateVertexRemap` instead of `NULL`, along with the correct source vertex count. In either case, the remap table is generated based on binary equivalence of the input vertices, so the resulting mesh will render the same way. Binary equivalence considers all input bytes, including padding which should be zero-initialized if the vertex structure has gaps. After generating the remap table, you can allocate space for the target vertex buffer (`vertex_count` elements) and index buffer (`index_count` elements) and generate them: ```c++ meshopt_remapIndexBuffer(indices, NULL, index_count, &remap[0]); meshopt_remapVertexBuffer(vertices, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex), &remap[0]); ``` You can then further optimize the resulting buffers by calling the other functions on them in-place. `meshopt_generateVertexRemap` uses binary equivalence of vertex data, which is generally a reasonable default; however, in some cases some attributes may have floating point drift causing extra vertices to be generated. For such cases, it may be necessary to quantize some attributes (most importantly, normals and tangents) before generating the remap, or use `meshopt_generateVertexRemapCustom` algorithm that allows comparing individual attributes with tolerance by providing a custom comparison function: ```c++ size_t vertex_count = meshopt_generateVertexRemapCustom(&remap[0], NULL, index_count, &unindexed_vertices[0].px, unindexed_vertex_count, sizeof(Vertex), [&](unsigned int lhs, unsigned int rhs) -> bool { const Vertex& lv = unindexed_vertices[lhs]; const Vertex& rv = unindexed_vertices[rhs]; return fabsf(lv.tx - rv.tx) < 1e-3f && fabsf(lv.ty - rv.ty) < 1e-3f; }); ``` ### Vertex cache optimization When the GPU renders the mesh, it runs the vertex shader for each vertex. Historically, GPUs used a small fixed-size post-transform cache (16-32 vertices) with different replacement policies to store the shader output and avoid redundant shader invocations. Modern GPUs still perform vertex reuse, but with substantially different mechanics: vertex invocations are batched into thread groups based on the input indices, and effective reuse depends on factors like vertex shader outputs and rasterizer throughput. To maximize the locality of reused vertex references, you have to reorder your triangles like so: ```c++ meshopt_optimizeVertexCache(indices, indices, index_count, vertex_count); ``` The details of vertex reuse vary between different GPU architectures, so vertex cache optimization uses an adaptive algorithm that produces a triangle sequence with good locality that works well across different GPUs. Alternatively, you can use an algorithm that optimizes specifically for fixed-size FIFO caches: `meshopt_optimizeVertexCacheFifo` (with a recommended cache size of 16). While it generally produces less performant results on most GPUs, it runs ~2x faster, which may benefit rapid content iteration. ### Overdraw optimization After transforming the vertices, GPU sends the triangles for rasterization which results in generating pixels that are usually first run through the depth test, and pixels that pass it get the pixel shader executed to generate the final color. As pixel shaders get more expensive, it becomes more and more important to reduce overdraw. While in general improving overdraw requires view-dependent operations, this library provides an algorithm to reorder triangles to minimize the overdraw from all directions, which you can run after vertex cache optimization like this: ```c++ meshopt_optimizeOverdraw(indices, indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), 1.05f); ``` The overdraw optimizer needs to read vertex positions as a float3 from the vertex; the code snippet above assumes that the vertex stores position as `float x, y, z`. When performing the overdraw optimization you have to specify a floating-point threshold parameter. The algorithm tries to maintain a balance between vertex cache efficiency and overdraw; the threshold determines how much the algorithm can compromise the vertex cache hit ratio, with 1.05 meaning that the resulting ratio should be at most 5% worse than before the optimization. Note that depending on the renderer structure and target hardware, the optimization may or may not be beneficial; for example, mobile GPUs with tiled deferred rendering (PowerVR, Apple) would not benefit from this optimization. For vertex heavy scenes it's recommended to measure the performance impact to ensure that the reduced vertex cache efficiency is outweighed by the reduced overdraw. ### Vertex fetch optimization After the final triangle order has been established, we still can optimize the vertex buffer for memory efficiency. Before running the vertex shader GPU has to fetch the vertex attributes from the vertex buffer; the fetch is usually backed by a memory cache, and as such optimizing the data for the locality of memory access is important. You can do this by running this code: ```c++ meshopt_optimizeVertexFetch(vertices, indices, index_count, vertices, vertex_count, sizeof(Vertex)); ``` This will reorder the vertices in the vertex buffer to try to improve the locality of reference, and rewrite the indices in place to match; if the vertex data is stored using multiple streams, you should use `meshopt_optimizeVertexFetchRemap` instead. This optimization has to be performed on the final index buffer since the optimal vertex order depends on the triangle order. Note that the algorithm does not try to model cache replacement precisely and instead just orders vertices in the order of use, which generally produces results that are close to optimal. ### Vertex quantization To optimize memory bandwidth when fetching the vertex data even further, and to reduce the amount of memory required to store the mesh, it is often beneficial to quantize the vertex attributes to smaller types. While this optimization can technically run at any part of the pipeline (and sometimes doing quantization as the first step can improve indexing by merging almost identical vertices), it generally is easier to run this after all other optimizations since some of them require access to float3 positions. Quantization is usually domain specific; it's common to quantize normals using 3 8-bit integers but you can use higher-precision quantization (for example using 10 bits per component in a 10_10_10_2 format), or a different encoding to use just 2 components. For positions and texture coordinate data the two most common storage formats are half precision floats, and 16-bit normalized integers that encode the position relative to the AABB of the mesh or the UV bounding rectangle. The number of possible combinations here is very large but this library does provide the building blocks, specifically functions to quantize floating point values to normalized integers, as well as half-precision floats. For example, here's how you can quantize a normal using 10-10-10 SNORM encoding: ```c++ unsigned int normal = ((meshopt_quantizeSnorm(v.nx, 10) & 1023) << 20) | ((meshopt_quantizeSnorm(v.ny, 10) & 1023) << 10) | (meshopt_quantizeSnorm(v.nz, 10) & 1023); ``` and here's how you can quantize a position using half precision floats: ```c++ unsigned short px = meshopt_quantizeHalf(v.x); unsigned short py = meshopt_quantizeHalf(v.y); unsigned short pz = meshopt_quantizeHalf(v.z); ``` Since quantized vertex attributes often need to remain in their compact representations for efficient transfer and storage, they are usually dequantized during vertex processing by configuring the GPU vertex input correctly to expect normalized integers or half precision floats, which often needs no or minimal changes to the shader code. When CPU dequantization is required instead, `meshopt_dequantizeHalf` can be used to convert half precision values back to single precision; for normalized integer formats, the dequantization just requires dividing by 2^N-1 for unorm and 2^(N-1)-1 for snorm variants. For example, manually reversing `meshopt_quantizeUnorm(v, 10)` can be done by dividing by 1023. ### Shadow indexing Many rendering pipelines require meshes to be rendered to depth-only targets, such as shadow maps or during a depth pre-pass, in addition to color/G-buffer targets. While using the same geometry data for both cases is possible, reducing the number of unique vertices for depth-only rendering can be beneficial, especially when the source geometry has many attribute seams due to faceted shading or lightmap texture seams. To achieve this, this library provides the `meshopt_generateShadowIndexBuffer` algorithm, which generates a second (shadow) index buffer that can be used with the original vertex data: ```c++ std::vector shadow_indices(index_count); // note: this assumes Vertex starts with float3 positions and should be adjusted accordingly for quantized positions meshopt_generateShadowIndexBuffer(&shadow_indices[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(float) * 3, sizeof(Vertex)); ``` Because the vertex data is shared, shadow indexing should be done after other optimizations of the vertex/index data. However, it's possible (and recommended) to optimize the resulting shadow index buffer for vertex cache: ```c++ meshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], index_count, vertex_count); ``` In some cases, it may be beneficial to split the vertex positions into a separate buffer to maximize efficiency for depth-only rendering. Note that the example above assumes only positions are relevant for shadow rendering, but more complex materials may require adding texture coordinates (for alpha testing) or skinning data to the vertex portion used as a key. `meshopt_generateShadowIndexBufferMulti` can be useful for these cases if the relevant data is not contiguous. Note that for meshes with optimal indexing and few attribute seams, the shadow index buffer will be very similar to the original index buffer, so it may not be always worth generating a separate shadow index buffer even if the rendering pipeline relies on depth-only passes. ## Clusterization While traditionally meshes have served as a unit of rendering, new approaches to rendering and raytracing are starting to use a smaller unit of work, such as clusters or meshlets. This allows more freedom in how the geometry is processed, and can lead to better performance and more efficient use of GPU hardware. This section describes algorithms designed to work with meshes as sets of clusters. ### Mesh shading Modern GPUs are beginning to deviate from the traditional rasterization model. NVidia GPUs starting from Turing and AMD GPUs starting from RDNA2 provide a new programmable geometry pipeline that, instead of being built around index buffers and vertex shaders, is built around mesh shaders - a new shader type that allows to provide a batch of work to the rasterizer. Using mesh shaders in context of traditional mesh rendering provides an opportunity to use a variety of optimization techniques, starting from more efficient vertex reuse, using various forms of culling (e.g. cluster frustum or occlusion culling) and in-memory compression to maximize the utilization of GPU hardware. Beyond traditional rendering mesh shaders provide a richer programming model that can synthesize new geometry more efficiently than common alternatives such as geometry shaders. Mesh shading can be accessed via Vulkan or Direct3D 12 APIs; please refer to [Introduction to Turing Mesh Shaders](https://developer.nvidia.com/blog/introduction-turing-mesh-shaders/) and [Mesh Shaders and Amplification Shaders: Reinventing the Geometry Pipeline](https://devblogs.microsoft.com/directx/coming-to-directx-12-mesh-shaders-and-amplification-shaders-reinventing-the-geometry-pipeline/) for additional information. To use mesh shaders for conventional rendering efficiently, geometry needs to be converted into a series of meshlets; each meshlet represents a small subset of the original mesh and comes with a small set of vertices and a separate micro-index buffer that references vertices in the meshlet. This information can be directly fed to the rasterizer from the mesh shader. This library provides algorithms to create meshlet data for a mesh, and - assuming geometry is static - can compute bounding information that can be used to perform cluster culling, rejecting meshlets that are invisible on screen. To generate meshlet data, this library provides `meshopt_buildMeshlets` algorithm, which tries to balance topological efficiency (by maximizing vertex reuse inside meshlets) with culling efficiency (by minimizing meshlet radius and triangle direction divergence) and produces GPU-friendly data. As an alternative (that can be useful for load-time processing), `meshopt_buildMeshletsScan` can create the meshlet data using a vertex cache-optimized index buffer as a starting point by greedily aggregating consecutive triangles until they go over the meshlet limits. `meshopt_buildMeshlets` is recommended for offline data processing even if cone culling is not used. ```c++ const size_t max_vertices = 64; const size_t max_triangles = 126; // note: in v0.25 or prior, max_triangles needs to be divisible by 4 const float cone_weight = 0.0f; size_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, max_triangles); std::vector meshlets(max_meshlets); std::vector meshlet_vertices(indices.size()); std::vector meshlet_triangles(indices.size()); // note: in v0.25 or prior, use indices.size() + max_meshlets * 3 size_t meshlet_count = meshopt_buildMeshlets(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices.data(), indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex), max_vertices, max_triangles, cone_weight); ``` To generate the meshlet data, `max_vertices` and `max_triangles` need to be set within limits supported by the hardware; for NVidia the values of 64 and 126 are recommended. `cone_weight` should be left as 0 if cluster cone culling is not used, and set to a value between 0 and 1 to balance cone culling efficiency with other forms of culling like frustum or occlusion culling (`0.25` is a reasonable default). > Note that for earlier AMD GPUs, the best configurations tend to use the same limits for `max_vertices` and `max_triangles`, such as 64 and 64, or 128 and 128. Additionally, while NVidia recommends 64/126 as a good configuration, consider using a different configuration like `max_vertices 64, max_triangles 96`, to provide more realistic limits that are achievable on real-world meshes, and to reduce the overhead on other GPUs. Each resulting meshlet refers to a portion of `meshlet_vertices` and `meshlet_triangles` arrays; the arrays are overallocated for the worst case so it's recommended to trim them before saving them as an asset / uploading them to the GPU: ```c++ const meshopt_Meshlet& last = meshlets[meshlet_count - 1]; meshlet_vertices.resize(last.vertex_offset + last.vertex_count); meshlet_triangles.resize(last.triangle_offset + last.triangle_count * 3); meshlets.resize(meshlet_count); ``` Depending on the application, other strategies of storing the data can be useful; for example, `meshlet_vertices` serves as indices into the original vertex buffer but it might be worthwhile to generate a mini vertex buffer for each meshlet to remove the extra indirection when accessing vertex data, or it might be desirable to compress vertex data as vertices in each meshlet are likely to be very spatially coherent. For optimal performance, it is recommended to further optimize each meshlet in isolation for better triangle and vertex locality by calling `meshopt_optimizeMeshlet` on vertex and index data like so: ```c++ meshopt_optimizeMeshlet(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], m.triangle_count, m.vertex_count); ``` Different applications will choose different strategies for rendering meshlets; on a GPU capable of mesh shading, meshlets can be rendered directly; for example, a basic GLSL shader for `VK_EXT_mesh_shader` extension could look like this (parts omitted for brevity): ```glsl layout(binding = 0) readonly buffer Meshlets { Meshlet meshlets[]; }; layout(binding = 1) readonly buffer MeshletVertices { uint meshlet_vertices[]; }; layout(binding = 2) readonly buffer MeshletTriangles { uint8_t meshlet_triangles[]; }; void main() { Meshlet meshlet = meshlets[gl_WorkGroupID.x]; SetMeshOutputsEXT(meshlet.vertex_count, meshlet.triangle_count); for (uint i = gl_LocalInvocationIndex; i < meshlet.vertex_count; i += gl_WorkGroupSize.x) { uint index = meshlet_vertices[meshlet.vertex_offset + i]; gl_MeshVerticesEXT[i].gl_Position = world_view_projection * vec4(vertex_positions[index], 1); } for (uint i = gl_LocalInvocationIndex; i < meshlet.triangle_count; i += gl_WorkGroupSize.x) { uint offset = meshlet.triangle_offset + i * 3; gl_PrimitiveTriangleIndicesEXT[i] = uvec3( meshlet_triangles[offset], meshlet_triangles[offset + 1], meshlet_triangles[offset + 2]); } } ``` > Note that DirectX 12 mesh shaders cannot index raw buffers using arbitrary byte offsets. Use a typed SRV buffer (`Buffer`) with `DXGI_FORMAT_R8_UINT` format, repack each triangle to 32 bits to be able to use aligned 32-bit loads with `ByteAddressBuffer`, or consider utilizing [16-bit scalar types](https://github.com/microsoft/DirectXShaderCompiler/wiki/16-Bit-Scalar-Types) to load a 3-byte triangle using two aligned 16-bit loads like so: `Buffer.Load(triangle_offset & ~1)`, then extracting indices with bitwise operations based on `triangle_offset & 1`. After generating the meshlet data, it's possible to generate extra data for each meshlet that can be saved and used at runtime to perform cluster culling, where each meshlet can be discarded if it's guaranteed to be invisible. To generate the data, `meshopt_computeMeshletBounds` can be used: ```c++ meshopt_Bounds bounds = meshopt_computeMeshletBounds(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], m.triangle_count, &vertices[0].x, vertices.size(), sizeof(Vertex)); ``` The resulting `bounds` values can be used to perform frustum or occlusion culling using the bounding sphere, or cone culling using the cone axis/angle (which will reject the entire meshlet if all triangles are guaranteed to be back-facing from the camera point of view): ```c++ if (dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff) reject(); ``` Cluster culling should ideally run at a lower frequency than mesh shading, either using amplification/task shaders, or using a separate compute dispatch. By default, the meshlet builder tries to form complete meshlets even if that requires merging disconnected regions of the mesh into a single meshlet. In some cases, such as hierarchical level of detail, or when advanced culling is used, it may be beneficial to prioritize spatial locality of triangles in a meshlet even if that results in partially filled meshlets. To that end, `meshopt_buildMeshletsFlex` function can be used instead of `meshopt_buildMeshlets`; it provides two triangle limits, `min_triangles` and `max_triangles`, and uses an additional configuration parameter, `split_factor` (recommended value is 2.0), to decide whether increasing the meshlet radius is worth it to fit more triangles in the meshlet. When using this function, the worst case bound for the number of meshlets has to be computed using `meshopt_buildMeshletsBound` with `min_triangles` parameter instead of `max_triangles`. ### Clustered raytracing In addition to rasterization, meshlets can also be used for ray tracing. NVidia GPUs starting from Turing with recent drivers provide support for cluster acceleration structures (via `VK_NV_cluster_acceleration_structure` extension / NVAPI); instead of building a traditional BLAS, a cluster acceleration structure can be built for each meshlet and combined into a single clustered BLAS. While this currently results in reduced ray tracing performance for static geometry (for which a traditional BLAS may be more suitable), it allows updating the individual clusters without having to rebuild or refit the entire BLAS, which can be useful for mesh deformation or hierarchical level of detail. When using meshlets for raytracing, the performance characteristics that matter differ from when rendering meshes with rasterization. For raytracing, clusters with optimal spatial division that minimize ray-triangle intersection tests are preferred, while for rasterization, clusters with maximum triangle count within vertex limits are ideal. To generate meshlets optimized for raytracing, this library provides `meshopt_buildMeshletsSpatial` algorithm, which builds clusters using surface area heuristic (SAH) to produce raytracing-friendly cluster distributions: ```c++ const size_t max_vertices = 64; const size_t min_triangles = 16; const size_t max_triangles = 64; const float fill_weight = 0.5f; size_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, min_triangles); // note: use min_triangles to compute worst case bound std::vector meshlets(max_meshlets); std::vector meshlet_vertices(indices.size()); std::vector meshlet_triangles(indices.size()); // note: in v0.25 or prior, use indices.size() + max_meshlets * 3 size_t meshlet_count = meshopt_buildMeshletsSpatial(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices.data(), indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex), max_vertices, min_triangles, max_triangles, fill_weight); ``` The algorithm recursively subdivides the triangles into a BVH-like hierarchy using SAH for optimal spatial partitioning while balancing cluster size; this results in clusters that are significantly more efficient to raytrace compared to clusters generated by `meshopt_buildMeshlets`, but can still be used for rasterization (for example, to build visibility buffers or G-buffers). The `min_triangles` and `max_triangles` parameters control the allowed range of triangles per cluster. For optimal raytracing performance, `min_triangles` should be at most `max_triangles/2` (or, ideally, `max_triangles/4`) to give the algorithm enough freedom to produce high-quality spatial partitioning. For meshes with few seams due to normal or UV discontinuities, using `max_vertices` equal to `max_triangles` is recommended when rasterization performance is a concern; for meshes with many seams or for renderers that primarily use meshlets for ray tracing, a higher `max_vertices` value should be used as it ensures that more clusters can fully utilize the triangle limit. The `fill_weight` parameter (typically between 0 and 1, although values higher than 1 could be used to prioritize cluster fill even more) controls the trade-off between pure SAH optimization and triangle utilization. A value of 0 will optimize purely for SAH, resulting in best raytracing performance but potentially smaller clusters. Values between 0.25 and 0.75 typically provide a good balance of SAH quality vs triangle count. When the resulting meshlets are used to generate hardware-specific acceleration structures, using fast trace (e.g. `VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR`) builds results in maximum performance; if build performance is important, using `meshopt_optimizeMeshlet` can help improve ray tracing performance when using fast build (e.g. `VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR`), although the tracing performance will still be lower than with fast trace builds. ### Point cloud clusterization Both of the meshlet algorithms are designed to work with triangle meshes. In some cases, splitting a point cloud into fixed size clusters can be useful; the resulting point clusters could be rendered via mesh or compute shaders, or the resulting subdivision can be used to parallelize point processing while maintaining locality of points. To that end, this library provides `meshopt_spatialClusterPoints` algorithm: ```c++ const size_t cluster_size = 256; std::vector index(mesh.vertices.size()); meshopt_spatialClusterPoints(&index[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), cluster_size); ``` The resulting index buffer could be used to process the points directly, or reorganize the point data into flat contiguous arrays. Every consecutive chunk of `cluster_size` points in the index buffer refers to a single cluster, with just the last cluster containing fewer points if the total number of points is not a multiple of `cluster_size`. Note that the index buffer is not a remap table, so `meshopt_remapVertexBuffer` can't be used to flatten the point data. ### Cluster partitioning When working with clustered geometry, it can be beneficial to organize clusters into larger groups (partitions) for more efficient processing or workload distribution. This library provides an algorithm to partition clusters into groups of similar size while prioritizing locality: ```c++ const size_t partition_size = 24; std::vector cluster_partitions(cluster_count); size_t partition_count = meshopt_partitionClusters(&cluster_partitions[0], &cluster_indices[0], total_index_count, &cluster_index_counts[0], cluster_count, &vertices[0].x, vertex_count, sizeof(Vertex), partition_size); ``` The algorithm assigns each cluster to a partition, aiming for a target partition size while prioritizing topological locality (sharing vertices) and spatial locality. The resulting partitions can be used for more efficient batched processing of clusters, or for hierarchical simplification schemes similar to Nanite. Two clusters are considered topologically adjacent if they reference the same indices. In some cases, it can be helpful to process the indices using `meshopt_generateShadowIndexBuffer` (or remap them manually using the remap table generated by `meshopt_generatePositionRemap`), which allows clusters to be considered adjacent even when boundary vertices have different indices due to attribute discontinuities. If vertex positions are specified (not `NULL`), spatial locality will influence priority of merging clusters; otherwise, the algorithm will rely solely on topological connections and will not merge disconnected clusters into the same partition, which may result in smaller partitions for some inputs. After partitioning, each element in the destination array contains the partition ID (ranging from 0 to the returned partition count minus 1) for the corresponding cluster. Note that the partitions may be both smaller and larger than the target size; given a target size, the maximum partition size returned currently is `target + target / 3`. ## Mesh compression In case storage size or transmission bandwidth is of importance, you might want to additionally compress vertex and index data. While several mesh compression libraries, like Google Draco, are available, they typically are designed to maximize the compression ratio at the cost of disturbing the vertex/index order (which makes the meshes inefficient to render on GPU) or decompression performance. They also frequently don't support custom game-ready quantized vertex formats and thus require to re-quantize the data after loading it, introducing extra quantization errors and making decoding slower. Alternatively you can use general purpose compression libraries like zstd or Oodle to compress vertex/index data - however these compressors aren't designed to exploit redundancies in vertex/index data and as such compression rates can be unsatisfactory. To that end, this library provides algorithms to "encode" vertex and index data. The result of the encoding is generally significantly smaller than initial data, and remains compressible with general purpose compressors - so you can either store encoded data directly (for modest compression ratios and maximum decoding performance), or further compress it with LZ4/zstd/Oodle to maximize compression ratio. > Note: this compression scheme is available as a glTF extension [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md) as well as [KHR_meshopt_compression](https://github.com/KhronosGroup/glTF/pull/2517). ### Vertex compression This library provides a lossless algorithm to encode/decode vertex data. To encode vertices, you need to allocate a target buffer (using the worst case bound) and call the encoding function: ```c++ std::vector vbuf(meshopt_encodeVertexBufferBound(vertex_count, sizeof(Vertex))); vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), vertices, vertex_count, sizeof(Vertex))); ``` To decode the data at runtime, call the decoding function: ```c++ int res = meshopt_decodeVertexBuffer(vertices, vertex_count, sizeof(Vertex), &vbuf[0], vbuf.size()); assert(res == 0); ``` Note that vertex encoding assumes that vertex buffer was optimized for vertex fetch, and that vertices are quantized. Feeding unoptimized data into the encoder may result in poor compression ratios. The codec is lossless by itself - the only lossy step is quantization/reordering or filters that you may apply before encoding. Additionally, if the vertex data contains padding bytes, they should be zero-initialized to ensure that the encoder does not need to store uninitialized data. Decoder is heavily optimized and can directly target write-combined memory; you can expect it to run at 3-6 GB/s on modern desktop CPUs. Compression ratio depends on the data; vertex data compression ratio is typically around 2-4x (compared to already quantized and optimally packed data). General purpose lossless compressors can further improve the compression ratio at some cost to decoding performance. The vertex codec tries to take advantage of the inherent locality of sequential vertices and identify bit patterns that repeat in consecutive vertices. Typically, vertex cache + vertex fetch provides a reasonably local vertex traversal order; without an index buffer, it is recommended to sort vertices spatially (via `meshopt_spatialSortRemap`) to improve the compression ratio. It is crucial to correctly specify the stride when encoding vertex data; however, for compression ratio it does not matter whether the vertices are interleaved or deinterleaved, as the codecs perform full byte deinterleaving internally. The stride of each stream must be a multiple of 4 bytes. For optimal compression results, the values should be quantized to small integers. It can be valuable to use bit counts that are not multiples of 8. For example, instead of using 16 bits to represent texture coordinates, use 12-bit integers and divide by 4095 in the shader. Alternatively, using half-precision floats can often achieve good results. For single-precision floating-point data, it's recommended to use `meshopt_quantizeFloat` to remove entropy from the lower bits of the mantissa; for best results, consider using 15 bits or 7 bits for extreme compression. For normal or tangent vectors, using octahedral encoding is recommended over three components as it reduces redundancy; similarly, consider using 10-12 bits per component instead of 16. When data is bit packed, specifying compression level 3 (via `meshopt_encodeVertexBufferLevel`) can improve the compression further by redistributing bits between components. ### Index compression This library also provides algorithms to encode/decode index data. To encode triangle indices, you need to allocate a target buffer (using the worst case bound) and call the encoding function: ```c++ std::vector ibuf(meshopt_encodeIndexBufferBound(index_count, vertex_count)); ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), indices, index_count)); ``` To decode the data at runtime, call the decoding function: ```c++ int res = meshopt_decodeIndexBuffer(indices, index_count, &ibuf[0], ibuf.size()); assert(res == 0); ``` Note that index encoding assumes that the index buffer was optimized for vertex cache and vertex fetch. Feeding unoptimized data into the encoder will result in poor compression ratios. Codec preserves the order of triangles, however it can rotate each triangle to improve compression ratio (which means the provoking vertex may change). Decoder is heavily optimized and can directly target write-combined memory; you can expect it to run at 3-6 GB/s on modern desktop CPUs. The index codec targets 1 byte per triangle as a best case (6x smaller than raw 16-bit index data); on real-world meshes, it's typical to achieve 1-1.2 bytes per triangle. To reach this, the index data needs to be optimized for vertex cache and vertex fetch. Optimizations that do not disrupt triangle locality (such as overdraw) are safe to use in between. To reduce the data size further, it's possible to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller index (and sometimes vertex) data. When referenced vertex indices are not sequential, the index codec will use around 2 bytes per index. This can happen when the referenced vertices are a sparse subset of the vertex buffer, such as when encoding LODs. General-purpose compression can be especially helpful in this case. Index buffer codec only supports triangle list topology; when encoding triangle strips or line lists, use `meshopt_encodeIndexSequence`/`meshopt_decodeIndexSequence` instead. This codec typically encodes indices into ~1 byte per index, but compressing the results further with a general purpose compressor can improve the results to 1-3 bits per index. ### Meshlet compression When using mesh shading or clustered raytracing, meshlet vertex reference and triangle data can be compressed similarly to index data. This library provides a dedicated codec that exploits locality inherent in meshlet data. Unlike vertex and index buffer codecs that work on entire buffers, the meshlet codec encodes each meshlet independently; this allows applications to have more flexibility in structuring the runtime storage and adjust the decoded data during decoding. This also means that in some applications, additional data describing the meshlet (vertex/triangle count, encoded size) will need to be encoded into the meshlet stream, if it isn't already available during decoding. To encode a meshlet, you need to allocate a target buffer (using the worst case bound) and call the encoding function with the vertex index references and micro-index buffer, as produced by `meshopt_buildMeshlets`: ```c++ std::vector mbuf(meshopt_encodeMeshletBound(max_vertices, max_triangles)); for (const meshopt_Meshlet& m : meshlets) { size_t msize = meshopt_encodeMeshlet(&mbuf[0], mbuf.size(), &meshlet_vertices[m.vertex_offset], m.vertex_count, &meshlet_triangles[m.triangle_offset], m.triangle_count); // write m.vertex_count, m.triangle_count, msize and mbuf[0..msize-1] to the output stream } ``` To decode the data at runtime, call the decoding function: ```c++ uint16_t* vertices = ...; uint8_t* triangles = ...; // automatically deduces `vertex_size=2` and `triangle_size=3` based on pointer types int res = meshopt_decodeMeshlet(vertices, m.vertex_count, triangles, m.triangle_count, stream, encoded_size); assert(res == 0); ``` Vertex index references can be decoded as either 16-bit or 32-bit integers; triangle data can be decoded as 3 bytes per triangle (matching `meshopt_buildMeshlets` output format) or as a 32-bit integer per triangle (with indices packed as `a | (b << 8) | (c << 16)` and top byte unused). Output buffers must have available space aligned to 4 bytes; for example, decoding a 3-triangle stream using 3 bytes per triangle needs to be able to write 12 bytes to the output triangles array. When using the C++ API, `meshopt_decodeMeshlet` will automatically deduce the element sizes based on the types of vertex and triangle pointers; when using the C API, the sizes need to be specified explicitly. Decoder is heavily optimized and can directly target write-combined memory; you can expect it to run at 7-10 GB/s on modern desktop CPUs. > Applications that do most of the streaming decompression on the GPU can also decode meshlet data on the GPU if CPU decoding is inconvenient; an example [meshletdec.slang](./demo/meshletdec.slang) shader is provided for 32-bit output format, and can be easily adapted to other formats, including custom ones. Note that meshlet encoding assumes that the meshlet data was optimized; meshlets should be processed using `meshopt_optimizeMeshlet` before encoding. Additionally, vertex references should have a high degree of reference locality; this can be achieved by building meshlets from meshes optimized for vertex cache/fetch, or linearizing the vertex reference data (and reordering the vertex buffer accordingly). Feeding unoptimized data into the encoder will result in poor compression ratios. Codec preserves the order of triangles, however it can rotate each triangle to improve compression ratio (which means the provoking vertex may change). Meshlets without vertex references are supported; passing `NULL` vertices and `0` vertex count during encoding and decoding will produce encoded meshlets with just triangle data. Note that parameters supplied during decoding must match those used during encoding; if a meshlet was encoded with vertex references, it must be decoded with the same number of vertex references. The meshlet codec targets 5-7 bits per triangle for triangle data; when vertex references are encoded, the encoded size strongly depends on how linear the references are, but it's typical to see 9-12 bits per triangle in aggregate. To reduce the compressed size further, it's possible to compress the resulting encoded data with a general purpose compressor, which usually achieves 5-8 bits/triangle in aggregate; note that in this case general purpose compressors should be applied to a stream with many encoded meshlets at once to amortize their overhead. > Note: this codec is currently experimental and the data format and APIs are subject to change. ### Point cloud compression The vertex encoding algorithms can be used to compress arbitrary streams of attribute data; one other use case besides triangle meshes is point cloud data. Typically point clouds come with position, color and possibly other attributes but don't have an implied point order. To compress point clouds efficiently, it's recommended to first preprocess the points by sorting them using the spatial sort algorithm: ```c++ std::vector remap(point_count); meshopt_spatialSortRemap(&remap[0], positions, point_count, sizeof(vec3)); // for each attribute stream meshopt_remapVertexBuffer(positions, positions, point_count, sizeof(vec3), &remap[0]); ``` After this the resulting arrays should be quantized (e.g. using 16-bit fixed point numbers for positions and 8-bit color components), and the result can be compressed using `meshopt_encodeVertexBuffer` as described in the previous section. To decompress, `meshopt_decodeVertexBuffer` will recover the quantized data that can be used directly or converted back to original floating-point data. The compression ratio depends on the nature of source data, for colored points it's typical to get 35-40 bits per point. ### Vertex filters To further leverage the inherent structure of some vertex data, it's possible to use filters that encode and decode the data in a lossy manner. This is similar to quantization but can be used without having to change the shader code. After decoding, the filter transformation needs to be reversed. For native game engine pipelines, it is usually more optimal to carefully prequantize and pretransform the vertex data, but sometimes (for example when serializing data in glTF format) this is not a practical option and filters are more convenient. This library provides four filters: - Octahedral filter (`meshopt_encodeFilterOct`/`meshopt_decodeFilterOct`) encodes quantized (snorm) normal or tangent vectors using octahedral encoding. Any number of bits between 2 and 16 can be used with 4 bytes or 8 bytes per vector. - Quaternion filter (`meshopt_encodeFilterQuat`/`meshopt_decodeFilterQuat`) encodes quantized (snorm) quaternion vectors; this can be used to encode rotations or tangent frames. Any number of bits between 4 and 16 can be used with 8 bytes per vector. - Exponential filter (`meshopt_encodeFilterExp`/`meshopt_decodeFilterExp`) encodes single-precision floating-point vectors; this can be used to encode arbitrary floating-point data more efficiently. In addition to an arbitrary bit count (<= 24), the filter takes a "mode" parameter that allows specifying how the exponent sharing is performed to trade off compression ratio and quality: - `meshopt_EncodeExpSeparate` does not share exponents and results in the largest output - `meshopt_EncodeExpSharedVector` shares exponents between different components of the same vector - `meshopt_EncodeExpSharedComponent` shares exponents between the same component in different vectors - `meshopt_EncodeExpClamped` does not share exponents but clamps the exponent range to reduce exponent entropy - Color filter (`meshopt_encodeFilterColor`/`meshopt_decodeFilterColor`) encodes quantized (unorm) RGBA colors using YCoCg encoding. Any number of bits between 2 and 16 can be used with 4 bytes or 8 bytes per vector. Note that all filters are lossy and require the data to be deinterleaved with one attribute per stream; this facilitates efficient SIMD implementation of filter decoders, which decodes at 5-10 GB/s on modern desktop CPUs, allowing the overall decompression speed to be closer to that of the raw vertex codec. ### Versioning and compatibility The following guarantees on data compatibility are provided for point releases (*no* guarantees are given for development branch): - Data encoded with older versions of the library can always be decoded with newer versions; - Data encoded with newer versions of the library can be decoded with older versions, provided that encoding versions are set correctly; if binary stability of encoded data is important, use `meshopt_encodeVertexVersion` and `meshopt_encodeIndexVersion` to 'pin' the data versions (or `version` argument of `meshopt_encodeVertexBufferLevel`). By default, vertex data is encoded for format version 1 (compatible with meshoptimizer v0.23+), and index data is encoded for format version 1 (compatible with meshoptimizer v0.14+). When decoding the data, the decoder will automatically detect the version from the data header. ## Simplification All algorithms presented so far don't affect visual appearance at all, with the exception of quantization that has minimal controlled impact. However, fundamentally the most effective way to reduce the rendering or transmission cost of a mesh is to reduce the number of triangles in the mesh. ### Basic simplification This library provides a simplification algorithm, `meshopt_simplify`, that reduces the number of triangles in the mesh. Given a vertex and an index buffer, it generates a second index buffer that uses existing vertices in the vertex buffer. This index buffer can be used directly for rendering with the original vertex buffer (preferably after vertex cache optimization using `meshopt_optimizeVertexCache`), or a new compact vertex/index buffer can be generated using `meshopt_optimizeVertexFetch` that uses the optimal number and order of vertices. ```c++ float threshold = 0.2f; size_t target_index_count = size_t(index_count * threshold); float target_error = 1e-2f; std::vector lod(index_count); float lod_error = 0.f; lod.resize(meshopt_simplify(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), target_index_count, target_error, /* options= */ 0, &lod_error)); ``` Target error is an approximate measure of the deviation from the original mesh using distance normalized to `[0..1]` range (e.g. `1e-2f` means that simplifier will try to maintain the error to be below 1% of the mesh extents). Note that the simplifier attempts to produce the requested number of indices at minimal error, but because of topological restrictions and error limit it is not guaranteed to reach the target index count and can stop earlier. To disable the error limit, `target_error` can be set to `FLT_MAX`. This makes it more likely that the simplifier will reach the target index count, but it may produce a mesh that looks significantly different from the original, so using the resulting error to control viewing distance would be required. Conversely, setting `target_index_count` to 0 will simplify the input mesh as much as possible within the specified error limit; this can be useful for generating LODs that should look good at a given viewing distance. The algorithm follows the topology of the original mesh in an attempt to preserve attribute seams, borders and overall appearance. For meshes with inconsistent topology or many seams, such as faceted meshes, it can result in simplifier getting "stuck" and not being able to simplify the mesh fully. Therefore it's critical that identical vertices are "welded" together, that is, the input vertex buffer does not contain duplicates. Additionally, it may be worthwhile to weld the vertices without taking into account vertex attributes that aren't critical and can be rebuilt later, or use "permissive" mode described below. Alternatively, the library provides another simplification algorithm, `meshopt_simplifySloppy`, which doesn't follow the topology of the original mesh. This means that it doesn't preserve attribute seams or borders, but it can collapse internal details that are too small to matter because it can merge mesh features that are topologically disjoint but spatially close. In general, this algorithm produces meshes with worse geometric quality and poor attribute quality compared to `meshopt_simplify`. The algorithm can also return the resulting normalized deviation that can be used to choose the correct level of detail based on screen size or solid angle; the error can be converted to object space by multiplying by the scaling factor returned by `meshopt_simplifyScale`. For example, given a mesh with a precomputed LOD and a prescaled error, the screen-space normalized error can be computed and used for LOD selection: ```c++ // lod_factor can be 1 or can be adjusted for more or less aggressive LOD selection float d = max(0, distance(camera_position, mesh_center) - mesh_radius); float e = d * (tan(camera_fovy / 2) * 2 / screen_height); // 1px in mesh space bool lod_ok = e * lod_factor >= lod_error; ``` When a sequence of LOD meshes is generated that all use the original vertex buffer, care must be taken to order vertices optimally to not penalize mobile GPU architectures that are only capable of transforming a sequential vertex buffer range. It's recommended in this case to first optimize each LOD for vertex cache, then assemble all LODs in one large index buffer starting from the coarsest LOD (the one with fewest triangles), and call `meshopt_optimizeVertexFetch` on the final large index buffer. This will make sure that coarser LODs require a smaller vertex range and are efficient with respect to vertex fetch and transform. ### Attribute-aware simplification While `meshopt_simplify` is aware of attribute discontinuities by default (and infers them through the supplied index buffer) and tries to preserve them, it can be useful to provide information about attribute values. This allows the simplifier to take attribute error into account which can improve shading (by using vertex normals), texture deformation (by using texture coordinates), and may be necessary to preserve vertex colors when textures are not used in the first place. This can be done by using a variant of the simplification function that takes attribute values and weight factors, `meshopt_simplifyWithAttributes`: ```c++ const float nrm_weight = 0.5f; const float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight}; std::vector lod(index_count); float lod_error = 0.f; lod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL, target_index_count, target_error, /* options= */ 0, &lod_error)); ``` The attributes are passed as a separate buffer (in the example above it's a subset of the same vertex buffer) and should be stored as consecutive floats; attribute weights are used to control the importance of each attribute in the simplification process. For normalized attributes like normals and vertex colors, a weight around 1.0 is usually appropriate; internally, a change of `1/weight` in attribute value over a distance `d` is approximately equivalent to a change of `d` in position. Using higher weights may be appropriate to preserve attribute quality at the cost of position quality. If the attribute has a different scale (e.g. unnormalized vertex colors in [0..255] range), the weight should be divided by the scaling factor (1/255 in this example). Including texture coordinates in the attribute set is optional, as simplification generally preserves texture quality reasonably well by default; if included, a weight of around 10-100 is usually appropriate depending on the UV density. It's also possible to compute the weight automatically by setting it to the reciprocal average density of UVs, which can be computed as `1/sqrt(average UV area)` = `1/sqrt(sum(abs(uv area)) / triangle count)` over all triangles in the mesh, possibly scaled by a constant factor if necessary. Both the target error and the resulting error combine positional error and attribute error, so the error can be used to control the LOD while taking attribute quality into account, assuming carefully chosen weights. ### Permissive simplification By default, `meshopt_simplify` preserves attribute discontinuities inferred from the supplied index buffer. For meshes with many seams, the simplifier can get "stuck" and fail to fully simplify the mesh, as it cannot collapse vertices across attribute seams. This is especially problematic for meshes with faceted normals (flat shading), as the simplifier may not be able to reduce the triangle count at all. The `meshopt_SimplifyPermissive` option relaxes these restrictions, allowing the simplifier to collapse vertices across attribute discontinuities when the resulting error is acceptable: ```c++ std::vector lod(index_count); float lod_error = 0.f; lod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL, target_index_count, target_error, /* options= */ meshopt_SimplifyPermissive, &lod_error)); ``` To maintain appearance, it's highly recommended to use this option together with attribute-aware simplification, as shown above, as it allows the simplifier to maintain attribute quality. In this mode, it is often desirable to selectively preserve certain attribute seams, such as UV seams or sharp creases. This can be achieved by using the `vertex_lock` array with flag `meshopt_SimplifyVertex_Protect` set for individual vertices to protect specific discontinuities. To fill this array, use `meshopt_generatePositionRemap` to create a mapping table for vertices with identical positions, and then compare each vertex to the remapped vertex to determine which attributes are different: ```c++ std::vector remap(vertices.size()); meshopt_generatePositionRemap(&remap[0], &vertices[0].px, vertices.size(), sizeof(Vertex)); std::vector locks(vertices.size()); for (size_t i = 0; i < vertices.size(); ++i) { unsigned int r = remap[i]; if (r != i && (vertices[r].tx != vertices[i].tx || vertices[r].ty != vertices[i].ty)) locks[i] |= meshopt_SimplifyVertex_Protect; // protect UV seams } ``` This approach provides fine-grained control over which discontinuities to preserve. The permissive mode combined with selective locking provides a balance between simplification quality and attribute preservation, and usually results in higher quality LODs for the same target triangle count (and dramatically higher quality compared to `meshopt_simplifySloppy`). > Note: this functionality is currently experimental and is subject to future improvements. Certain collapses are restricted to protect the overall topology, and attribute quality may occasionally regress. ### Simplification with vertex update All simplification functions described so far reuse the original vertex buffer and only produce a new index buffer. This means that the resulting mesh will have the same vertex positions and attributes as the original mesh; this is optimal for minimizing the memory consumption and for highly detailed meshes often provides good quality. However, for more aggressive simplification to retain visual quality, it may be necessary to adjust vertex data for optimal appearance. This can be done by using a variant of the simplification function that updates vertex positions and attributes, `meshopt_simplifyWithUpdate`: ```c++ indices.resize(meshopt_simplifyWithUpdate(&indices[0], indices.size(), &vertices[0].px, vertices.size(), sizeof(Vertex), &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL, target_index_count, target_error, /* options= */ 0, &result_error)); ``` Unlike `meshopt_simplify`/`meshopt_simplifyWithAttributes`, this function updates the index buffer as well as vertex positions and attributes in place. The resulting indices still refer to the original vertex buffer; any attributes that are not passed to the simplifier can be left unchanged. However, since the original contents of `vertices` is no longer valid for rendering the original mesh, a new compact vertex/index buffer should be generated using `meshopt_optimizeVertexFetch` (after optimizing the index data with `meshopt_optimizeVertexCache`). If the original data was important, it should be copied before calling this function. Since the vertex positions are updated, this may require updating some attributes that could previously be left as-is when using the original vertex buffer. Notably, texture coordinates need to be updated to avoid texture distortion; thus it's highly recommended to include texture coordinates in the attribute data passed to the simplifier. For attributes to be updated, the corresponding attribute weight must not be zero; for texture coordinates, a weight of 1.0 is usually sufficient in this case (although a higher or mesh dependent weight could be used with this function or other functions to reduce UV stretching). Attributes that have specific constraints like normals and colors should be renormalized or clamped after the function returns new data. Attributes like bone indices/weights don't have to be updated for reasonable results (but regularization via `meshopt_SimplifyRegularize` may still be helpful to maintain deformation quality). If bone weights *are* provided as attributes, they will need to be clamped and renormalized after the update to ensure they continue to add up to 1. Using unique vertex data for each LOD in a chain can improve visual quality, but it comes at a cost of ~doubling vertex memory used (if each LOD is using half the triangles of the previous LOD). To reduce the memory footprint, it is possible to use shared vertices with `meshopt_simplifyWithAttributes` for the first one or two LODs in the chain, and only switch to `meshopt_simplifyWithUpdate` for the remainder. In that case, similarly to the use of `meshopt_simplify` described earlier, care must be taken to optimally arrange the vertices in the original vertex buffer. ### Advanced simplification `meshopt_simplify*` functions expose additional options and parameters that can be used to control the simplification process in more detail. For basic customization, a number of options can be passed via `options` bitmask that adjust the behavior of the simplifier: - `meshopt_SimplifyLockBorder` restricts the simplifier from collapsing edges that are on the border of the mesh. This can be useful for simplifying mesh subsets independently, so that the LODs can be combined without introducing cracks. - `meshopt_SimplifyErrorAbsolute` changes the error metric from relative to absolute both for the input error limit as well as for the resulting error. This can be used instead of `meshopt_simplifyScale`. - `meshopt_SimplifySparse` improves simplification performance assuming input indices are a sparse subset of the mesh. This can be useful when simplifying small mesh subsets independently, and is intended to be used for meshlet simplification. For consistency, it is recommended to use absolute errors when sparse simplification is desired, as this flag changes the meaning of the relative errors. - `meshopt_SimplifyPrune` allows the simplifier to remove isolated components regardless of the topological restrictions inside the component. This is generally recommended for full-mesh simplification as it can improve quality and reduce triangle count; note that with this option, triangles connected to locked vertices may be removed as part of their component. - `meshopt_SimplifyRegularize` produces more regular triangle sizes and shapes during simplification, at some cost to geometric quality. This can improve geometric quality under deformation such as skinning. - `meshopt_SimplifyPermissive` allows collapses across attribute discontinuities, except for vertices that are tagged with `meshopt_SimplifyVertex_Protect` via `vertex_lock`. When using `meshopt_simplifyWithAttributes`, it is also possible to lock certain vertices by providing a `vertex_lock` array that contains a value for each vertex in the mesh, with `meshopt_SimplifyVertex_Lock` set for vertices that should not be collapsed. This can be useful to preserve certain vertices, such as the boundary of the mesh, with more control than `meshopt_SimplifyLockBorder` option provides. When using `meshopt_simplifyWithUpdate`, locking vertices (whether via `vertex_lock` or `meshopt_SimplifyLockBorder`) will also prevent the simplifier from updating their positions and attributes; this can be useful together with `meshopt_SimplifySparse` for meshlet simplification, as meshlets at one level of hierarchy can be simplified together without excessive data copying. In addition to the `meshopt_SimplifyPrune` flag, you can explicitly prune isolated components by calling the `meshopt_simplifyPrune` function. This can be done before regular simplification or as the only step, which is useful for scenarios like isosurface cleanup. Similar to other simplification functions, the `target_error` argument controls the cutoff of component radius and is specified in relative units (e.g., `1e-2f` will remove components under 1%). If an absolute cutoff is desired, divide the parameter by the factor returned by `meshopt_simplifyScale`. Simplification currently assumes that the input mesh is using the same material for all triangles. If the mesh uses multiple materials, it is possible to split the mesh into subsets based on the material and simplify each subset independently, using `meshopt_SimplifyLockBorder` or `vertex_lock` to preserve material boundaries; however, this limits the collapses and may reduce the resulting quality. An alternative approach is to encode information about the material into the vertex buffer, ensuring that all three vertices referencing the same triangle have the same material ID; this may require duplicating vertices on the boundary between materials. After this, simplification can be performed as usual, and after simplification per-triangle material information can be computed from the vertex material IDs. There is no need to inform the simplifier of the value of the material ID: the implicit boundaries created by duplicating vertices with conflicting material IDs will be preserved automatically (unless permissive simplification is used, in which case material boundaries should be protected via `vertex_lock`). If the source mesh is already split into subsets with non-overlapping vertex indices, and permissive simplification is not used, it should be sufficient to concatenate the subsets into a single vertex/index buffer and simplify the entire mesh at once; the result can be split back into subsets after simplification. When generating a LOD chain, you can either re-simplify each LOD from the original mesh or use the previous LOD as the starting point for the next level. The latter approach is more efficient and produces smoother visual transitions between LOD levels while preserving mesh attributes better. With this method, resulting error values from previous levels should be accumulated for LOD selection. Additionally, consider using `meshopt_SimplifySparse` to improve performance when generating deep LOD chains. ### Point cloud simplification In addition to triangle mesh simplification, this library provides a function to simplify point clouds. The algorithm reduces the point cloud to a specified number of points while preserving the overall appearance, and can optionally take per-point colors into account: ```c++ const float color_weight = 1; std::vector indices(target_count); indices.resize(meshopt_simplifyPoints(&indices[0], &points[0].x, points.size(), sizeof(Point), &points[0].r, sizeof(Point), color_weight, target_count)); ``` The resulting indices can be used to render the simplified point cloud; to reduce the memory footprint, the point cloud can be reindexed to create an array of points from the indices. ## Efficiency analyzers While the only way to get precise performance data is to measure performance on the target GPU, it can be valuable to measure the impact of these optimization in a GPU-independent manner. To this end, the library provides analyzers for all three major optimization routines. For each optimization there is a corresponding analyze function, like `meshopt_analyzeOverdraw`, that returns a struct with statistics. `meshopt_analyzeVertexCache` returns vertex cache statistics. The common metric to use is ACMR - average cache miss ratio, which is the ratio of the total number of vertex invocations to the triangle count. The worst-case ACMR is 3 (GPU has to process 3 vertices for each triangle); on regular grids the optimal ACMR approaches 0.5. On real meshes it usually is in [0.5..1.5] range depending on the amount of vertex splits. One other useful metric is ATVR - average transformed vertex ratio - which represents the ratio of vertex shader invocations to the total vertices, and has the best case of 1.0 regardless of mesh topology (each vertex is transformed once). `meshopt_analyzeVertexFetch` returns vertex fetch statistics. The main metric it uses is overfetch - the ratio between the number of bytes read from the vertex buffer to the total number of bytes in the vertex buffer. Assuming non-redundant vertex buffers, the best case is 1.0 - each byte is fetched once. `meshopt_analyzeOverdraw` returns overdraw statistics. The main metric it uses is overdraw - the ratio between the number of pixel shader invocations to the total number of covered pixels, as measured from several different orthographic cameras. The best case for overdraw is 1.0 - each pixel is shaded once. `meshopt_analyzeCoverage` returns coverage statistics: the ratio of covered pixels to the viewport extent from each cardinal axis. This is not an efficiency measure per se, but it can be used to measure silhouette change after simplification as well as more precise distance based culling, where the amount of view dependent coverage can be estimated by computing a dot product between the view direction and the coverage vector. Note that all analyzers use approximate models for the relevant GPU units, so the numbers you will get as the result are only a rough approximation of the actual performance. ## Deinterleaved geometry All of the examples above assume that geometry is represented as a single vertex buffer and a single index buffer. This requires storing all vertex attributes - position, normal, texture coordinate, skinning weights etc. - in a single contiguous struct. However, in some cases using multiple vertex streams may be preferable. In particular, if some passes require only positional data - such as depth pre-pass or shadow map - then it may be beneficial to split it from the rest of the vertex attributes to make sure the bandwidth use during these passes is optimal. On some mobile GPUs a position-only attribute stream also improves efficiency of tiling algorithms. Most of the functions in this library either only need the index buffer (such as vertex cache optimization) or only need positional information (such as overdraw optimization). However, several tasks require knowledge about all vertex attributes. For indexing, `meshopt_generateVertexRemap` assumes that there's just one vertex stream; when multiple vertex streams are used, it's necessary to use `meshopt_generateVertexRemapMulti` as follows: ```c++ meshopt_Stream streams[] = { {&unindexed_pos[0], sizeof(float) * 3, sizeof(float) * 3}, {&unindexed_nrm[0], sizeof(float) * 3, sizeof(float) * 3}, {&unindexed_uv[0], sizeof(float) * 2, sizeof(float) * 2}, }; std::vector remap(index_count); size_t vertex_count = meshopt_generateVertexRemapMulti(&remap[0], NULL, index_count, index_count, streams, sizeof(streams) / sizeof(streams[0])); ``` After this `meshopt_remapVertexBuffer` needs to be called once for each vertex stream to produce the correctly reindexed stream. For shadow indexing, similarly `meshopt_generateShadowIndexBufferMulti` is available as a replacement. Instead of calling `meshopt_optimizeVertexFetch` for reordering vertices in a single vertex buffer for efficiency, calling `meshopt_optimizeVertexFetchRemap` and then calling `meshopt_remapVertexBuffer` for each stream again is recommended. Finally, when compressing vertex data, `meshopt_encodeVertexBuffer` should be used on each vertex stream separately - this allows the encoder to best utilize correlation between attribute values for different vertices. ## Specialized processing In addition to the core optimization techniques, the library provides several specialized algorithms for specific rendering techniques and pipeline optimizations that require a particular configuration of vertex and index data. ### Triangle strip conversion On most hardware, indexed triangle lists are the most efficient way to drive the GPU. However, in some cases triangle strips might prove beneficial: - On some older GPUs, triangle strips may be a bit more efficient to render - On extremely memory constrained systems, index buffers for triangle strips could save a bit of memory This library provides an algorithm for converting a vertex cache optimized triangle list to a triangle strip: ```c++ std::vector strip(meshopt_stripifyBound(index_count)); unsigned int restart_index = ~0u; size_t strip_size = meshopt_stripify(&strip[0], indices, index_count, vertex_count, restart_index); ``` Typically you should expect triangle strips to have ~50-60% of indices compared to triangle lists (~1.5-1.8 indices per triangle) and have ~5% worse ACMR. Note that triangle strips can be stitched with or without restart index support. Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance. To reduce the triangle strip size further, it's recommended to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller index buffers. ### Geometry shader adjacency For algorithms that use geometry shaders and require adjacency information, this library can generate an index buffer with adjacency data: ```c++ std::vector adjacency(indices.size() * 2); meshopt_generateAdjacencyIndexBuffer(&adjacency[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex)); ``` This creates an index buffer suitable for rendering with triangle-with-adjacency topology, providing 3 extra vertices per triangle that represent vertices opposite to each triangle's edge. This data can be used to compute silhouettes and perform other types of local geometric processing in geometry shaders. To render the mesh with adjacency data, the index buffer should be used with `D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ`/`VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY`/`GL_TRIANGLES_ADJACENCY` topology. Note that the use of geometry shaders may have a performance impact on some GPUs; in some cases alternative implementation strategies may be more efficient. ### Tessellation with displacement mapping For hardware tessellation with crack-free displacement mapping, this library can generate a special index buffer that supports PN-AEN tessellation: ```c++ std::vector tess(indices.size() * 4); meshopt_generateTessellationIndexBuffer(&tess[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex)); ``` This generates a 12-vertex patch for each input triangle with the following layout: - 0, 1, 2: original triangle vertices - 3, 4: opposing edge for edge 0, 1 - 5, 6: opposing edge for edge 1, 2 - 7, 8: opposing edge for edge 2, 0 - 9, 10, 11: dominant vertices for corners 0, 1, 2 This allows the use of hardware tessellation to implement PN-AEN and/or displacement mapping without cracks along UV seams or normal discontinuities. To render the mesh, the index buffer should be used with `D3D_PRIMITIVE_TOPOLOGY_12_CONTROL_POINT_PATCHLIST`/`VK_PRIMITIVE_TOPOLOGY_PATCH_LIST` (`patchControlPoints=12`) topology. For more details please refer to the following papers: [Crack-Free Point-Normal Triangles using Adjacent Edge Normals](https://developer.download.nvidia.com/whitepapers/2010/PN-AEN-Triangles-Whitepaper.pdf), [Tessellation on Any Budget](https://www.nvidia.com/content/pdf/gdc2011/john_mcdonald.pdf) and [My Tessellation Has Cracks!](https://developer.download.nvidia.com/assets/gamedev/files/gdc12/GDC12_DUDASH_MyTessellationHasCracks.pdf). ### Visibility buffers To render geometry into visibility buffers, access to primitive index in fragment shader is required. While it is possible to use `SV_PrimitiveID`/`gl_PrimitiveID` in the fragment shader, this can result in suboptimal performance on some GPUs (notably, AMD RDNA1 and all NVidia GPUs), and may not be supported on mobile or console hardware. Using mesh shaders to generate primitive IDs is efficient but requires hardware support that is not universally available. To work around these limitations, this library provides a way to generate a special index buffer that uses provoking vertex to encode primitive IDs: ```c++ std::vector provoke(indices.size()); std::vector reorder(vertices.size() + indices.size() / 3); reorder.resize(meshopt_generateProvokingIndexBuffer(&provoke[0], &reorder[0], &indices[0], indices.size(), vertices.size())); ``` This generates a special index buffer along with a reorder table that satisfies two constraints: - `provoke[3 * tri] == tri` - `reorder[provoke[x]]` refers to the original triangle vertices To render the mesh with provoking vertex data, the application should use `provoke` as an index buffer and a vertex shader that passes vertex index (`SV_VertexID`/`gl_VertexIndex`) via a `flat`/`nointerpolation` attribute to the fragment shader as a primitive index, and loads vertex data manually by computing the real vertex index based on `reorder` table (`reorder[gl_VertexIndex]`). For more details please refer to [Variable Rate Shading with Visibility Buffer Rendering](https://advances.realtimerendering.com/s2024/content/Hable/Advances_SIGGRAPH_2024_VisibilityVRS-SIGGRAPH_Advances_2024.pptx); naturally, this technique does not require VRS. > Note: This assumes the provoking vertex is the first vertex of a triangle, which is true for all graphics APIs except OpenGL/WebGL. For OpenGL/WebGL, you may need to rotate each triangle (abc -> bca) in the resulting index buffer, or use the `glProvokingVertex` function (OpenGL 3.2+) or `WEBGL_provoking_vertex` extension (WebGL2) to change the provoking vertex convention. For WebGL2, this is highly recommended to avoid a variety of emulation slowdowns that happen by default if `flat` attributes are used, such as an implicit use of geometry shaders. Because the order of indices in the resulting index buffer must be preserved exactly for the technique to work, all optimizations that reorder indices (such as vertex cache optimization) must be applied before generating the provoking index buffer. Additionally, if index compression is used, `meshopt_encodeIndexSequence` should be used instead of `meshopt_encodeIndexBuffer` to ensure that the triangles are not rotated during encoding. ### Opacity micromaps When using hardware raytracing with alpha-tested transparency, tracing the ray requires invoking any-hit shaders for each intersected surface; this can be inefficient as it requires extra communication and synchronization between raytracing hardware and shader units. Opacity micromaps (OMMs) can significantly accelerate the tests by providing opacity masks for each triangle; this requires subdividing each triangle using a uniform grid (4^N microtriangles for subdivision level N), with each microtriangle storing 1 bit (for 2-state micromaps) or 2 bits (for 4-state micromaps) of opacity data. To minimize the memory overhead, the maps can be reused between triangles using a per-triangle OMM index buffer. This library provides algorithms to generate OMM data for a given mesh from an alpha texture; the resulting data can be used directly in Vulkan via [VK_EXT_opacity_micromap](https://docs.vulkan.org/spec/latest/chapters/raytraversal.html#ray-opacity-micromap) or in DirectX via [DXR1.2](https://github.com/microsoft/DirectX-Specs/blob/master/d3d/Raytracing.md#opacity-micromaps). Generating opacity micromaps happens in three stages: measure (and layout), rasterize and compact. Compaction is optional but recommended, and can be performed on each mesh individually or on all meshes in the same model/scene. First, call `meshopt_opacityMapMeasure` to compute a subdivision level for each triangle based on its texel footprint; this also computes initial per-triangle OMM indices as it's common for triangles in the source mesh to refer to the same UVs: ```c++ const int states = 4; // 2-state or 4-state OMMs (used after measure) const int max_level = 6; // max subdivision level const float target_edge = 3.0f; // target 3x3px area for each microtriangle std::vector levels(indices.size() / 3); std::vector sources(indices.size() / 3); std::vector omm_indices(indices.size() / 3); size_t omm_count = meshopt_opacityMapMeasure(&levels[0], &sources[0], &omm_indices[0], &indices[0], indices.size(), &vertices[0].u, vertices.size(), sizeof(Vertex), texture_width, texture_height, max_level, target_edge); ``` Each OMM entry requires separate storage which can be determined based on subdivision level and format (2-state or 4-state), and can be computed via `meshopt_opacityMapEntrySize`: ```c++ std::vector offsets(omm_count); size_t data_size = 0; for (size_t i = 0; i < omm_count; ++i) { offsets[i] = unsigned(data_size); data_size += meshopt_opacityMapEntrySize(levels[i], states); } ``` Second, call `meshopt_opacityMapRasterize` for each triangle to compute the opacity state per microtriangle. This can be done sequentially or in parallel; it can use the original texture resolution or a smaller mip level to balance rasterization cost vs quality. When generating 4-state micromaps, using mip 0 is recommended to produce maximally conservative output so that enabling opacity micromaps does not noticeably change the raytraced output. For 2-state micromaps, or if the original textures are much higher resolution than the micromap subdivision, smaller mips (e.g. 1 or 2) can also work well. ```c++ for (size_t i = 0; i < omm_count; ++i) { unsigned int tri = sources[i]; const float* uv0 = &vertices[indices[tri * 3 + 0]].u; const float* uv1 = &vertices[indices[tri * 3 + 1]].u; const float* uv2 = &vertices[indices[tri * 3 + 2]].u; // texture addressing below assumes RGBA texture input without padding; +3 points to A meshopt_opacityMapRasterize(&data[offsets[i]], levels[i], states, uv0, uv1, uv2, texture.data() + 3, 4, texture_width * 4, texture_width, texture_height); } ``` > Note: Opacity micromap data is sensitive to triangle corner order. If index or meshlet compression is used, to match runtime order the index data should be decompressed from the compressed representation first. Since per-triangle OMM indices use the original triangle order, it's recommended to perform OMM processing after the index order has been finalized. After rasterization, the OMM data *can* be used as is; however, it's typical to see redundant entries that either can be reused between different triangles, or that have consistent states for all micro-triangles, which can be represented using "special" indices (-4..-1) per triangle. Thus it's recommended to compact the data - if it's already laid out sequentially similarly to the example above, then just calling `meshopt_opacityMapCompact` and trimming the output arrays is sufficient for optimal output: ```c++ omm_count = meshopt_opacityMapCompact(&data[0], data_size, &levels[0], &offsets[0], omm_count, &omm_indices[0], indices.size() / 3, states); data_size = (omm_count == 0) ? 0 : offsets[omm_count - 1] + meshopt_opacityMapEntrySize(levels[omm_count - 1], states); ``` After compaction, `levels` and `offsets` (`omm_count` entries) and `data` (`data_size` bytes) can be serialized and later passed to the raytracing runtime to build the opacity micromap structures. Often, compacting OMM data across multiple meshes can produce smaller results; in that case, all resulting OMM indices will point to a single OMM array object. Additionally, note that while the code above works with 32-bit OMM indices, after compaction it's typical to see each mesh refer to a small section of OMM array data which can be represented using 16-bit (or, sometimes, 8-bit indices). The index data can be narrowed to a shorter type in these cases; note that when using 16-bit OMM index data, due to special indices, the index values should be in range `[0..65531]` (and `[0..251]` for 8-bit indices, assuming 4-state OMMs are used). When using 4-state OMMs, rasterization code produces both unknown-transparent and unknown-opaque states based on microtriangle coverage; this enables the use of forced 2-state flag during traversal for specific effects where micromap data is sufficient for reasonable quality; this is recommended for performance as this results in no any-hit invocations. ## Memory management Many algorithms allocate temporary memory to store intermediate results or accelerate processing. The amount of memory allocated is a function of various input parameters such as vertex count and index count. By default memory is allocated using `operator new` and `operator delete`; if these operators are overloaded by the application, the overloads will be used instead. Alternatively it's possible to specify custom allocation/deallocation functions using `meshopt_setAllocator`, e.g. ```c++ meshopt_setAllocator(malloc, free); ``` > Note that the library expects the allocation function to either throw in case of out-of-memory (in which case the exception will propagate to the caller) or abort, so technically the use of `malloc` above isn't safe. If you want to handle out-of-memory errors without using C++ exceptions, you can use `setjmp`/`longjmp` instead. Vertex and index decoders (`meshopt_decodeVertexBuffer`, `meshopt_decodeIndexBuffer`, `meshopt_decodeIndexSequence`) do not allocate memory and work completely within the buffer space provided via arguments. All functions have bounded stack usage that does not exceed 32 KB for any algorithms. ## Experimental APIs Several algorithms provided by this library are marked as "experimental"; this status is reflected in the comments as well as the annotation `MESHOPTIMIZER_EXPERIMENTAL` for each function. APIs that are not experimental (annotated with `MESHOPTIMIZER_API`) are considered stable, which means that library updates will not break compatibility: existing calls should compile (API compatibility), existing binaries should link (ABI compatibility), and existing behavior should not change significantly (for example, floating point parameters will have similar behavior). This does not mean that the output of the algorithms will be identical: future versions may improve the algorithms and produce different results. APIs that *are* experimental may have their interface change, both in ways that will cause existing calls to not compile, and in ways that may compile but have significantly different behavior (e.g., changes in parameter order, meaning, valid ranges). Experimental APIs may also, in rare cases, be removed from future library versions. It is recommended to carefully read release notes when updating the library if experimental APIs are in use. Some experimental APIs may also lack documentation in this README. Applications may configure the library to change the attributes of experimental APIs, for example defining `MESHOPTIMIZER_EXPERIMENTAL` as `__attribute__((deprecated))` will emit compiler warnings when experimental APIs are used. When building a shared library with CMake, `MESHOPT_STABLE_EXPORTS` option can be set to only export stable APIs; this produces an ABI-stable shared library that can be updated without recompiling the application code. Currently, the following APIs are experimental: - `meshopt_SimplifyPermissive` mode for `meshopt_simplify*` functions (and associated `meshopt_SimplifyVertex_*` flags) - `meshopt_encodeMeshlet` and `meshopt_encodeMeshletBound` functions - `meshopt_decodeMeshlet` and `meshopt_decodeMeshletRaw` functions - `meshopt_extractMeshletIndices` function - `meshopt_opacityMap*` functions (`meshopt_opacityMapMeasure`, `meshopt_opacityMapRasterize`, `meshopt_opacityMapCompact`, `meshopt_opacityMapEntrySize`) ## License This library is available to anybody free of charge, under the terms of [MIT License](LICENSE.md). To honor the license agreement, please include attribution into the user-facing product documentation and/or credits, for example using this or similar text: > Uses meshoptimizer. Copyright (c) 2016-2026, Arseny Kapoulkine ================================================ FILE: demo/ansi.c ================================================ /* This file makes sure the library can be used by C89 code */ #include "../src/meshoptimizer.h" ================================================ FILE: demo/clusterlod.h ================================================ /** * clusterlod - a small "library"/example built on top of meshoptimizer to generate cluster LOD hierarchies * This is intended to either be used as is, or as a reference for implementing similar functionality in your engine. * * To use this code, you need to have one source file which includes meshoptimizer.h and defines CLUSTERLOD_IMPLEMENTATION * before including this file. Other source files in your project can just include this file and use the provided functions. * * Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * This code is distributed under the MIT License. See notice at the end of this file. */ #pragma once #include struct clodConfig { // configuration of each cluster; maps to meshopt_buildMeshlets* parameters size_t max_vertices; size_t min_triangles; size_t max_triangles; // partitioning setup; maps to meshopt_partitionClusters parameters (plus optional partition sorting) // note: partition size is the target size, not maximum; actual partitions may be up to 1/3 larger (e.g. target 24 results in maximum 32) bool partition_spatial; bool partition_sort; size_t partition_size; // clusterization setup; maps to meshopt_buildMeshletsSpatial / meshopt_buildMeshletsFlex bool cluster_spatial; float cluster_fill_weight; float cluster_split_factor; // every level aims to reduce the number of triangles by ratio, and considers clusters that don't reach the threshold stuck float simplify_ratio; float simplify_threshold; // to compute the error of simplified clusters, we use the formula that combines previous accumulated error as follows: // max(previous_error * simplify_error_merge_previous, current_error) + current_error * simplify_error_merge_additive float simplify_error_merge_previous; float simplify_error_merge_additive; // amplify the error of clusters that go through sloppy simplification to account for appearance degradation float simplify_error_factor_sloppy; // experimental: limit error by edge length, aiming to remove subpixel triangles even if the attribute error is high float simplify_error_edge_limit; // use permissive simplification instead of regular simplification (make sure to use attribute_protect_mask if this is set!) bool simplify_permissive; // use permissive or sloppy simplification but only if regular simplification gets stuck bool simplify_fallback_permissive; bool simplify_fallback_sloppy; // use regularization during simplification to make triangle density more uniform, at some cost to overall triangle count; recommended for deformable objects bool simplify_regularize; // should clodCluster::bounds be computed based on the geometry of each cluster bool optimize_bounds; // should clodCluster::indices be optimized for locality; helps with rasterization performance and ray tracing performance in fast-build modes bool optimize_clusters; }; struct clodMesh { // input triangle indices const unsigned int* indices; size_t index_count; // total vertex count size_t vertex_count; // input vertex positions; must be 3 floats per vertex const float* vertex_positions; size_t vertex_positions_stride; // input vertex attributes; used for attribute-aware simplification and permissive simplification const float* vertex_attributes; size_t vertex_attributes_stride; // input vertex locks; allows to preserve additional seams (when not using attribute_protect_mask) or lock vertices via meshopt_SimplifyVertex_* flags const unsigned char* vertex_lock; // attribute weights for attribute-aware simplification; maps to meshopt_simplifyWithAttributes parameters const float* attribute_weights; size_t attribute_count; // attribute mask to flag attribute discontinuities for permissive simplification; mask (1< size_t clodBuild(clodConfig config, clodMesh mesh, Output output) { struct Call { static int output(void* output_context, clodGroup group, const clodCluster* clusters, size_t cluster_count) { return (*static_cast(output_context))(group, clusters, cluster_count); } }; return clodBuild(config, mesh, &output, &Call::output); } #endif #ifdef CLUSTERLOD_IMPLEMENTATION // For reference, see the original Nanite paper: // Brian Karis. Nanite: A Deep Dive. 2021 #include #include #include #include #include namespace clod { struct Cluster { size_t vertices; std::vector indices; int group; int refined; clodBounds bounds; }; static clodBounds boundsCompute(const clodMesh& mesh, const std::vector& indices, float error) { meshopt_Bounds bounds = meshopt_computeClusterBounds(&indices[0], indices.size(), mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride); clodBounds result; result.center[0] = bounds.center[0]; result.center[1] = bounds.center[1]; result.center[2] = bounds.center[2]; result.radius = bounds.radius; result.error = error; return result; } static clodBounds boundsMerge(const std::vector& clusters, const std::vector& group) { std::vector bounds(group.size()); for (size_t j = 0; j < group.size(); ++j) bounds[j] = clusters[group[j]].bounds; meshopt_Bounds merged = meshopt_computeSphereBounds(&bounds[0].center[0], bounds.size(), sizeof(clodBounds), &bounds[0].radius, sizeof(clodBounds)); clodBounds result = {}; result.center[0] = merged.center[0]; result.center[1] = merged.center[1]; result.center[2] = merged.center[2]; result.radius = merged.radius; // merged bounds error must be conservative wrt cluster errors result.error = 0.f; for (size_t j = 0; j < group.size(); ++j) result.error = std::max(result.error, clusters[group[j]].bounds.error); return result; } static std::vector clusterize(const clodConfig& config, const clodMesh& mesh, const unsigned int* indices, size_t index_count) { size_t max_meshlets = meshopt_buildMeshletsBound(index_count, config.max_vertices, config.min_triangles); std::vector meshlets(max_meshlets); std::vector meshlet_vertices(index_count); #if MESHOPTIMIZER_VERSION < 1000 std::vector meshlet_triangles(index_count + max_meshlets * 3); // account for 4b alignment #else std::vector meshlet_triangles(index_count); #endif if (config.cluster_spatial) meshlets.resize(meshopt_buildMeshletsSpatial(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices, index_count, mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride, config.max_vertices, config.min_triangles, config.max_triangles, config.cluster_fill_weight)); else meshlets.resize(meshopt_buildMeshletsFlex(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices, index_count, mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride, config.max_vertices, config.min_triangles, config.max_triangles, 0.f, config.cluster_split_factor)); std::vector clusters(meshlets.size()); for (size_t i = 0; i < meshlets.size(); ++i) { const meshopt_Meshlet& meshlet = meshlets[i]; if (config.optimize_clusters) meshopt_optimizeMeshlet(&meshlet_vertices[meshlet.vertex_offset], &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count, meshlet.vertex_count); clusters[i].vertices = meshlet.vertex_count; // note: we discard meshlet-local indices; they can be recovered by the caller using clodLocalIndices clusters[i].indices.resize(meshlet.triangle_count * 3); for (size_t j = 0; j < meshlet.triangle_count * 3; ++j) clusters[i].indices[j] = meshlet_vertices[meshlet.vertex_offset + meshlet_triangles[meshlet.triangle_offset + j]]; clusters[i].group = -1; clusters[i].refined = -1; } return clusters; } static std::vector > partition(const clodConfig& config, const clodMesh& mesh, const std::vector& clusters, const std::vector& pending, const std::vector& remap) { if (pending.size() <= config.partition_size) return {pending}; std::vector cluster_indices; std::vector cluster_counts(pending.size()); // copy cluster index data into a flat array for partitioning size_t total_index_count = 0; for (size_t i = 0; i < pending.size(); ++i) total_index_count += clusters[pending[i]].indices.size(); cluster_indices.reserve(total_index_count); for (size_t i = 0; i < pending.size(); ++i) { const Cluster& cluster = clusters[pending[i]]; cluster_counts[i] = unsigned(cluster.indices.size()); for (size_t j = 0; j < cluster.indices.size(); ++j) cluster_indices.push_back(remap[cluster.indices[j]]); } // partition clusters into groups; the output is a partition id per cluster std::vector cluster_part(pending.size()); size_t partition_count = meshopt_partitionClusters(&cluster_part[0], &cluster_indices[0], cluster_indices.size(), &cluster_counts[0], cluster_counts.size(), config.partition_spatial ? mesh.vertex_positions : NULL, remap.size(), mesh.vertex_positions_stride, config.partition_size); // preallocate partitions for worst case std::vector > partitions(partition_count); for (size_t i = 0; i < partition_count; ++i) partitions[i].reserve(config.partition_size + config.partition_size / 3); std::vector partition_remap; if (config.partition_sort) { // compute partition points for sorting; any representative point will do, we use last cluster center for simplicity std::vector partition_point(partition_count * 3); for (size_t i = 0; i < pending.size(); ++i) memcpy(&partition_point[cluster_part[i] * 3], clusters[pending[i]].bounds.center, sizeof(float) * 3); // sort partitions spatially; the output is a remap table from old index (partition id) to new index partition_remap.resize(partition_count); meshopt_spatialSortRemap(partition_remap.data(), partition_point.data(), partition_count, sizeof(float) * 3); } // distribute clusters into partitions, applying spatial order if requested for (size_t i = 0; i < pending.size(); ++i) partitions[partition_remap.empty() ? cluster_part[i] : partition_remap[cluster_part[i]]].push_back(pending[i]); return partitions; } static void lockBoundary(std::vector& locks, const std::vector >& groups, const std::vector& clusters, const std::vector& remap, const unsigned char* vertex_lock) { // for each remapped vertex, use bit 7 as temporary storage to indicate that the vertex has been used by a different group previously for (size_t i = 0; i < locks.size(); ++i) locks[i] &= ~((1 << 0) | (1 << 7)); for (size_t i = 0; i < groups.size(); ++i) { // mark all remapped vertices as locked if seen by a prior group for (size_t j = 0; j < groups[i].size(); ++j) { const Cluster& cluster = clusters[groups[i][j]]; for (size_t k = 0; k < cluster.indices.size(); ++k) { unsigned int v = cluster.indices[k]; unsigned int r = remap[v]; locks[r] |= locks[r] >> 7; } } // mark all remapped vertices as seen for (size_t j = 0; j < groups[i].size(); ++j) { const Cluster& cluster = clusters[groups[i][j]]; for (size_t k = 0; k < cluster.indices.size(); ++k) { unsigned int v = cluster.indices[k]; unsigned int r = remap[v]; locks[r] |= 1 << 7; } } } for (size_t i = 0; i < locks.size(); ++i) { unsigned int r = remap[i]; // consistently lock all vertices with the same position; keep protect bit if set locks[i] = (locks[r] & 1) | (locks[i] & meshopt_SimplifyVertex_Protect); if (vertex_lock) locks[i] |= vertex_lock[i]; } } struct SloppyVertex { float x, y, z; unsigned int id; }; static void simplifyFallback(std::vector& lod, const clodMesh& mesh, const std::vector& indices, const std::vector& locks, size_t target_count, float* error) { std::vector subset(indices.size()); std::vector subset_locks(indices.size()); lod.resize(indices.size()); size_t positions_stride = mesh.vertex_positions_stride / sizeof(float); // deindex the mesh subset to avoid calling simplifySloppy on the entire vertex buffer (which is prohibitively expensive without sparsity) for (size_t i = 0; i < indices.size(); ++i) { unsigned int v = indices[i]; assert(v < mesh.vertex_count); subset[i].x = mesh.vertex_positions[v * positions_stride + 0]; subset[i].y = mesh.vertex_positions[v * positions_stride + 1]; subset[i].z = mesh.vertex_positions[v * positions_stride + 2]; subset[i].id = v; subset_locks[i] = locks[v]; lod[i] = unsigned(i); } lod.resize(meshopt_simplifySloppy(&lod[0], &lod[0], lod.size(), &subset[0].x, subset.size(), sizeof(SloppyVertex), subset_locks.data(), target_count, FLT_MAX, error)); // convert error to absolute *error *= meshopt_simplifyScale(&subset[0].x, subset.size(), sizeof(SloppyVertex)); // restore original vertex indices for (size_t i = 0; i < lod.size(); ++i) lod[i] = subset[lod[i]].id; } static std::vector simplify(const clodConfig& config, const clodMesh& mesh, const std::vector& indices, const std::vector& locks, size_t target_count, float* error) { if (target_count > indices.size()) return indices; std::vector lod(indices.size()); unsigned int options = meshopt_SimplifySparse | meshopt_SimplifyErrorAbsolute | (config.simplify_permissive ? meshopt_SimplifyPermissive : 0) | (config.simplify_regularize ? meshopt_SimplifyRegularize : 0); lod.resize(meshopt_simplifyWithAttributes(&lod[0], &indices[0], indices.size(), mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride, mesh.vertex_attributes, mesh.vertex_attributes_stride, mesh.attribute_weights, mesh.attribute_count, &locks[0], target_count, FLT_MAX, options, error)); if (lod.size() > target_count && config.simplify_fallback_permissive && !config.simplify_permissive) lod.resize(meshopt_simplifyWithAttributes(&lod[0], &indices[0], indices.size(), mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride, mesh.vertex_attributes, mesh.vertex_attributes_stride, mesh.attribute_weights, mesh.attribute_count, &locks[0], target_count, FLT_MAX, options | meshopt_SimplifyPermissive, error)); // while it's possible to call simplifySloppy directly, it doesn't support sparsity or absolute error, so we need to do some extra work if (lod.size() > target_count && config.simplify_fallback_sloppy) { simplifyFallback(lod, mesh, indices, locks, target_count, error); *error *= config.simplify_error_factor_sloppy; // scale error up to account for appearance degradation } // optionally limit error by edge length, aiming to remove subpixel triangles even if the attribute error is high if (config.simplify_error_edge_limit > 0) { float max_edge_sq = 0; for (size_t i = 0; i < indices.size(); i += 3) { unsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2]; assert(a < mesh.vertex_count && b < mesh.vertex_count && c < mesh.vertex_count); const float* va = &mesh.vertex_positions[a * (mesh.vertex_positions_stride / sizeof(float))]; const float* vb = &mesh.vertex_positions[b * (mesh.vertex_positions_stride / sizeof(float))]; const float* vc = &mesh.vertex_positions[c * (mesh.vertex_positions_stride / sizeof(float))]; // compute squared edge lengths float eab = (va[0] - vb[0]) * (va[0] - vb[0]) + (va[1] - vb[1]) * (va[1] - vb[1]) + (va[2] - vb[2]) * (va[2] - vb[2]); float eac = (va[0] - vc[0]) * (va[0] - vc[0]) + (va[1] - vc[1]) * (va[1] - vc[1]) + (va[2] - vc[2]) * (va[2] - vc[2]); float ebc = (vb[0] - vc[0]) * (vb[0] - vc[0]) + (vb[1] - vc[1]) * (vb[1] - vc[1]) + (vb[2] - vc[2]) * (vb[2] - vc[2]); float emax = std::max(std::max(eab, eac), ebc); float emin = std::min(std::min(eab, eac), ebc); // we prefer using min edge length to reduce the number of triangles <1px thick, but need some stopgap for thin and long triangles like wires max_edge_sq = std::max(max_edge_sq, std::max(emin, emax / 4)); } // adjust the error to limit it for dense clusters based on edge lengths *error = std::min(*error, sqrtf(max_edge_sq) * config.simplify_error_edge_limit); } return lod; } static int outputGroup(const clodConfig& config, const clodMesh& mesh, const std::vector& clusters, const std::vector& group, const clodBounds& simplified, int depth, void* output_context, clodOutput output_callback) { std::vector group_clusters(group.size()); for (size_t i = 0; i < group.size(); ++i) { const Cluster& cluster = clusters[group[i]]; clodCluster& result = group_clusters[i]; result.refined = cluster.refined; result.bounds = (config.optimize_bounds && cluster.refined != -1) ? boundsCompute(mesh, cluster.indices, cluster.bounds.error) : cluster.bounds; result.indices = cluster.indices.data(); result.index_count = cluster.indices.size(); result.vertex_count = cluster.vertices; } return output_callback ? output_callback(output_context, {depth, simplified}, group_clusters.data(), group_clusters.size()) : -1; } } // namespace clod clodConfig clodDefaultConfig(size_t max_triangles) { assert(max_triangles >= 4 && max_triangles <= 256); clodConfig config = {}; config.max_vertices = max_triangles; config.min_triangles = max_triangles / 3; config.max_triangles = max_triangles; #if MESHOPTIMIZER_VERSION < 1000 config.min_triangles &= ~3; // account for 4b alignment #endif config.partition_spatial = true; config.partition_size = 16; config.cluster_spatial = false; config.cluster_split_factor = 2.0f; config.optimize_clusters = true; config.simplify_ratio = 0.5f; config.simplify_threshold = 0.85f; config.simplify_error_merge_previous = 1.0f; config.simplify_error_factor_sloppy = 2.0f; config.simplify_permissive = true; config.simplify_fallback_permissive = false; // note: by default we run in permissive mode, but it's also possible to disable that and use it only as a fallback config.simplify_fallback_sloppy = true; return config; } clodConfig clodDefaultConfigRT(size_t max_triangles) { clodConfig config = clodDefaultConfig(max_triangles); // for ray tracing, we may want smaller clusters when that improves BVH quality further; for maximum ray tracing performance this could be reduced even further config.min_triangles = max_triangles / 4; // by default, we use larger max_vertices for RT; the vertex count is not important for ray tracing performance, and this helps improve cluster utilization config.max_vertices = std::min(size_t(256), max_triangles * 2); config.cluster_spatial = true; config.cluster_fill_weight = 0.5f; return config; } size_t clodBuild(clodConfig config, clodMesh mesh, void* output_context, clodOutput output_callback) { using namespace clod; assert(mesh.vertex_attributes_stride % sizeof(float) == 0); assert(mesh.attribute_count * sizeof(float) <= mesh.vertex_attributes_stride); assert(mesh.attribute_protect_mask < (1u << (mesh.vertex_attributes_stride / sizeof(float)))); std::vector locks(mesh.vertex_count); // for cluster connectivity, we need a position-only remap that maps vertices with the same position to the same index std::vector remap(mesh.vertex_count); meshopt_generatePositionRemap(&remap[0], mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride); // set up protect bits on UV seams for permissive mode if (mesh.attribute_protect_mask) { size_t max_attributes = mesh.vertex_attributes_stride / sizeof(float); for (size_t i = 0; i < mesh.vertex_count; ++i) { unsigned int r = remap[i]; // canonical vertex with the same position for (size_t j = 0; j < max_attributes; ++j) if (r != i && (mesh.attribute_protect_mask & (1u << j)) && mesh.vertex_attributes[i * max_attributes + j] != mesh.vertex_attributes[r * max_attributes + j]) locks[i] |= meshopt_SimplifyVertex_Protect; } } // initial clusterization splits the original mesh std::vector clusters = clusterize(config, mesh, mesh.indices, mesh.index_count); // compute initial precise bounds; subsequent bounds will be using group-merged bounds for (Cluster& cluster : clusters) cluster.bounds = boundsCompute(mesh, cluster.indices, 0.f); std::vector pending(clusters.size()); for (size_t i = 0; i < clusters.size(); ++i) pending[i] = int(i); int depth = 0; // merge and simplify clusters until we can't merge anymore while (pending.size() > 1) { std::vector > groups = partition(config, mesh, clusters, pending, remap); pending.clear(); // mark boundaries between groups with a lock bit to avoid gaps in simplified result lockBoundary(locks, groups, clusters, remap, mesh.vertex_lock); // every group needs to be simplified now for (size_t i = 0; i < groups.size(); ++i) { std::vector merged; merged.reserve(groups[i].size() * config.max_triangles * 3); for (size_t j = 0; j < groups[i].size(); ++j) merged.insert(merged.end(), clusters[groups[i][j]].indices.begin(), clusters[groups[i][j]].indices.end()); size_t target_size = size_t((merged.size() / 3) * config.simplify_ratio) * 3; // enforce bounds and error monotonicity // note: it is incorrect to use the precise bounds of the merged or simplified mesh, because this may violate monotonicity clodBounds bounds = boundsMerge(clusters, groups[i]); float error = 0.f; std::vector simplified = simplify(config, mesh, merged, locks, target_size, &error); if (simplified.size() > merged.size() * config.simplify_threshold) { bounds.error = FLT_MAX; // terminal group, won't simplify further outputGroup(config, mesh, clusters, groups[i], bounds, depth, output_context, output_callback); continue; // simplification is stuck; abandon the merge } // enforce error monotonicity (with an optional hierarchical factor to separate transitions more) bounds.error = std::max(bounds.error * config.simplify_error_merge_previous, error) + error * config.simplify_error_merge_additive; // output the new group with all clusters; the resulting id will be recorded in new clusters as clodCluster::refined int refined = outputGroup(config, mesh, clusters, groups[i], bounds, depth, output_context, output_callback); // discard clusters from the group - they won't be used anymore for (size_t j = 0; j < groups[i].size(); ++j) clusters[groups[i][j]].indices = std::vector(); std::vector split = clusterize(config, mesh, simplified.data(), simplified.size()); for (Cluster& cluster : split) { cluster.refined = refined; // update cluster group bounds to the group-merged bounds; this ensures that we compute the group bounds for whatever group this cluster will be part of conservatively cluster.bounds = bounds; // enqueue new cluster for further processing clusters.push_back(std::move(cluster)); pending.push_back(int(clusters.size()) - 1); } } depth++; } if (pending.size()) { assert(pending.size() == 1); const Cluster& cluster = clusters[pending[0]]; clodBounds bounds = cluster.bounds; bounds.error = FLT_MAX; // terminal group, won't simplify further outputGroup(config, mesh, clusters, pending, bounds, depth, output_context, output_callback); } return clusters.size(); } size_t clodLocalIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count) { return meshopt_extractMeshletIndices(vertices, triangles, indices, index_count); } #endif /** * Copyright (c) 2016-2026 Arseny Kapoulkine * * 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. */ ================================================ FILE: demo/index.html ================================================ meshoptimizer - demo ================================================ FILE: demo/main.cpp ================================================ #include "../src/meshoptimizer.h" #include #include #include #include #include #include #include "../extern/fast_obj.h" #define SDEFL_IMPLEMENTATION #include "../extern/sdefl.h" // This file uses assert() to verify algorithm correctness #undef NDEBUG #include #if defined(__linux__) double timestamp() { timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return double(ts.tv_sec) + 1e-9 * double(ts.tv_nsec); } #elif defined(_WIN32) struct LARGE_INTEGER { __int64 QuadPart; }; extern "C" __declspec(dllimport) int __stdcall QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount); extern "C" __declspec(dllimport) int __stdcall QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency); double timestamp() { LARGE_INTEGER freq, counter; QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&counter); return double(counter.QuadPart) / double(freq.QuadPart); } #else double timestamp() { return double(clock()) / double(CLOCKS_PER_SEC); } #endif struct Vertex { float px, py, pz; float nx, ny, nz; float tx, ty; }; struct Mesh { std::vector vertices; std::vector indices; }; union Triangle { Vertex v[3]; char data[sizeof(Vertex) * 3]; }; Mesh parseObj(const char* path, double& reindex) { fastObjMesh* obj = fast_obj_read(path); if (!obj) { printf("Error loading %s: file not found\n", path); return Mesh(); } size_t total_indices = 0; for (unsigned int i = 0; i < obj->face_count; ++i) if (obj->face_vertices[i] > 2) total_indices += 3 * (obj->face_vertices[i] - 2); std::vector vertices(total_indices); size_t vertex_offset = 0; size_t index_offset = 0; for (unsigned int i = 0; i < obj->face_count; ++i) { if (obj->face_vertices[i] <= 2) continue; for (unsigned int j = 0; j < obj->face_vertices[i]; ++j) { fastObjIndex gi = obj->indices[index_offset + j]; Vertex v = { obj->positions[gi.p * 3 + 0], obj->positions[gi.p * 3 + 1], obj->positions[gi.p * 3 + 2], obj->normals[gi.n * 3 + 0], obj->normals[gi.n * 3 + 1], obj->normals[gi.n * 3 + 2], obj->texcoords[gi.t * 2 + 0], obj->texcoords[gi.t * 2 + 1], }; // triangulate polygon on the fly; offset-3 is always the first polygon vertex if (j >= 3) { vertices[vertex_offset + 0] = vertices[vertex_offset - 3]; vertices[vertex_offset + 1] = vertices[vertex_offset - 1]; vertex_offset += 2; } vertices[vertex_offset] = v; vertex_offset++; } index_offset += obj->face_vertices[i]; } fast_obj_destroy(obj); reindex = timestamp(); Mesh result; // empty mesh if (total_indices == 0) return result; std::vector remap(total_indices); size_t total_vertices = meshopt_generateVertexRemap(&remap[0], NULL, total_indices, &vertices[0], total_indices, sizeof(Vertex)); result.indices.resize(total_indices); meshopt_remapIndexBuffer(&result.indices[0], NULL, total_indices, &remap[0]); result.vertices.resize(total_vertices); meshopt_remapVertexBuffer(&result.vertices[0], &vertices[0], total_indices, sizeof(Vertex), &remap[0]); return result; } void dumpObj(const std::vector& vertices, const std::vector& indices, bool recomputeNormals = false) { std::vector normals; if (recomputeNormals) { normals.resize(vertices.size() * 3); for (size_t i = 0; i < indices.size(); i += 3) { unsigned int a = indices[i], b = indices[i + 1], c = indices[i + 2]; const Vertex& va = vertices[a]; const Vertex& vb = vertices[b]; const Vertex& vc = vertices[c]; float nx = (vb.py - va.py) * (vc.pz - va.pz) - (vb.pz - va.pz) * (vc.py - va.py); float ny = (vb.pz - va.pz) * (vc.px - va.px) - (vb.px - va.px) * (vc.pz - va.pz); float nz = (vb.px - va.px) * (vc.py - va.py) - (vb.py - va.py) * (vc.px - va.px); for (int k = 0; k < 3; ++k) { unsigned int index = indices[i + k]; normals[index * 3 + 0] += nx; normals[index * 3 + 1] += ny; normals[index * 3 + 2] += nz; } } } for (size_t i = 0; i < vertices.size(); ++i) { const Vertex& v = vertices[i]; float nx = v.nx, ny = v.ny, nz = v.nz; if (recomputeNormals) { nx = normals[i * 3 + 0]; ny = normals[i * 3 + 1]; nz = normals[i * 3 + 2]; float l = sqrtf(nx * nx + ny * ny + nz * nz); float s = l == 0.f ? 0.f : 1.f / l; nx *= s; ny *= s; nz *= s; } fprintf(stderr, "v %f %f %f\n", v.px, v.py, v.pz); fprintf(stderr, "vn %f %f %f\n", nx, ny, nz); } for (size_t i = 0; i < indices.size(); i += 3) { unsigned int a = indices[i], b = indices[i + 1], c = indices[i + 2]; fprintf(stderr, "f %d//%d %d//%d %d//%d\n", a + 1, a + 1, b + 1, b + 1, c + 1, c + 1); } } void dumpObj(const char* section, const std::vector& indices) { fprintf(stderr, "o %s\n", section); for (size_t j = 0; j < indices.size(); j += 3) { unsigned int a = indices[j], b = indices[j + 1], c = indices[j + 2]; fprintf(stderr, "f %d//%d %d//%d %d//%d\n", a + 1, a + 1, b + 1, b + 1, c + 1, c + 1); } } struct PackedVertex { unsigned short px, py, pz; unsigned short pw; // padding to 4b boundary signed char nx, ny, nz, nw; unsigned short tx, ty; }; void packMesh(std::vector& pv, const std::vector& vertices) { for (size_t i = 0; i < vertices.size(); ++i) { const Vertex& vi = vertices[i]; PackedVertex& pvi = pv[i]; pvi.px = meshopt_quantizeHalf(vi.px); pvi.py = meshopt_quantizeHalf(vi.py); pvi.pz = meshopt_quantizeHalf(vi.pz); pvi.pw = 0; pvi.nx = char(meshopt_quantizeSnorm(vi.nx, 8)); pvi.ny = char(meshopt_quantizeSnorm(vi.ny, 8)); pvi.nz = char(meshopt_quantizeSnorm(vi.nz, 8)); pvi.nw = 0; pvi.tx = meshopt_quantizeHalf(vi.tx); pvi.ty = meshopt_quantizeHalf(vi.ty); } } struct PackedVertexOct { unsigned short px, py, pz; signed char nu, nv; // octahedron encoded normal, aliases .pw unsigned short tx, ty; }; void packMesh(std::vector& pv, const std::vector& vertices) { for (size_t i = 0; i < vertices.size(); ++i) { const Vertex& vi = vertices[i]; PackedVertexOct& pvi = pv[i]; pvi.px = meshopt_quantizeHalf(vi.px); pvi.py = meshopt_quantizeHalf(vi.py); pvi.pz = meshopt_quantizeHalf(vi.pz); float nsum = fabsf(vi.nx) + fabsf(vi.ny) + fabsf(vi.nz); float nx = vi.nx / nsum; float ny = vi.ny / nsum; float nz = vi.nz; float nu = nz >= 0 ? nx : (1 - fabsf(ny)) * (nx >= 0 ? 1 : -1); float nv = nz >= 0 ? ny : (1 - fabsf(nx)) * (ny >= 0 ? 1 : -1); pvi.nu = char(meshopt_quantizeSnorm(nu, 8)); pvi.nv = char(meshopt_quantizeSnorm(nv, 8)); pvi.tx = meshopt_quantizeHalf(vi.tx); pvi.ty = meshopt_quantizeHalf(vi.ty); } } void simplify(const Mesh& mesh, float threshold = 0.2f, unsigned int options = 0) { Mesh lod; double start = timestamp(); size_t target_index_count = size_t(mesh.indices.size() * threshold); float target_error = 1e-2f; float result_error = 0; lod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count lod.indices.resize(meshopt_simplify(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error, options, &result_error)); lod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize() lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex))); double end = timestamp(); printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\n", "Simplify", int(mesh.indices.size() / 3), int(lod.indices.size() / 3), result_error * 100, (end - start) * 1000); } void simplifyAttr(const Mesh& mesh, float threshold = 0.2f, unsigned int options = 0) { Mesh lod; double start = timestamp(); size_t target_index_count = size_t(mesh.indices.size() * threshold); float target_error = 1e-2f; float result_error = 0; const float nrm_weight = 0.5f; const float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight}; lod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count lod.indices.resize(meshopt_simplifyWithAttributes(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), &mesh.vertices[0].nx, sizeof(Vertex), attr_weights, 3, NULL, target_index_count, target_error, options, &result_error)); lod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize() lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex))); double end = timestamp(); printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\n", "SimplifyAttr", int(mesh.indices.size() / 3), int(lod.indices.size() / 3), result_error * 100, (end - start) * 1000); } void simplifyUpdate(const Mesh& mesh, float threshold = 0.2f, unsigned int options = 0) { Mesh lod; double start = timestamp(); size_t target_index_count = size_t(mesh.indices.size() * threshold); float target_error = 1e-2f; float result_error = 0; const float nrm_weight = 0.5f; const float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight}; lod = mesh; // start from the original mesh lod.indices.resize(meshopt_simplifyWithUpdate(&lod.indices[0], mesh.indices.size(), &lod.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), &lod.vertices[0].nx, sizeof(Vertex), attr_weights, 3, NULL, target_index_count, target_error, options, &result_error)); lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex))); for (size_t i = 0; i < lod.vertices.size(); ++i) { // update normals Vertex& v = lod.vertices[i]; float nl = sqrtf(v.nx * v.nx + v.ny * v.ny + v.nz * v.nz); if (nl > 0) { v.nx /= nl; v.ny /= nl; v.nz /= nl; } } double end = timestamp(); printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\n", "SimplifyUpdt", int(mesh.indices.size() / 3), int(lod.indices.size() / 3), result_error * 100, (end - start) * 1000); } void simplifySloppy(const Mesh& mesh, float threshold = 0.2f) { Mesh lod; double start = timestamp(); size_t target_index_count = size_t(mesh.indices.size() * threshold); float target_error = 1e-1f; float result_error = 0; lod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count lod.indices.resize(meshopt_simplifySloppy(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error, &result_error)); lod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize() lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex))); double end = timestamp(); printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\n", "SimplifyS", int(mesh.indices.size() / 3), int(lod.indices.size() / 3), result_error * 100, (end - start) * 1000); } void simplifyPoints(const Mesh& mesh, float threshold = 0.2f) { double start = timestamp(); size_t target_vertex_count = size_t(mesh.vertices.size() * threshold); if (target_vertex_count == 0) return; std::vector indices(target_vertex_count); indices.resize(meshopt_simplifyPoints(&indices[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), NULL, 0, 0, target_vertex_count)); double end = timestamp(); printf("%-9s: %d points => %d points in %.2f msec\n", "SimplifyP", int(mesh.vertices.size()), int(indices.size()), (end - start) * 1000); } void simplifyComplete(const Mesh& mesh) { static const size_t lod_count = 5; double start = timestamp(); // generate 4 LOD levels (1-4), with each subsequent LOD using 70% triangles // note that each LOD uses the same (shared) vertex buffer std::vector lods[lod_count]; lods[0] = mesh.indices; for (size_t i = 1; i < lod_count; ++i) { std::vector& lod = lods[i]; float threshold = powf(0.7f, float(i)); size_t target_index_count = size_t(mesh.indices.size() * threshold) / 3 * 3; float target_error = 1e-2f; // we can simplify all the way from base level or from the last result // simplifying from the base level sometimes produces better results, but simplifying from last level is faster const std::vector& source = lods[i - 1]; if (source.size() < target_index_count) target_index_count = source.size(); lod.resize(source.size()); lod.resize(meshopt_simplify(&lod[0], &source[0], source.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error)); } double middle = timestamp(); // optimize each individual LOD for vertex cache & overdraw for (size_t i = 0; i < lod_count; ++i) { std::vector& lod = lods[i]; meshopt_optimizeVertexCache(&lod[0], &lod[0], lod.size(), mesh.vertices.size()); meshopt_optimizeOverdraw(&lod[0], &lod[0], lod.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), 1.0f); } // concatenate all LODs into one IB // note: the order of concatenation is important - since we optimize the entire IB for vertex fetch, // putting coarse LODs first makes sure that the vertex range referenced by them is as small as possible // some GPUs process the entire range referenced by the index buffer region so doing this optimizes the vertex transform // cost for coarse LODs // this order also produces much better vertex fetch cache coherency for coarse LODs (since they're essentially optimized first) // somewhat surprisingly, the vertex fetch cache coherency for fine LODs doesn't seem to suffer that much. size_t lod_index_offsets[lod_count] = {}; size_t lod_index_counts[lod_count] = {}; size_t total_index_count = 0; for (int i = lod_count - 1; i >= 0; --i) { lod_index_offsets[i] = total_index_count; lod_index_counts[i] = lods[i].size(); total_index_count += lods[i].size(); } std::vector indices(total_index_count); for (size_t i = 0; i < lod_count; ++i) { memcpy(&indices[lod_index_offsets[i]], &lods[i][0], lods[i].size() * sizeof(lods[i][0])); } std::vector vertices = mesh.vertices; // vertex fetch optimization should go last as it depends on the final index order // note that the order of LODs above affects vertex fetch results meshopt_optimizeVertexFetch(&vertices[0], &indices[0], indices.size(), &vertices[0], vertices.size(), sizeof(Vertex)); double end = timestamp(); printf("%-9s: %d triangles => %d LOD levels down to %d triangles in %.2f msec, optimized in %.2f msec\n", "SimplifyC", int(lod_index_counts[0]) / 3, int(lod_count), int(lod_index_counts[lod_count - 1]) / 3, (middle - start) * 1000, (end - middle) * 1000); // for using LOD data at runtime, in addition to vertices and indices you have to save lod_index_offsets/lod_index_counts. { meshopt_VertexCacheStatistics vcs0 = meshopt_analyzeVertexCache(&indices[lod_index_offsets[0]], lod_index_counts[0], vertices.size(), 16, 0, 0); meshopt_VertexFetchStatistics vfs0 = meshopt_analyzeVertexFetch(&indices[lod_index_offsets[0]], lod_index_counts[0], vertices.size(), sizeof(Vertex)); meshopt_VertexCacheStatistics vcsN = meshopt_analyzeVertexCache(&indices[lod_index_offsets[lod_count - 1]], lod_index_counts[lod_count - 1], vertices.size(), 16, 0, 0); meshopt_VertexFetchStatistics vfsN = meshopt_analyzeVertexFetch(&indices[lod_index_offsets[lod_count - 1]], lod_index_counts[lod_count - 1], vertices.size(), sizeof(Vertex)); typedef PackedVertexOct PV; std::vector pv(vertices.size()); packMesh(pv, vertices); std::vector vbuf(meshopt_encodeVertexBufferBound(vertices.size(), sizeof(PV))); vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], vertices.size(), sizeof(PV))); std::vector ibuf(meshopt_encodeIndexBufferBound(indices.size(), vertices.size())); ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), &indices[0], indices.size())); printf("%-9s ACMR %f...%f Overfetch %f..%f Codec VB %.1f bits/vertex IB %.1f bits/triangle\n", "", vcs0.acmr, vcsN.acmr, vfs0.overfetch, vfsN.overfetch, double(vbuf.size()) / double(vertices.size()) * 8, double(ibuf.size()) / double(indices.size() / 3) * 8); } } void simplifyClusters(const Mesh& mesh, float threshold = 0.2f) { const size_t max_vertices = 64; const size_t max_triangles = 64; const size_t target_group_size = 8; double start = timestamp(); // build clusters (meshlets) out of the mesh size_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, max_triangles); std::vector meshlets(max_meshlets); std::vector meshlet_vertices(mesh.indices.size()); std::vector meshlet_triangles(mesh.indices.size()); meshlets.resize(meshopt_buildMeshlets(&meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), max_vertices, max_triangles, 0.f)); double middle = timestamp(); // generate position remap; we'll use that to partition clusters using position-only adjacency std::vector remap(mesh.vertices.size()); meshopt_generatePositionRemap(&remap[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex)); // partition clusters in groups; each group will be simplified separately and the boundaries between groups will be preserved std::vector cluster_indices; cluster_indices.reserve(mesh.indices.size()); // slight underestimate, vector should realloc once std::vector cluster_sizes(meshlets.size()); for (size_t i = 0; i < meshlets.size(); ++i) { const meshopt_Meshlet& m = meshlets[i]; for (size_t j = 0; j < m.triangle_count * 3; ++j) { unsigned int v = meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + j]]; // use the first vertex with equivalent position so that cluster adjacency ignores attribute seams cluster_indices.push_back(remap[v]); } cluster_sizes[i] = m.triangle_count * 3; } std::vector partition(meshlets.size()); size_t partition_count = meshopt_partitionClusters(&partition[0], &cluster_indices[0], cluster_indices.size(), &cluster_sizes[0], cluster_sizes.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_group_size); // convert partitions to linked lists to make it easier to iterate over (vectors of vectors would work too) std::vector partnext(meshlets.size(), -1); std::vector partlast(partition_count, -1); for (size_t i = 0; i < meshlets.size(); ++i) { unsigned int part = partition[i]; if (partlast[part] >= 0) partnext[partlast[part]] = int(i); partlast[part] = int(i); partnext[i] = -1; } double parttime = timestamp(); float scale = meshopt_simplifyScale(&mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex)); std::vector lod; lod.reserve(mesh.indices.size()); float error = 0.f; for (size_t i = 0; i < meshlets.size(); ++i) { if (partlast[partition[i]] < 0) continue; // part of a group that was already processed // mark group as processed partlast[partition[i]] = -1; size_t group_offset = lod.size(); for (int j = int(i); j >= 0; j = partnext[j]) { const meshopt_Meshlet& m = meshlets[j]; for (size_t k = 0; k < m.triangle_count * 3; ++k) lod.push_back(meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + k]]); } size_t group_triangles = (lod.size() - group_offset) / 3; // simplify the group, preserving the border vertices // note: this technically also locks the exterior border; a full mesh analysis (see clusterlod.h / lockBoundary) would work better for some meshes unsigned int options = meshopt_SimplifyLockBorder | meshopt_SimplifySparse | meshopt_SimplifyErrorAbsolute; float group_target_error = 1e-2f * scale; size_t group_target = size_t(float(group_triangles) * threshold) * 3; float group_error = 0.f; size_t group_size = meshopt_simplify(&lod[group_offset], &lod[group_offset], group_triangles * 3, &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), group_target, group_target_error, options, &group_error); error = group_error > error ? group_error : error; // simplified group is available in lod[group_offset..group_offset + group_size] lod.resize(group_offset + group_size); } double end = timestamp(); printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec, clusterized in %.2f msec, partitioned in %.2f msec (%d clusters in %d groups, %.1f avg)\n", "SimplifyG", int(mesh.indices.size() / 3), int(lod.size() / 3), error / scale * 100, (end - parttime) * 1000, (middle - start) * 1000, (parttime - middle) * 1000, int(meshlets.size()), int(partition_count), double(meshlets.size()) / double(partition_count)); } void optimize(const Mesh& mesh, bool fifo = false) { Mesh copy = mesh; // note: we assume that the mesh is already optimally indexed (via parseObj); if that is not the case, you'd need to reindex first double start = timestamp(); // vertex cache optimization should go first as it provides starting order for overdraw // note: fifo optimization is not recommended as a default, since it produces worse results, but it's faster to run so it can be useful for procedural meshes if (fifo) meshopt_optimizeVertexCacheFifo(©.indices[0], ©.indices[0], copy.indices.size(), copy.vertices.size(), /* cache_size= */ 16); else meshopt_optimizeVertexCache(©.indices[0], ©.indices[0], copy.indices.size(), copy.vertices.size()); // reorder indices for overdraw, balancing overdraw and vertex cache efficiency const float kThreshold = 1.01f; // allow up to 1% worse ACMR to get more reordering opportunities for overdraw meshopt_optimizeOverdraw(©.indices[0], ©.indices[0], copy.indices.size(), ©.vertices[0].px, copy.vertices.size(), sizeof(Vertex), kThreshold); // vertex fetch optimization should go last as it depends on the final index order meshopt_optimizeVertexFetch(©.vertices[0], ©.indices[0], copy.indices.size(), ©.vertices[0], copy.vertices.size(), sizeof(Vertex)); double end = timestamp(); meshopt_VertexCacheStatistics vcs = meshopt_analyzeVertexCache(©.indices[0], copy.indices.size(), copy.vertices.size(), 16, 0, 0); meshopt_VertexFetchStatistics vfs = meshopt_analyzeVertexFetch(©.indices[0], copy.indices.size(), copy.vertices.size(), sizeof(Vertex)); meshopt_OverdrawStatistics os = meshopt_analyzeOverdraw(©.indices[0], copy.indices.size(), ©.vertices[0].px, copy.vertices.size(), sizeof(Vertex)); meshopt_VertexCacheStatistics vcs_nv = meshopt_analyzeVertexCache(©.indices[0], copy.indices.size(), copy.vertices.size(), 32, 32, 32); meshopt_VertexCacheStatistics vcs_amd = meshopt_analyzeVertexCache(©.indices[0], copy.indices.size(), copy.vertices.size(), 14, 64, 128); meshopt_VertexCacheStatistics vcs_intel = meshopt_analyzeVertexCache(©.indices[0], copy.indices.size(), copy.vertices.size(), 128, 0, 0); printf("Optimize%s: ACMR %f ATVR %f (NV %f AMD %f Intel %f) overfetch %f overdraw %f in %.2f msec\n", fifo ? "F" : " ", vcs.acmr, vcs.atvr, vcs_nv.atvr, vcs_amd.atvr, vcs_intel.atvr, vfs.overfetch, os.overdraw, (end - start) * 1000); } template size_t compress(const std::vector& data, int level = SDEFL_LVL_DEF) { std::vector cbuf(sdefl_bound(int(data.size() * sizeof(T)))); sdefl s = {}; return sdeflate(&s, &cbuf[0], reinterpret_cast(&data[0]), int(data.size() * sizeof(T)), level); } void encodeIndex(const std::vector& indices, size_t vertex_count, char desc) { // allocate result outside of the timing loop to exclude memset() from decode timing std::vector result(indices.size()); double start = timestamp(); std::vector buffer(meshopt_encodeIndexBufferBound(indices.size(), vertex_count)); buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), &indices[0], indices.size())); double middle = timestamp(); int res = meshopt_decodeIndexBuffer(&result[0], indices.size(), &buffer[0], buffer.size()); assert(res == 0); (void)res; double end = timestamp(); size_t csize = compress(buffer); for (size_t i = 0; i < indices.size(); i += 3) { assert( (result[i + 0] == indices[i + 0] && result[i + 1] == indices[i + 1] && result[i + 2] == indices[i + 2]) || (result[i + 1] == indices[i + 0] && result[i + 2] == indices[i + 1] && result[i + 0] == indices[i + 2]) || (result[i + 2] == indices[i + 0] && result[i + 0] == indices[i + 1] && result[i + 1] == indices[i + 2])); } printf("IdxCodec%c: %.1f bits/triangle (post-deflate %.1f bits/triangle); encode %.2f msec (%.3f GB/s), decode %.2f msec (%.2f GB/s)\n", desc, double(buffer.size() * 8) / double(indices.size() / 3), double(csize * 8) / double(indices.size() / 3), (middle - start) * 1000, (double(result.size() * 4) / 1e9) / (middle - start), (end - middle) * 1000, (double(result.size() * 4) / 1e9) / (end - middle)); } void encodeIndex(const Mesh& mesh, char desc) { encodeIndex(mesh.indices, mesh.vertices.size(), desc); } void encodeIndexSequence(const std::vector& data, size_t vertex_count, char desc) { // allocate result outside of the timing loop to exclude memset() from decode timing std::vector result(data.size()); double start = timestamp(); std::vector buffer(meshopt_encodeIndexSequenceBound(data.size(), vertex_count)); buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), &data[0], data.size())); double middle = timestamp(); int res = meshopt_decodeIndexSequence(&result[0], data.size(), &buffer[0], buffer.size()); assert(res == 0); (void)res; double end = timestamp(); size_t csize = compress(buffer); assert(memcmp(&data[0], &result[0], data.size() * sizeof(unsigned int)) == 0); printf("IdxCodec%c: %.1f bits/index (post-deflate %.1f bits/index); encode %.2f msec (%.3f GB/s), decode %.2f msec (%.2f GB/s)\n", desc, double(buffer.size() * 8) / double(data.size()), double(csize * 8) / double(data.size()), (middle - start) * 1000, (double(result.size() * 4) / 1e9) / (middle - start), (end - middle) * 1000, (double(result.size() * 4) / 1e9) / (end - middle)); } template static void validateDecodeMeshlet(const unsigned char* data, size_t size, const unsigned int* vertices, size_t vertex_count, const unsigned char* triangles, size_t triangle_count) { V rv[256]; T rt[sizeof(T) == 1 ? 256 * 3 : 256]; int rc = meshopt_decodeMeshlet(rv, vertex_count, rt, triangle_count, data, size); assert(rc == 0); for (size_t j = 0; j < vertex_count; ++j) assert(rv[j] == V(vertices[j])); for (size_t j = 0; j < triangle_count; ++j) { unsigned int a = triangles[j * 3 + 0]; unsigned int b = triangles[j * 3 + 1]; unsigned int c = triangles[j * 3 + 2]; unsigned int tri = sizeof(T) == 1 ? rt[j * 3] | (rt[j * 3 + 1] << 8) | (rt[j * 3 + 2] << 16) : rt[j]; unsigned int abc = (a << 0) | (b << 8) | (c << 16); unsigned int bca = (b << 0) | (c << 8) | (a << 16); unsigned int cba = (c << 0) | (a << 8) | (b << 16); assert(tri == abc || tri == bca || tri == cba); } } void encodeMeshlets(const Mesh& mesh, size_t max_vertices, size_t max_triangles, bool reorder = true) { size_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, max_triangles); std::vector meshlets(max_meshlets); std::vector meshlet_vertices(mesh.indices.size()); std::vector meshlet_triangles(mesh.indices.size()); meshlets.resize(meshopt_buildMeshlets(&meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), max_vertices, max_triangles, 0.f)); if (meshlets.size()) { const meshopt_Meshlet& last = meshlets.back(); // this is an example of how to trim the vertex/triangle arrays when copying data out to GPU storage meshlet_vertices.resize(last.vertex_offset + last.vertex_count); meshlet_triangles.resize(last.triangle_offset + last.triangle_count * 3); // TODO: over-allocate meshlet_vertices to multiple of 3 to make meshopt_optimizeVertexFetch below work without assertions meshlet_vertices.resize((meshlet_vertices.size() + 2) / 3 * 3); } std::vector cbuf(meshopt_encodeMeshletBound(max_vertices, max_triangles)); // optimize each meshlet for locality; this is important for performance, and critical for good compression for (size_t i = 0; i < meshlets.size(); ++i) meshopt_optimizeMeshlet(&meshlet_vertices[meshlets[i].vertex_offset], &meshlet_triangles[meshlets[i].triangle_offset], meshlets[i].triangle_count, meshlets[i].vertex_count); // optimize the order of vertex references within each meshlet and globally; this is valuable for access locality and critical for compression of vertex references // note that this reorders the vertex buffer too, so if a traditional index buffer is required it would need to be reconstructed from the meshlet data for optimal locality std::vector vertices = mesh.vertices; if (reorder) meshopt_optimizeVertexFetch(&vertices[0], &meshlet_vertices[0], meshlet_vertices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)); size_t mbst = 0; std::vector packed; for (size_t i = 0; i < meshlets.size(); ++i) { const meshopt_Meshlet& meshlet = meshlets[i]; size_t mbs = meshopt_encodeMeshlet(&cbuf[0], cbuf.size(), &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count); assert(mbs > 0); // 24-bit header: 7 bit (vertex_count-1), 7 bit (triangle_count-1), 10 bit size // fits up to 128v/128t meshlet with 1024 bytes of encoded data; meshopt_encodeMeshletBound(128,128) < 1000 assert(size_t(meshlet.vertex_count - 1) < 128 && size_t(meshlet.triangle_count - 1) < 128 && mbs < 1024); unsigned int header = ((meshlet.vertex_count - 1) & 0x7f) | (((meshlet.triangle_count - 1) & 0x7f) << 7) | ((unsigned(mbs) & 0x3ff) << 14); packed.push_back((unsigned char)(header & 0xff)); packed.push_back((unsigned char)((header >> 8) & 0xff)); packed.push_back((unsigned char)((header >> 16) & 0xff)); packed.insert(packed.end(), &cbuf[0], &cbuf[mbs]); validateDecodeMeshlet(&cbuf[0], mbs, &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count); validateDecodeMeshlet(&cbuf[0], mbs, &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count); validateDecodeMeshlet(&cbuf[0], mbs, &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count); validateDecodeMeshlet(&cbuf[0], mbs, &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count); mbst += mbs; } size_t mbc = compress(packed); printf("MeshletCodec (%d/%d): %d meshlets, %d bytes/meshlet; %d bytes, %.1f bits/triangle\n", int(max_vertices), int(max_triangles), int(meshlets.size()), int(mbst / meshlets.size()), int(mbst), double(mbst * 8) / double(mesh.indices.size() / 3)); printf("MeshletCodec (%d/%d, packed): %d bytes/meshlet, %.1f bits/triangle; post-deflate: %d bytes/meshlet, %.1f bits/triangle)\n", int(max_vertices), int(max_triangles), int(packed.size() / meshlets.size()), double(packed.size() * 8) / double(mesh.indices.size() / 3), int(mbc / meshlets.size()), double(mbc * 8) / double(mesh.indices.size() / 3)); #if !TRACE double mbtime = 0; for (int i = 0; i < 10; ++i) { unsigned int rv[256]; unsigned int rt[256]; double t0 = timestamp(); unsigned char* p = &packed[0]; for (size_t j = 0; j < meshlets.size(); ++j) { unsigned int header = p[0] | (p[1] << 8) | (p[2] << 16); size_t vertex_count = (header & 0x7f) + 1; size_t triangle_count = ((header >> 7) & 0x7f) + 1; size_t size = (header >> 14) & 0x3ff; meshopt_decodeMeshletRaw(rv, vertex_count, rt, triangle_count, p + 3, size); p += 3 + size; } double t1 = timestamp(); mbtime = (mbtime == 0 || t1 - t0 < mbtime) ? (t1 - t0) : mbtime; } printf("MeshletCodec (%d/%d, packed): decode time %.3f msec, %.3fB tri/sec, %.1f ns/meshlet\n", int(max_vertices), int(max_triangles), mbtime * 1000, double(mesh.indices.size() / 3) / 1e9 / mbtime, mbtime * 1e9 / double(meshlets.size())); #endif } template void packVertex(const Mesh& mesh, const char* pvn) { std::vector pv(mesh.vertices.size()); packMesh(pv, mesh.vertices); size_t csize = compress(pv); printf("VtxPack%s : %.1f bits/vertex (post-deflate %.1f bits/vertex)\n", pvn, double(pv.size() * sizeof(PV) * 8) / double(mesh.vertices.size()), double(csize * 8) / double(mesh.vertices.size())); } template void encodeVertex(const Mesh& mesh, const char* pvn, int level = 2) { std::vector pv(mesh.vertices.size()); packMesh(pv, mesh.vertices); // allocate result outside of the timing loop to exclude memset() from decode timing std::vector result(mesh.vertices.size()); double start = timestamp(); std::vector vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV))); vbuf.resize(meshopt_encodeVertexBufferLevel(&vbuf[0], vbuf.size(), &pv[0], mesh.vertices.size(), sizeof(PV), level, -1)); double middle = timestamp(); int res = meshopt_decodeVertexBuffer(&result[0], mesh.vertices.size(), sizeof(PV), &vbuf[0], vbuf.size()); assert(res == 0); (void)res; double end = timestamp(); assert(memcmp(&pv[0], &result[0], pv.size() * sizeof(PV)) == 0); size_t csize = compress(vbuf); printf("VtxCodec%1s: %.1f bits/vertex (post-deflate %.1f bits/vertex); encode %.2f msec (%.3f GB/s), decode %.2f msec (%.2f GB/s)\n", pvn, double(vbuf.size() * 8) / double(mesh.vertices.size()), double(csize * 8) / double(mesh.vertices.size()), (middle - start) * 1000, (double(result.size() * sizeof(PV)) / 1e9) / (middle - start), (end - middle) * 1000, (double(result.size() * sizeof(PV)) / 1e9) / (end - middle)); } void stripify(const Mesh& mesh, bool use_restart, char desc) { unsigned int restart_index = use_restart ? ~0u : 0; // note: input mesh is assumed to be optimized for vertex cache and vertex fetch double start = timestamp(); std::vector strip(meshopt_stripifyBound(mesh.indices.size())); strip.resize(meshopt_stripify(&strip[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), restart_index)); double end = timestamp(); size_t restarts = 0; for (size_t i = 0; i < strip.size(); ++i) restarts += use_restart && strip[i] == restart_index; Mesh copy = mesh; copy.indices.resize(meshopt_unstripify(©.indices[0], &strip[0], strip.size(), restart_index)); assert(copy.indices.size() <= meshopt_unstripifyBound(strip.size())); meshopt_VertexCacheStatistics vcs = meshopt_analyzeVertexCache(©.indices[0], mesh.indices.size(), mesh.vertices.size(), 16, 0, 0); meshopt_VertexCacheStatistics vcs_nv = meshopt_analyzeVertexCache(©.indices[0], mesh.indices.size(), mesh.vertices.size(), 32, 32, 32); meshopt_VertexCacheStatistics vcs_amd = meshopt_analyzeVertexCache(©.indices[0], mesh.indices.size(), mesh.vertices.size(), 14, 64, 128); meshopt_VertexCacheStatistics vcs_intel = meshopt_analyzeVertexCache(©.indices[0], mesh.indices.size(), mesh.vertices.size(), 128, 0, 0); printf("Stripify%c: ACMR %f ATVR %f (NV %f AMD %f Intel %f); %.1f run avg, %d strip indices (%.1f%%) in %.2f msec\n", desc, vcs.acmr, vcs.atvr, vcs_nv.atvr, vcs_amd.atvr, vcs_intel.atvr, use_restart ? double(strip.size() - restarts) / double(restarts + 1) : 0, int(strip.size()), double(strip.size()) / double(mesh.indices.size()) * 100, (end - start) * 1000); } void shadow(const Mesh& mesh) { // note: input mesh is assumed to be optimized for vertex cache and vertex fetch double start = timestamp(); // this index buffer can be used for position-only rendering using the same vertex data that the original index buffer uses std::vector shadow_indices(mesh.indices.size()); meshopt_generateShadowIndexBuffer(&shadow_indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(float) * 3, sizeof(Vertex)); double end = timestamp(); // while you can't optimize the vertex data after shadow IB was constructed, you can and should optimize the shadow IB for vertex cache // this is valuable even if the original indices array was optimized for vertex cache! meshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], shadow_indices.size(), mesh.vertices.size()); meshopt_VertexCacheStatistics vcs = meshopt_analyzeVertexCache(&mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), 16, 0, 0); meshopt_VertexCacheStatistics vcss = meshopt_analyzeVertexCache(&shadow_indices[0], shadow_indices.size(), mesh.vertices.size(), 16, 0, 0); std::vector shadow_flags(mesh.vertices.size()); size_t shadow_vertices = 0; for (size_t i = 0; i < shadow_indices.size(); ++i) { unsigned int index = shadow_indices[i]; shadow_vertices += 1 - shadow_flags[index]; shadow_flags[index] = 1; } printf("ShadowIB : ACMR %f (%.2fx improvement); %d shadow vertices (%.2fx improvement) in %.2f msec\n", vcss.acmr, double(vcs.vertices_transformed) / double(vcss.vertices_transformed), int(shadow_vertices), double(mesh.vertices.size()) / double(shadow_vertices), (end - start) * 1000); } static int follow(int* parents, int index) { while (index != parents[index]) { int parent = parents[index]; parents[index] = parents[parent]; index = parent; } return index; } void meshlets(const Mesh& mesh, bool scan = false, bool uniform = false, bool flex = false, bool spatial = false, bool dump = false) { // NVidia recommends 64/126; we also test uniform configuration with 64/64 which is better for earlier AMD GPUs const size_t max_vertices = 64; const size_t max_triangles = uniform ? 64 : 126; const size_t min_triangles = spatial ? 16 : (uniform ? 24 : 32); // only used in flex/spatial modes // note: should be set to 0 unless cone culling is used at runtime! const float cone_weight = 0.25f; const float split_factor = flex ? 2.0f : 0.0f; // note: input mesh is assumed to be optimized for vertex cache and vertex fetch double start = timestamp(); size_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, min_triangles); std::vector meshlets(max_meshlets); std::vector meshlet_vertices(mesh.indices.size()); std::vector meshlet_triangles(mesh.indices.size()); if (scan) meshlets.resize(meshopt_buildMeshletsScan(&meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), max_vertices, max_triangles)); else if (flex) meshlets.resize(meshopt_buildMeshletsFlex(&meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), max_vertices, min_triangles, max_triangles, cone_weight, split_factor)); else if (spatial) meshlets.resize(meshopt_buildMeshletsSpatial(&meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), max_vertices, min_triangles, max_triangles, 0.f)); else // note: equivalent to the call of buildMeshletsFlex() with split_factor = 0 and min_triangles = max_triangles meshlets.resize(meshopt_buildMeshlets(&meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), max_vertices, max_triangles, cone_weight)); if (!dump) for (size_t i = 0; i < meshlets.size(); ++i) meshopt_optimizeMeshlet(&meshlet_vertices[meshlets[i].vertex_offset], &meshlet_triangles[meshlets[i].triangle_offset], meshlets[i].triangle_count, meshlets[i].vertex_count); if (meshlets.size()) { const meshopt_Meshlet& last = meshlets.back(); // this is an example of how to trim the vertex/triangle arrays when copying data out to GPU storage meshlet_vertices.resize(last.vertex_offset + last.vertex_count); meshlet_triangles.resize(last.triangle_offset + last.triangle_count * 3); } double end = timestamp(); if (dump) dumpObj(mesh.vertices, std::vector()); double avg_vertices = 0; double avg_triangles = 0; double avg_boundary = 0; double avg_connected = 0; size_t not_full = 0; std::vector boundary(mesh.vertices.size()); for (size_t i = 0; i < meshlets.size(); ++i) { const meshopt_Meshlet& m = meshlets[i]; for (unsigned int j = 0; j < m.vertex_count; ++j) boundary[meshlet_vertices[m.vertex_offset + j]]++; } std::vector cluster; for (size_t i = 0; i < meshlets.size(); ++i) { const meshopt_Meshlet& m = meshlets[i]; if (dump) { cluster.clear(); for (unsigned int j = 0; j < m.triangle_count * 3; ++j) cluster.push_back(meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + j]]); char cname[32]; snprintf(cname, sizeof(cname), "ml_%d\n", int(i)); dumpObj(cname, cluster); } avg_vertices += m.vertex_count; avg_triangles += m.triangle_count; not_full += uniform ? m.triangle_count < max_triangles : m.vertex_count < max_vertices; for (unsigned int j = 0; j < m.vertex_count; ++j) if (boundary[meshlet_vertices[m.vertex_offset + j]] > 1) avg_boundary += 1; // union-find vertices to check if the meshlet is connected int parents[256]; for (unsigned int j = 0; j < m.vertex_count; ++j) parents[j] = int(j); for (unsigned int j = 0; j < m.triangle_count * 3; ++j) { int v0 = meshlet_triangles[m.triangle_offset + j]; int v1 = meshlet_triangles[m.triangle_offset + j + (j % 3 == 2 ? -2 : 1)]; v0 = follow(parents, v0); v1 = follow(parents, v1); parents[v0] = v1; } int roots = 0; for (unsigned int j = 0; j < m.vertex_count; ++j) roots += follow(parents, j) == int(j); assert(roots != 0); avg_connected += roots; } avg_vertices /= double(meshlets.size()); avg_triangles /= double(meshlets.size()); avg_boundary /= double(meshlets.size()); avg_connected /= double(meshlets.size()); printf("Meshlets%c: %d meshlets (avg vertices %.1f, avg triangles %.1f, avg boundary %.1f, avg connected %.2f, not full %d) in %.2f msec\n", scan ? 'S' : (flex ? 'F' : (spatial ? 'X' : (uniform ? 'U' : ' '))), int(meshlets.size()), avg_vertices, avg_triangles, avg_boundary, avg_connected, int(not_full), (end - start) * 1000); float camera[3] = {100, 100, 100}; size_t rejected = 0; size_t accepted = 0; double radius_mean = 0; double cone_mean = 0; std::vector radii(meshlets.size()); std::vector cones(meshlets.size()); double startc = timestamp(); for (size_t i = 0; i < meshlets.size(); ++i) { const meshopt_Meshlet& m = meshlets[i]; meshopt_Bounds bounds = meshopt_computeMeshletBounds(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], m.triangle_count, &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex)); radii[i] = bounds.radius; cones[i] = 90.f - acosf(bounds.cone_cutoff) * (180.f / 3.1415926f); radius_mean += radii[i]; cone_mean += cones[i]; // trivial accept: we can't ever backface cull this meshlet accepted += (bounds.cone_cutoff >= 1); // perspective projection: dot(normalize(cone_apex - camera_position), cone_axis) > cone_cutoff // alternative formulation for perspective projection that doesn't use apex (and uses cluster bounding sphere instead): // dot(normalize(center - camera_position), cone_axis) > cone_cutoff + radius / length(center - camera_position) float cview[3] = {bounds.center[0] - camera[0], bounds.center[1] - camera[1], bounds.center[2] - camera[2]}; float cviewlength = sqrtf(cview[0] * cview[0] + cview[1] * cview[1] + cview[2] * cview[2]); rejected += cview[0] * bounds.cone_axis[0] + cview[1] * bounds.cone_axis[1] + cview[2] * bounds.cone_axis[2] >= bounds.cone_cutoff * cviewlength + bounds.radius; } double endc = timestamp(); radius_mean /= double(meshlets.size()); cone_mean /= double(meshlets.size()); double radius_variance = 0; for (size_t i = 0; i < meshlets.size(); ++i) radius_variance += (radii[i] - radius_mean) * (radii[i] - radius_mean); radius_variance /= double(meshlets.size() - 1); double radius_stddev = sqrt(radius_variance); size_t meshlets_std = 0; for (size_t i = 0; i < meshlets.size(); ++i) meshlets_std += radii[i] < radius_mean + radius_stddev; printf("Bounds : radius mean %f stddev %f; %.1f%% meshlets under 1σ; cone angle %.1f°; cone reject %.1f%% trivial accept %.1f%% in %.2f msec\n", radius_mean, radius_stddev, double(meshlets_std) / double(meshlets.size()) * 100, cone_mean, double(rejected) / double(meshlets.size()) * 100, double(accepted) / double(meshlets.size()) * 100, (endc - startc) * 1000); } void spatialSort(const Mesh& mesh) { typedef PackedVertexOct PV; std::vector pv(mesh.vertices.size()); packMesh(pv, mesh.vertices); double start = timestamp(); std::vector remap(mesh.vertices.size()); meshopt_spatialSortRemap(&remap[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex)); double end = timestamp(); meshopt_remapVertexBuffer(&pv[0], &pv[0], mesh.vertices.size(), sizeof(PV), &remap[0]); std::vector vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV))); vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], mesh.vertices.size(), sizeof(PV))); size_t csize = compress(vbuf); printf("Spatial : %.1f bits/vertex (post-deflate %.1f bits/vertex); sort %.2f msec\n", double(vbuf.size() * 8) / double(mesh.vertices.size()), double(csize * 8) / double(mesh.vertices.size()), (end - start) * 1000); } void spatialSortTriangles(const Mesh& mesh) { typedef PackedVertexOct PV; Mesh copy = mesh; double start = timestamp(); meshopt_spatialSortTriangles(©.indices[0], ©.indices[0], mesh.indices.size(), ©.vertices[0].px, copy.vertices.size(), sizeof(Vertex)); double end = timestamp(); meshopt_optimizeVertexCache(©.indices[0], ©.indices[0], copy.indices.size(), copy.vertices.size()); meshopt_optimizeVertexFetch(©.vertices[0], ©.indices[0], copy.indices.size(), ©.vertices[0], copy.vertices.size(), sizeof(Vertex)); std::vector pv(mesh.vertices.size()); packMesh(pv, copy.vertices); std::vector vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV))); vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], mesh.vertices.size(), sizeof(PV))); std::vector ibuf(meshopt_encodeIndexBufferBound(mesh.indices.size(), mesh.vertices.size())); ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), ©.indices[0], mesh.indices.size())); size_t csizev = compress(vbuf); size_t csizei = compress(ibuf); printf("SpatialT : %.1f bits/vertex (post-deflate %.1f bits/vertex); %.1f bits/triangle (post-deflate %.1f bits/triangle); sort %.2f msec\n", double(vbuf.size() * 8) / double(mesh.vertices.size()), double(csizev * 8) / double(mesh.vertices.size()), double(ibuf.size() * 8) / double(mesh.indices.size() / 3), double(csizei * 8) / double(mesh.indices.size() / 3), (end - start) * 1000); } void spatialClusterPoints(const Mesh& mesh, size_t cluster_size) { typedef PackedVertexOct PV; double start = timestamp(); std::vector index(mesh.vertices.size()); meshopt_spatialClusterPoints(&index[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), cluster_size); double end = timestamp(); std::vector pv(mesh.vertices.size()); packMesh(pv, mesh.vertices); std::vector pvo(mesh.vertices.size()); for (size_t i = 0; i < index.size(); ++i) pvo[i] = pv[index[i]]; std::vector vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV))); vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pvo[0], mesh.vertices.size(), sizeof(PV))); size_t csize = compress(vbuf); printf("SpatialCP: %.1f bits/vertex (post-deflate %.1f bits/vertex); sort %.2f msec\n", double(vbuf.size() * 8) / double(mesh.vertices.size()), double(csize * 8) / double(mesh.vertices.size()), (end - start) * 1000); } void tessellationAdjacency(const Mesh& mesh) { double start = timestamp(); // 12 indices per input triangle std::vector tessib(mesh.indices.size() * 4); meshopt_generateTessellationIndexBuffer(&tessib[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex)); double middle = timestamp(); // 6 indices per input triangle std::vector adjib(mesh.indices.size() * 2); meshopt_generateAdjacencyIndexBuffer(&adjib[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex)); double end = timestamp(); printf("Tesselltn: %d patches in %.2f msec\n", int(mesh.indices.size() / 3), (middle - start) * 1000); printf("Adjacency: %d patches in %.2f msec\n", int(mesh.indices.size() / 3), (end - middle) * 1000); } void provoking(const Mesh& mesh) { double start = timestamp(); // worst case number of vertices: vertex count + triangle count std::vector pib(mesh.indices.size()); std::vector reorder(mesh.vertices.size() + mesh.indices.size() / 3); size_t pcount = meshopt_generateProvokingIndexBuffer(&pib[0], &reorder[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size()); reorder.resize(pcount); double end = timestamp(); // validate invariant: pib[i] == i/3 for provoking vertices for (size_t i = 0; i < mesh.indices.size(); i += 3) assert(pib[i] == i / 3); // validate invariant: reorder[pib[x]] == ib[x] modulo triangle rotation // note: this is technically not promised by the interface (it may reorder triangles!), it just happens to hold right now for (size_t i = 0; i < mesh.indices.size(); i += 3) { unsigned int a = mesh.indices[i + 0], b = mesh.indices[i + 1], c = mesh.indices[i + 2]; unsigned int ra = reorder[pib[i + 0]], rb = reorder[pib[i + 1]], rc = reorder[pib[i + 2]]; assert((a == ra && b == rb && c == rc) || (a == rb && b == rc && c == ra) || (a == rc && b == ra && c == rb)); } // best case number of vertices: max(vertex count, triangle count), assuming non-redundant indexing (all vertices are used) // note: this is a lower bound, and it's not theoretically possible on some meshes; // for example, a union of a flat shaded cube (12t 24v) and a smooth shaded icosahedron (20t 12v) will have 36 vertices and 32 triangles // however, the best case for that union is 44 vertices (24 cube vertices + 20 icosahedron vertices due to provoking invariant) size_t bestv = mesh.vertices.size() > mesh.indices.size() / 3 ? mesh.vertices.size() : mesh.indices.size() / 3; printf("Provoking: %d triangles / %d vertices (+%.1f%% extra) in %.2f msec\n", int(mesh.indices.size() / 3), int(pcount), double(pcount) / double(bestv) * 100.0 - 100.0, (end - start) * 1000); } static int reindexCompare(void* context, unsigned int lhs, unsigned int rhs) { const Vertex* vertices = static_cast(context); const Vertex& lv = vertices[lhs]; const Vertex& rv = vertices[rhs]; float ln = lv.nx * lv.nx + lv.ny * lv.ny + lv.nz * lv.nz; float rn = rv.nx * rv.nx + rv.ny * rv.ny + rv.nz * rv.nz; // 1/1024px UV tolerance, 3 degree normal tolerance return fabsf(lv.tx - rv.tx) < 1e-3f && fabsf(lv.ty - rv.ty) < 1e-3f && (lv.nx * rv.nx + lv.ny * rv.ny + lv.nz * rv.nz >= 0.9986f * sqrtf(ln * rn)); } void reindexFuzzy(const Mesh& mesh) { std::vector pv(mesh.vertices.size()); packMesh(pv, mesh.vertices); std::vector remap(mesh.vertices.size()); double start = timestamp(); size_t up = meshopt_generateVertexRemap(&remap[0], &mesh.indices[0], mesh.indices.size(), &pv[0], mesh.vertices.size(), sizeof(PackedVertex)); double middle = timestamp(); size_t uf = meshopt_generateVertexRemapCustom(&remap[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), reindexCompare, const_cast(&mesh.vertices[0])); double end = timestamp(); printf("ReindexQ : %d vertices => %d unique vertices in %.2f msec\n", int(mesh.vertices.size()), int(up), (middle - start) * 1000); printf("ReindexF : %d vertices => %d unique vertices in %.2f msec\n", int(mesh.vertices.size()), int(uf), (end - middle) * 1000); } void coverage(const Mesh& mesh) { double start = timestamp(); meshopt_CoverageStatistics cs = meshopt_analyzeCoverage(&mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex)); double end = timestamp(); printf("Coverage : X %.1f%% Y %.1f%% Z %.1f%% in %.2f msec\n", cs.coverage[0] * 100, cs.coverage[1] * 100, cs.coverage[2] * 100, (end - start) * 1000); } void nanite(const std::vector& vertices, const std::vector& indices); // nanite.cpp bool loadMesh(Mesh& mesh, const char* path) { double start = timestamp(); double middle; mesh = parseObj(path, middle); double end = timestamp(); if (mesh.vertices.empty()) { printf("Mesh %s is empty, skipping\n", path); return false; } printf("# %s: %d vertices, %d triangles; read in %.2f msec; indexed in %.2f msec\n", path, int(mesh.vertices.size()), int(mesh.indices.size() / 3), (middle - start) * 1000, (end - middle) * 1000); return true; } void processDeinterleaved(const char* path) { // Most algorithms in the library work out of the box with deinterleaved geometry, but some require slightly special treatment; // this code runs a simplified version of complete opt. pipeline using deinterleaved geo. There's no compression performed but you // can trivially run it by quantizing all elements and running meshopt_encodeVertexBuffer once for each vertex stream. fastObjMesh* obj = fast_obj_read(path); if (!obj) { printf("Error loading %s: file not found\n", path); return; } size_t total_indices = 0; for (unsigned int i = 0; i < obj->face_count; ++i) total_indices += 3 * (obj->face_vertices[i] - 2); std::vector unindexed_pos(total_indices * 3); std::vector unindexed_nrm(total_indices * 3); std::vector unindexed_uv(total_indices * 2); size_t vertex_offset = 0; size_t index_offset = 0; for (unsigned int i = 0; i < obj->face_count; ++i) { for (unsigned int j = 0; j < obj->face_vertices[i]; ++j) { fastObjIndex gi = obj->indices[index_offset + j]; // triangulate polygon on the fly; offset-3 is always the first polygon vertex if (j >= 3) { memcpy(&unindexed_pos[(vertex_offset + 0) * 3], &unindexed_pos[(vertex_offset - 3) * 3], 3 * sizeof(float)); memcpy(&unindexed_nrm[(vertex_offset + 0) * 3], &unindexed_nrm[(vertex_offset - 3) * 3], 3 * sizeof(float)); memcpy(&unindexed_uv[(vertex_offset + 0) * 2], &unindexed_uv[(vertex_offset - 3) * 2], 2 * sizeof(float)); memcpy(&unindexed_pos[(vertex_offset + 1) * 3], &unindexed_pos[(vertex_offset - 1) * 3], 3 * sizeof(float)); memcpy(&unindexed_nrm[(vertex_offset + 1) * 3], &unindexed_nrm[(vertex_offset - 1) * 3], 3 * sizeof(float)); memcpy(&unindexed_uv[(vertex_offset + 1) * 2], &unindexed_uv[(vertex_offset - 1) * 2], 2 * sizeof(float)); vertex_offset += 2; } memcpy(&unindexed_pos[vertex_offset * 3], &obj->positions[gi.p * 3], 3 * sizeof(float)); memcpy(&unindexed_nrm[vertex_offset * 3], &obj->normals[gi.n * 3], 3 * sizeof(float)); memcpy(&unindexed_uv[vertex_offset * 2], &obj->texcoords[gi.t * 2], 2 * sizeof(float)); vertex_offset++; } index_offset += obj->face_vertices[i]; } fast_obj_destroy(obj); double start = timestamp(); meshopt_Stream streams[] = { {&unindexed_pos[0], sizeof(float) * 3, sizeof(float) * 3}, {&unindexed_nrm[0], sizeof(float) * 3, sizeof(float) * 3}, {&unindexed_uv[0], sizeof(float) * 2, sizeof(float) * 2}, }; std::vector remap(total_indices); size_t total_vertices = meshopt_generateVertexRemapMulti(&remap[0], NULL, total_indices, total_indices, streams, sizeof(streams) / sizeof(streams[0])); std::vector indices(total_indices); meshopt_remapIndexBuffer(&indices[0], NULL, total_indices, &remap[0]); std::vector pos(total_vertices * 3); meshopt_remapVertexBuffer(&pos[0], &unindexed_pos[0], total_indices, sizeof(float) * 3, &remap[0]); std::vector nrm(total_vertices * 3); meshopt_remapVertexBuffer(&nrm[0], &unindexed_nrm[0], total_indices, sizeof(float) * 3, &remap[0]); std::vector uv(total_vertices * 2); meshopt_remapVertexBuffer(&uv[0], &unindexed_uv[0], total_indices, sizeof(float) * 2, &remap[0]); double reindex = timestamp(); meshopt_optimizeVertexCache(&indices[0], &indices[0], total_indices, total_vertices); meshopt_optimizeVertexFetchRemap(&remap[0], &indices[0], total_indices, total_vertices); meshopt_remapVertexBuffer(&pos[0], &pos[0], total_vertices, sizeof(float) * 3, &remap[0]); meshopt_remapVertexBuffer(&nrm[0], &nrm[0], total_vertices, sizeof(float) * 3, &remap[0]); meshopt_remapVertexBuffer(&uv[0], &uv[0], total_vertices, sizeof(float) * 2, &remap[0]); double optimize = timestamp(); // note: since shadow index buffer is computed based on regular vertex/index buffer, the stream points at the indexed data - not unindexed_pos meshopt_Stream shadow_stream = {&pos[0], sizeof(float) * 3, sizeof(float) * 3}; std::vector shadow_indices(total_indices); meshopt_generateShadowIndexBufferMulti(&shadow_indices[0], &indices[0], total_indices, total_vertices, &shadow_stream, 1); meshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], total_indices, total_vertices); double shadow = timestamp(); printf("Deintrlvd: %d vertices, reindexed in %.2f msec, optimized in %.2f msec, generated & optimized shadow indices in %.2f msec\n", int(total_vertices), (reindex - start) * 1000, (optimize - reindex) * 1000, (shadow - optimize) * 1000); } void process(const char* path) { Mesh mesh; if (!loadMesh(mesh, path)) return; optimize(mesh); optimize(mesh, /* fifo= */ true); Mesh copy = mesh; meshopt_optimizeVertexCache(©.indices[0], ©.indices[0], copy.indices.size(), copy.vertices.size()); meshopt_optimizeVertexFetch(©.vertices[0], ©.indices[0], copy.indices.size(), ©.vertices[0], copy.vertices.size(), sizeof(Vertex)); Mesh copystrip = mesh; meshopt_optimizeVertexCacheStrip(©strip.indices[0], ©strip.indices[0], copystrip.indices.size(), copystrip.vertices.size()); meshopt_optimizeVertexFetch(©strip.vertices[0], ©strip.indices[0], copystrip.indices.size(), ©strip.vertices[0], copystrip.vertices.size(), sizeof(Vertex)); stripify(copy, false, ' '); stripify(copy, /* use_restart= */ true, 'R'); stripify(copystrip, /* use_restart= */ true, 'S'); meshlets(copy, /* scan= */ true); meshlets(copy, /* scan= */ false); meshlets(copy, /* scan= */ false, /* uniform= */ true); meshlets(copy, /* scan= */ false, /* uniform= */ false, /* flex= */ true); meshlets(copy, /* scan= */ false, /* uniform= */ true, /* flex= */ false, /* spatial= */ true); shadow(copy); tessellationAdjacency(copy); provoking(copy); encodeIndex(copy, ' '); encodeIndex(copystrip, 'S'); std::vector strip(meshopt_stripifyBound(copystrip.indices.size())); strip.resize(meshopt_stripify(&strip[0], ©strip.indices[0], copystrip.indices.size(), copystrip.vertices.size(), 0)); encodeIndexSequence(strip, copystrip.vertices.size(), 'D'); packVertex(copy, ""); encodeVertex(copy, ""); encodeVertex(copy, "O"); encodeMeshlets(mesh, 64, 96); simplify(mesh); simplify(mesh, 0.1f, meshopt_SimplifyPrune); simplifyAttr(mesh); simplifyAttr(mesh, 0.1f, meshopt_SimplifyPermissive); simplifyUpdate(mesh); simplifyUpdate(mesh, 0.1f, meshopt_SimplifyPermissive); simplifySloppy(mesh); simplifyComplete(mesh); simplifyPoints(mesh); simplifyClusters(mesh); spatialSort(mesh); spatialSortTriangles(mesh); spatialClusterPoints(mesh, 64); reindexFuzzy(mesh); coverage(mesh); if (path) processDeinterleaved(path); } void processDev(const char* path) { Mesh mesh; if (!loadMesh(mesh, path)) return; encodeMeshlets(mesh, 32, 48); encodeMeshlets(mesh, 64, 64); encodeMeshlets(mesh, 64, 96); } void processNanite(const char* path) { Mesh mesh; if (!loadMesh(mesh, path)) return; nanite(mesh.vertices, mesh.indices); } int main(int argc, char** argv) { void runTests(); if (argc == 1) { runTests(); } else { if (strcmp(argv[1], "-d") == 0) { for (int i = 2; i < argc; ++i) processDev(argv[i]); } else if (strcmp(argv[1], "-n") == 0) { for (int i = 2; i < argc; ++i) processNanite(argv[i]); } else { for (int i = 1; i < argc; ++i) process(argv[i]); runTests(); } } } ================================================ FILE: demo/meshletdec.slang ================================================ /** * meshletdec.slang - an example GPU decoder for meshlet data encoded using meshopt_encodeMeshlet * This is intended to be used as a starting point for applications that want to decode meshlet data on the GPU. * * The shader exposes an entrypoint, decodeMeshlets, that decodes a set of meshlets; each meshlet is decoded independently, * and the output vertex/triangle data is written as uint32 per element (triangle data is written as 0xccbbaa). * This matches the output format for meshopt_decodeMeshlet with vertex_size=4 triangle_size=4. If alternative formats are * needed, the code should be changed to output them; note that for triangle data, it may make sense to output data to shared * memory to be able to use larger aligned 32-bit writes to global memory after that. * * Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * This code is distributed under the MIT License. See notice at the end of this file. */ struct MeshletDesc { uint stream_offset; uint output_offset; uint16_t encoded_size; uint8_t vertex_count; uint8_t triangle_count; }; [[vk::binding(0)]] StructuredBuffer gStream : register(t0); [[vk::binding(1)]] StructuredBuffer gMeshlets : register(t1); [[vk::binding(2)]] RWStructuredBuffer gOutput : register(u2); [[vk::binding(3)]] cbuffer MeshletConfigCB : register(b3) { uint gMeshletCount; } uint decodeVertices(uint out_vertices, uint ctrl, uint data, uint bound, uint vertex_count) { uint last = ~0u; for (uint i = 0; i < vertex_count; i += 4) { if (data > bound) return ~0u; uint code4 = uint(gStream[ctrl + i / 4]); for (int k = 0; k < 4; ++k) { int code = ((code4 >> k) & 1) | ((code4 >> (k + 3)) & 2); int length = code4 == 0xff ? 4 : code; // branchlessly read up to 4 bytes uint mask = (length == 4) ? ~0u : (1 << (8 * length)) - 1; uint v = (uint(gStream[data + 0]) | (uint(gStream[data + 1]) << 8) | (uint(gStream[data + 2]) << 16) | (uint(gStream[data + 3]) << 24)) & mask; // unzigzag + 1 uint d = (v >> 1) ^ -int(v & 1); uint r = last + d + 1; if (i + k < vertex_count) gOutput[out_vertices + i + k] = r; data += length; last = r; } } return data; } uint decodeTriangle(uint code, uint extra0, uint extra1, uint extra2, inout uint fifo0, inout uint fifo1, inout uint fifo2, inout uint next, inout uint extra) { // reuse: 0-1 extra vertices uint fifo = code < 4 ? fifo0 : (code < 8 ? fifo1 : fifo2); uint edge = fifo >> ((code << 3) & 16); // shift by 16 if bit 1 is set (odd edge for each triangle) uint c_reuse = (code & 1) == 1 ? extra0 : next; // restart: 0-3 extra vertices uint extran = code & 3; uint a = extran > 0 ? extra0 : next; uint b = extran > 1 ? extra1 : next + (1 - extran); uint c = extran > 2 ? extra2 : next + (2 - extran); // select between reuse and restart and repack triangle into edge format (0xcbac) a = code >= 12 ? a : (edge >> 8) & 0xff; b = code >= 12 ? b : edge & 0xff; c = code >= 12 ? c : c_reuse; uint tri = c | (a << 8) | (b << 16) | (c << 24); // advance next/extra; reuse codes use 1 lsb for extra count, restart codes use 2 lsbs uint extrab = code < 12 ? 1 : 3; next += extrab - code & extrab; extra += code & extrab; // rotate fifo fifo2 = fifo1; fifo1 = fifo0; fifo0 = tri; // output triangle is stored without extra edge vertex (0xcbac => 0xcba) return tri >> 8; } uint decodeTriangles(uint out_triangles, uint codes, uint extra, uint bound, uint triangle_count) { uint next = 0; uint fifo0 = 0, fifo1 = 0, fifo2 = 0; // two edge fifo entries in one uint: 0xcbac for (uint i = 0; i < triangle_count; i += 2) { if (extra > bound) return ~0u; uint codeg = uint(gStream[codes + i / 2]); // first triangle uint extra0 = uint(gStream[extra + 0]); uint extra1 = uint(gStream[extra + 1]); uint extra2 = uint(gStream[extra + 2]); uint tri = decodeTriangle(codeg & 15, extra0, extra1, extra2, fifo0, fifo1, fifo2, next, extra); gOutput[out_triangles + i] = tri; // second triangle, if any extra0 = uint(gStream[extra + 0]); extra1 = uint(gStream[extra + 1]); extra2 = uint(gStream[extra + 2]); tri = decodeTriangle(codeg >> 4, extra0, extra1, extra2, fifo0, fifo1, fifo2, next, extra); if (i + 1 < triangle_count) gOutput[out_triangles + i + 1] = tri; } return extra; } int decodeMeshlet(uint out_vertices, uint vertex_count, uint out_triangles, uint triangle_count, uint buffer, uint buffer_size) { uint codes_size = (triangle_count + 1) / 2; uint ctrl_size = (vertex_count + 3) / 4; uint gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0; if (buffer_size < codes_size + ctrl_size + gap_size) return -2; uint end = buffer + buffer_size; uint codes = end - codes_size; uint ctrl = codes - ctrl_size; uint data = buffer; // gap ensures we have at least 16 bytes available after bound; this allows decoder to over-read safely uint bound = ctrl - gap_size; data = decodeVertices(out_vertices, ctrl, data, bound, vertex_count); if (data == ~0u) return -2; data = decodeTriangles(out_triangles, codes, data, bound, triangle_count); if (data == ~0u) return -2; return (data == bound) ? 0 : -3; } [shader("compute")] [numthreads(32, 1, 1)] void decodeMeshlets(uint3 dispatch_thread_id: SV_DispatchThreadID) { uint meshlet_count = gMeshletCount; uint tid = dispatch_thread_id.x; if (tid >= meshlet_count) return; MeshletDesc desc = gMeshlets[tid]; uint out_vertices = desc.output_offset; uint out_triangles = desc.output_offset + uint(desc.vertex_count); int rc = decodeMeshlet(out_vertices, uint(desc.vertex_count), out_triangles, uint(desc.triangle_count), desc.stream_offset, uint(desc.encoded_size)); // if decoding failed, we write 0xff.. to the first word of the output data // this can be adjusted arbitrarily; for example, a separate buffer with a single status for the entire stream could be used // note that decoding fails only if the input data is corrupt; so this may not be required at all depending on the requirements if (rc < 0) gOutput[desc.output_offset] = ~0u; } /** * Copyright (c) 2016-2026 Arseny Kapoulkine * * 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. */ ================================================ FILE: demo/nanite.cpp ================================================ // For reference, see the original Nanite paper: // Brian Karis. Nanite: A Deep Dive. 2021 #include "../src/meshoptimizer.h" #include #include #include #include #include #include #define CLUSTERLOD_IMPLEMENTATION #include "clusterlod.h" struct Vertex { float px, py, pz; float nx, ny, nz; float tx, ty; }; // computes approximate (perspective) projection error of a cluster in screen space (0..1; multiply by screen height to get pixels) // camera_proj is projection[1][1], or cot(fovy/2); camera_znear is *positive* near plane distance // for DAG cut to be valid, boundsError must be monotonic: it must return a larger error for parent cluster // for simplicity, we ignore perspective distortion and use rotationally invariant projection size estimation static float boundsError(const clodBounds& bounds, float camera_x, float camera_y, float camera_z, float camera_proj, float camera_znear) { float dx = bounds.center[0] - camera_x, dy = bounds.center[1] - camera_y, dz = bounds.center[2] - camera_z; float d = sqrtf(dx * dx + dy * dy + dz * dz) - bounds.radius; return bounds.error / (d > camera_znear ? d : camera_znear) * (camera_proj * 0.5f); } static int measureComponents(std::vector& parents, const std::vector& indices, const std::vector& remap); static size_t measureBoundary(std::vector& used, const std::vector >& clusters, const std::vector& remap); static float sahOverhead(const std::vector >& clusters, const std::vector& vertices); void dumpObj(const std::vector& vertices, const std::vector& indices, bool recomputeNormals = false); void dumpObj(const char* section, const std::vector& indices); void nanite(const std::vector& vertices, const std::vector& indices) { #ifdef _MSC_VER static const char* dump = NULL; // tired of C4996 static const char* clrt = NULL; #else static const char* dump = getenv("DUMP"); static const char* clrt = getenv("CLRT"); #endif clodConfig config = clodDefaultConfig(/*max_triangles=*/128); if (clrt) { config = clodDefaultConfigRT(config.max_triangles); config.cluster_fill_weight = float(atof(clrt)); } const float attribute_weights[3] = {0.5f, 0.5f, 0.5f}; clodMesh mesh = {}; mesh.indices = indices.data(); mesh.index_count = indices.size(); mesh.vertex_count = vertices.size(); mesh.vertex_positions = &vertices[0].px; mesh.vertex_positions_stride = sizeof(Vertex); mesh.vertex_attributes = &vertices[0].nx; mesh.vertex_attributes_stride = sizeof(Vertex); mesh.attribute_weights = attribute_weights; mesh.attribute_count = sizeof(attribute_weights) / sizeof(attribute_weights[0]); mesh.attribute_protect_mask = (1 << 3) | (1 << 4); // protect UV seams, maps to Vertex::tx/ty struct Stats { size_t groups; size_t clusters; size_t triangles; size_t vertices; size_t full_clusters; size_t singleton_groups; size_t stuck_clusters; size_t stuck_triangles; double radius; std::vector > indices; // for detailed connectivity analysis }; std::vector stats; std::vector groups; std::vector > cut; int cut_level = dump ? atoi(dump) : -2; // for testing purposes, we can compute a DAG cut from a given viewpoint and dump it as an OBJ float maxx = 0.f, maxy = 0.f, maxz = 0.f; for (size_t i = 0; i < vertices.size(); ++i) { maxx = std::max(maxx, vertices[i].px * 2); maxy = std::max(maxy, vertices[i].py * 2); maxz = std::max(maxz, vertices[i].pz * 2); } float threshold = 2e-3f; // 2 pixels at 1080p float fovy = 60.f; float znear = 1e-2f; float proj = 1.f / tanf(fovy * 3.1415926f / 180.f * 0.5f); clodBuild(config, mesh, [&](clodGroup group, const clodCluster* clusters, size_t cluster_count) -> int { // clang-format! if (stats.size() <= size_t(group.depth)) stats.push_back({}); Stats& level = stats[group.depth]; level.groups++; level.clusters += cluster_count; if (group.simplified.error == FLT_MAX) level.stuck_clusters += cluster_count; level.singleton_groups += cluster_count == 1; for (size_t i = 0; i < cluster_count; ++i) { const clodCluster& cluster = clusters[i]; level.triangles += cluster.index_count / 3; if (group.simplified.error == FLT_MAX) level.stuck_triangles += cluster.index_count / 3; level.vertices += cluster.vertex_count; level.full_clusters += (cluster.index_count == config.max_triangles * 3); level.radius += cluster.bounds.radius; level.indices.push_back(std::vector(cluster.indices, cluster.indices + cluster.index_count)); // when requesting DAG cut at a given level, we need to render all terminal clusters at lower depth as well if (cut_level >= 0 && (group.depth == cut_level || (group.depth < cut_level && group.simplified.error == FLT_MAX))) cut.push_back(std::vector(cluster.indices, cluster.indices + cluster.index_count)); // when requesting DAG cut from a viewpoint, we need to check if each cluster is the least detailed cluster that passes the error threshold if (cut_level == -1 && (cluster.refined < 0 || boundsError(groups[cluster.refined], maxx, maxy, maxz, proj, znear) <= threshold) && boundsError(group.simplified, maxx, maxy, maxz, proj, znear) > threshold) cut.push_back(std::vector(cluster.indices, cluster.indices + cluster.index_count)); } level.indices.push_back(std::vector()); // mark end of group for measureBoundary groups.push_back(group.simplified); return int(groups.size() - 1); }); // for cluster connectivity analysis and boundary statistics, we need a position-only remap that maps vertices with the same position to the same index std::vector remap(vertices.size()); meshopt_generatePositionRemap(&remap[0], &vertices[0].px, vertices.size(), sizeof(Vertex)); std::vector used(vertices.size()); size_t lowest_clusters = 0; size_t lowest_triangles = 0; for (size_t i = 0; i < stats.size(); ++i) { Stats& level = stats[i]; lowest_clusters += level.stuck_clusters; lowest_triangles += level.stuck_triangles; size_t connected = 0; for (const auto& cluster : level.indices) connected += measureComponents(used, cluster, remap); size_t boundary = measureBoundary(used, level.indices, remap); float saho = clrt ? sahOverhead(level.indices, vertices) : 0.f; double inv_clusters = 1.0 / double(level.clusters); printf("lod %d: %d clusters (%.1f%% full, %.1f tri/cl, %.1f vtx/cl, %.2f connected, %.1f boundary, %.1f partition, %d singletons, %.3f sah overhead, %f radius), %d triangles", int(i), int(level.clusters), double(level.full_clusters) * inv_clusters * 100, double(level.triangles) * inv_clusters, double(level.vertices) * inv_clusters, double(connected) * inv_clusters, double(boundary) * inv_clusters, double(level.clusters) / double(level.groups), int(level.singleton_groups), saho, level.radius * inv_clusters, int(level.triangles)); if (level.stuck_clusters && level.clusters > 1) printf("; stuck %d clusters (%d triangles)", int(level.stuck_clusters), int(level.stuck_triangles)); printf("\n"); } printf("lowest lod: %d clusters, %d triangles\n", int(lowest_clusters), int(lowest_triangles)); if (cut_level >= -1) { size_t cut_tris = 0; for (auto& cluster : cut) cut_tris += cluster.size() / 3; if (cut_level >= 0) printf("cut (level %d): %d triangles\n", cut_level, int(cut_tris)); else printf("cut (error %.3f): %d triangles\n", threshold, int(cut_tris)); dumpObj(vertices, std::vector()); for (auto& cluster : cut) dumpObj("cluster", cluster); } } // What follows is code that is helpful for collecting metrics, visualizing cuts, etc. // This code is not used in the actual clustering implementation and can be ignored. static int follow(std::vector& parents, int index) { while (index != parents[index]) { int parent = parents[index]; parents[index] = parents[parent]; index = parent; } return index; } static int measureComponents(std::vector& parents, const std::vector& indices, const std::vector& remap) { assert(parents.size() == remap.size()); for (size_t i = 0; i < indices.size(); ++i) { unsigned int v = remap[indices[i]]; parents[v] = v; } for (size_t i = 0; i < indices.size(); ++i) { int v0 = remap[indices[i]]; int v1 = remap[indices[i + (i % 3 == 2 ? -2 : 1)]]; v0 = follow(parents, v0); v1 = follow(parents, v1); parents[v0] = v1; } for (size_t i = 0; i < indices.size(); ++i) { unsigned int v = remap[indices[i]]; parents[v] = follow(parents, v); } int roots = 0; for (size_t i = 0; i < indices.size(); ++i) { unsigned int v = remap[indices[i]]; roots += parents[v] == int(v); parents[v] = -1; // make sure we only count each root once } return roots; } static size_t measureBoundary(std::vector& used, const std::vector >& clusters, const std::vector& remap) { for (size_t i = 0; i < used.size(); ++i) used[i] = -1; // mark vertices that are used by multiple groups with -2 int group = 0; for (size_t i = 0; i < clusters.size(); ++i) { group += clusters[i].empty(); for (size_t j = 0; j < clusters[i].size(); ++j) { unsigned int v = remap[clusters[i][j]]; used[v] = (used[v] == -1 || used[v] == group) ? group : -2; } } size_t result = 0; for (size_t i = 0; i < clusters.size(); ++i) { // count vertices that are used by multiple groups and change marks to -1 for (size_t j = 0; j < clusters[i].size(); ++j) { unsigned int v = remap[clusters[i][j]]; result += (used[v] == -2); used[v] = (used[v] == -2) ? -1 : used[v]; } // change marks back from -1 to -2 for the next pass for (size_t j = 0; j < clusters[i].size(); ++j) { unsigned int v = remap[clusters[i][j]]; used[v] = (used[v] == -1) ? -2 : used[v]; } } return int(result); } struct Box { float min[3]; float max[3]; }; static const Box kDummyBox = {{FLT_MAX, FLT_MAX, FLT_MAX}, {-FLT_MAX, -FLT_MAX, -FLT_MAX}}; static void mergeBox(Box& box, const Box& other) { for (int k = 0; k < 3; ++k) { box.min[k] = other.min[k] < box.min[k] ? other.min[k] : box.min[k]; box.max[k] = other.max[k] > box.max[k] ? other.max[k] : box.max[k]; } } inline float surface(const Box& box) { float sx = box.max[0] - box.min[0], sy = box.max[1] - box.min[1], sz = box.max[2] - box.min[2]; return sx * sy + sx * sz + sy * sz; } static float sahCost(const Box* boxes, unsigned int* order, unsigned int* temp, size_t count) { Box total = boxes[order[0]]; for (size_t i = 1; i < count; ++i) mergeBox(total, boxes[order[i]]); int best_axis = -1; int best_bin = -1; float best_cost = FLT_MAX; const int kBins = 15; for (int axis = 0; axis < 3; ++axis) { Box bins[kBins]; unsigned int counts[kBins] = {}; float extent = total.max[axis] - total.min[axis]; if (extent <= 0.f) continue; for (int i = 0; i < kBins; ++i) bins[i] = kDummyBox; for (size_t i = 0; i < count; ++i) { unsigned int index = order[i]; float p = (boxes[index].min[axis] + boxes[index].max[axis]) * 0.5f; int bin = int((p - total.min[axis]) / extent * (kBins - 1) + 0.5f); assert(bin >= 0 && bin < kBins); mergeBox(bins[bin], boxes[index]); counts[bin]++; } Box laccum = kDummyBox, raccum = kDummyBox; size_t lcount = 0, rcount = 0; float costs[kBins] = {}; for (int i = 0; i < kBins - 1; ++i) { mergeBox(laccum, bins[i]); mergeBox(raccum, bins[kBins - 1 - i]); lcount += counts[i]; costs[i] += lcount ? surface(laccum) * lcount : 0.f; rcount += counts[kBins - 1 - i]; costs[kBins - 2 - i] += rcount ? surface(raccum) * rcount : 0.f; } for (int i = 0; i < kBins - 1; ++i) if (costs[i] < best_cost) { best_cost = costs[i]; best_bin = i; best_axis = axis; } } if (best_axis == -1) return surface(total) * float(count); float best_extent = total.max[best_axis] - total.min[best_axis]; size_t offset0 = 0, offset1 = count; for (size_t i = 0; i < count; ++i) { unsigned int index = order[i]; float p = (boxes[index].min[best_axis] + boxes[index].max[best_axis]) * 0.5f; int bin = int((p - total.min[best_axis]) / best_extent * (kBins - 1) + 0.5f); assert(bin >= 0 && bin < kBins); if (bin <= best_bin) temp[offset0++] = index; else temp[--offset1] = index; } assert(offset0 == offset1); if (offset0 == 0 || offset0 == count) return surface(total) * float(count); return surface(total) + sahCost(boxes, temp, order, offset0) + sahCost(boxes, temp + offset0, order + offset0, count - offset0); } static float sahCost(const Box* boxes, size_t count) { if (count == 0) return 0.f; std::vector order(count); for (size_t i = 0; i < count; ++i) order[i] = unsigned(i); std::vector temp(count); return sahCost(boxes, &order[0], &temp[0], count); } static float sahOverhead(const std::vector >& clusters, const std::vector& vertices) { std::vector all_tris, cluster_tris, cluster_boxes; float sahc = 0.f; for (size_t i = 0; i < clusters.size(); ++i) { if (clusters[i].empty()) continue; cluster_tris.clear(); Box cluster_box = kDummyBox; for (size_t k = 0; k < clusters[i].size(); k += 3) { Box box = kDummyBox; for (int v = 0; v < 3; ++v) { const Vertex& vertex = vertices[clusters[i][k + v]]; Box p = {{vertex.px, vertex.py, vertex.pz}, {vertex.px, vertex.py, vertex.pz}}; mergeBox(box, p); } mergeBox(cluster_box, box); all_tris.push_back(box); cluster_tris.push_back(box); } cluster_boxes.push_back(cluster_box); sahc += sahCost(&cluster_tris[0], cluster_tris.size()); sahc -= surface(cluster_box); // box will be accounted for in tlas } sahc += sahCost(&cluster_boxes[0], cluster_boxes.size()); float saht = sahCost(all_tris.data(), all_tris.size()); return sahc / saht; } ================================================ FILE: demo/pirate.obj ================================================ # Blender v2.79 (sub 0) OBJ File: 'pirate.blend' # www.blender.org # Pirate by Clint Bellanger # https://opengameart.org/content/pirate # Distributed under CC-BY-SA 3.0 mtllib pirate.mtl o Pirate_Sphere.001 v -0.086616 1.541207 0.048687 v 0.000000 1.521174 0.062100 v 0.086616 1.541207 0.048687 v -0.086616 1.467515 0.104849 v -0.000000 1.460505 0.104473 v 0.086616 1.467515 0.104849 v -0.075497 1.360887 0.136036 v 0.000000 1.353877 0.135660 v 0.075497 1.360887 0.136036 v -0.062354 1.259913 0.142685 v 0.000000 1.259005 0.147301 v 0.062354 1.259913 0.142685 v 0.000000 1.173866 0.151424 v 0.058120 1.175055 0.151800 v -0.058120 1.175056 0.151800 v 0.236368 0.092607 -0.020215 v 0.244447 0.050649 0.055733 v 0.160035 0.050649 0.068862 v 0.158087 0.096969 -0.009511 v 0.231907 0.050962 0.096377 v 0.171927 0.050962 0.109907 v 0.198856 0.056750 0.063817 v 0.200434 0.099614 -0.008311 v 0.203658 0.053735 0.117253 v 0.228066 0.128577 -0.048921 v 0.191707 0.128577 -0.030516 v 0.155310 0.128577 -0.040374 v 0.243809 0.048939 -0.062636 v 0.259032 0.034710 0.052294 v 0.229486 0.128577 -0.086203 v 0.215595 0.128577 -0.123264 v 0.215978 0.054773 -0.140594 v 0.166330 0.128577 -0.141207 v 0.168375 0.056821 -0.160469 v 0.138088 0.128577 -0.118289 v 0.138284 0.053982 -0.136358 v 0.140126 0.128577 -0.082307 v 0.145418 0.049313 -0.053791 v 0.142801 0.033877 0.063946 v 0.241657 0.038506 0.108880 v 0.163953 0.037606 0.125343 v 0.203658 0.036372 0.132175 v 0.240419 0.028025 -0.064598 v 0.255085 0.016737 0.039452 v 0.214742 0.028524 -0.135022 v 0.170136 0.028524 -0.153646 v 0.141940 0.028524 -0.131052 v 0.153248 0.027295 -0.055831 v 0.145661 0.016737 0.050411 v 0.238718 0.016737 0.097813 v 0.164969 0.016737 0.113922 v 0.202729 0.016737 0.120323 v 0.242952 0.028025 -0.066538 v 0.258034 0.016737 0.042949 v 0.215666 0.028524 -0.141348 v 0.168821 0.028524 -0.160906 v 0.139209 0.028524 -0.137178 v 0.150897 0.027295 -0.057712 v 0.143524 0.016737 0.054426 v 0.240914 0.016737 0.103692 v 0.164210 0.016737 0.120066 v 0.203423 0.016737 0.126789 v 0.258034 -0.001088 0.025696 v 0.242952 0.013555 -0.070778 v 0.215666 0.014054 -0.141348 v 0.168821 0.014054 -0.160906 v 0.139209 0.014054 -0.137178 v 0.150897 0.012826 -0.061952 v 0.143524 -0.001088 0.037173 v 0.240914 -0.000249 0.103692 v 0.164210 -0.000249 0.120066 v 0.203423 -0.000249 0.126789 v 0.242952 -0.000311 -0.070778 v 0.215666 0.000188 -0.141348 v 0.168821 0.000188 -0.160906 v 0.139209 0.000188 -0.137178 v 0.150897 -0.001041 -0.061952 v 0.240407 0.067287 0.012779 v 0.157989 0.070033 0.027636 v 0.199645 0.074798 0.025643 v 0.254610 0.041825 -0.006398 v 0.148123 0.042231 -0.000025 v 0.250941 0.022381 -0.013800 v 0.154169 0.022381 -0.007738 v 0.253682 0.022381 -0.013022 v 0.151925 0.022381 -0.006671 v 0.253682 0.005814 -0.021702 v 0.151925 0.005814 -0.015351 v 0.218563 0.209613 -0.046429 v 0.188733 0.208412 -0.021345 v 0.148752 0.208412 -0.032174 v 0.230231 0.208412 -0.082515 v 0.214972 0.208412 -0.125426 v 0.160857 0.208412 -0.142935 v 0.129834 0.208412 -0.119961 v 0.132073 0.208412 -0.078236 v 0.184486 0.296737 -0.005237 v 0.225084 0.299002 -0.034678 v 0.140596 0.296697 -0.019914 v 0.236935 0.298150 -0.081582 v 0.217650 0.297630 -0.136721 v 0.158346 0.295603 -0.157599 v 0.110046 0.294732 -0.129811 v 0.112876 0.294808 -0.076172 v 0.180417 0.388284 0.013613 v 0.219940 0.392383 -0.019552 v 0.129895 0.386004 -0.002073 v 0.236443 0.395326 -0.069991 v 0.219625 0.400030 -0.135825 v 0.144725 0.400515 -0.158651 v 0.091043 0.394958 -0.126268 v 0.093351 0.389803 -0.062979 v 0.141647 0.491328 -0.114189 v 0.205701 0.490563 -0.094984 v 0.172100 0.480332 0.031533 v 0.088130 0.486878 -0.030961 v 0.124555 0.485816 0.028877 v 0.085756 0.492816 -0.089075 v 0.138819 0.500966 -0.123683 v 0.212734 0.500023 -0.101320 v 0.229349 0.492623 -0.040656 v 0.212929 0.490855 0.006320 v 0.174315 0.487937 0.044232 v 0.078510 0.469649 -0.030733 v 0.118959 0.469167 0.037975 v 0.075626 0.476194 -0.096815 v 0.135258 0.486616 -0.136305 v 0.219656 0.487755 -0.110271 v 0.239055 0.480310 -0.041331 v 0.221618 0.477597 0.013127 v 0.176187 0.472900 0.055532 v 0.073827 0.311152 -0.055639 v 0.116017 0.309592 0.028478 v 0.066537 0.324125 -0.137977 v 0.244434 0.339411 -0.148584 v 0.277813 0.321962 -0.063759 v 0.262464 0.316457 0.009962 v 0.191486 0.312070 0.054821 v 0.178630 0.357606 -0.193864 v 0.123421 0.350867 -0.185934 v 0.118294 0.398249 0.026084 v 0.078910 0.396001 -0.044382 v 0.074782 0.405104 -0.114011 v 0.225539 0.420431 -0.125449 v 0.254013 0.412894 -0.052038 v 0.236933 0.409543 0.008791 v 0.180250 0.404852 0.047154 v 0.153892 0.427512 -0.158986 v 0.129930 0.424512 -0.155542 v 0.204864 0.482881 -0.001814 v 0.129203 0.478420 0.018134 v 0.219844 0.485703 -0.041760 v 0.095093 0.485847 -0.085518 v 0.097055 0.480678 -0.033262 v 0.058986 1.778749 -0.098358 v 0.079459 1.777951 -0.069506 v 0.089357 1.778162 -0.031658 v 0.086722 1.779085 0.007426 v 0.072214 1.784068 0.043280 v 0.056289 1.778569 0.078209 v 0.030933 1.778055 0.100438 v 0.000029 1.777957 0.107919 v -0.030113 1.780207 0.099812 v -0.055386 1.784491 0.077437 v -0.075219 1.785706 0.045458 v -0.088151 1.781940 0.008471 v -0.090065 1.778396 -0.031092 v -0.079190 1.777552 -0.068779 v -0.058930 1.778485 -0.099223 v -0.031333 1.780597 -0.112832 v 0.000044 1.782147 -0.114884 v 0.031409 1.780958 -0.111651 v 0.058347 1.849556 -0.086516 v 0.078385 1.847862 -0.063548 v 0.086845 1.848279 -0.030617 v 0.083311 1.849049 0.003948 v 0.071195 1.849480 0.037787 v 0.053178 1.848875 0.067663 v 0.029426 1.848109 0.088584 v 0.000478 1.848497 0.095845 v -0.027984 1.853117 0.087607 v -0.052069 1.858957 0.066812 v -0.071600 1.860267 0.037959 v -0.084706 1.855989 0.004879 v -0.087909 1.850094 -0.029810 v -0.078406 1.847763 -0.063610 v -0.058167 1.849198 -0.087595 v -0.030896 1.852027 -0.097518 v 0.000108 1.853595 -0.099203 v 0.031101 1.852409 -0.096369 v 0.065910 1.863193 -0.055048 v 0.049138 1.864533 -0.074499 v 0.073086 1.863481 -0.027452 v 0.070484 1.864051 0.001907 v 0.060498 1.864257 0.030547 v 0.045081 1.863910 0.055529 v 0.024683 1.863350 0.072867 v 0.000521 1.863915 0.078932 v -0.023521 1.867598 0.072098 v -0.044097 1.871953 0.054804 v -0.060628 1.872897 0.030575 v -0.071485 1.869625 0.002596 v -0.073853 1.865274 -0.026848 v -0.065981 1.863099 -0.055126 v -0.048960 1.864257 -0.075334 v -0.026002 1.866220 -0.084925 v 0.000102 1.867361 -0.086874 v 0.026196 1.866493 -0.084095 v 0.000089 1.863019 -0.010396 v 0.163089 1.835772 -0.135080 v 0.189568 1.760501 -0.131727 v -0.008953 1.770214 0.212054 v -0.203693 1.757624 -0.117194 v -0.163052 1.839092 -0.132407 v -0.081971 1.883991 -0.105049 v 0.000092 1.900686 -0.082792 v 0.082159 1.884257 -0.104242 v 0.142218 1.770391 -0.134324 v 0.074281 1.779322 -0.133913 v 0.000114 1.782525 -0.124317 v -0.074089 1.778930 -0.135083 v -0.141499 1.772850 -0.124412 v 0.178914 1.767742 -0.065645 v 0.146196 1.778066 -0.011723 v 0.101668 1.784760 0.048946 v 0.072940 1.777907 0.117313 v 0.029853 1.778522 0.166702 v -0.042368 1.777305 0.168503 v -0.071558 1.781615 0.116535 v -0.105547 1.786529 0.050382 v -0.147493 1.778899 -0.010879 v -0.179551 1.768847 -0.059894 v 0.192030 1.774595 -0.157698 v -0.196140 1.771098 -0.150907 v -0.148438 1.795914 -0.157062 v -0.077901 1.801581 -0.157771 v 0.000121 1.806535 -0.143703 v 0.078096 1.802044 -0.156394 v 0.148773 1.792710 -0.160503 v 0.055593 1.796899 0.190306 v 0.091024 1.800365 0.132154 v 0.120949 1.807803 0.061471 v 0.167953 1.800547 -0.003569 v 0.202690 1.790509 -0.058345 v 0.219763 1.772986 -0.103608 v 0.025376 1.782599 0.225570 v 0.041662 1.883651 0.108372 v 0.065429 1.899024 0.027469 v 0.122970 1.883781 -0.034211 v 0.189371 1.834813 -0.085696 v 0.026824 1.840460 0.191371 v -0.189957 1.838185 -0.083454 v -0.028163 1.840382 0.191146 v -0.040263 1.886331 0.107609 v -0.065407 1.903963 0.027451 v -0.123778 1.884084 -0.033638 v -0.225231 1.766756 -0.101532 v -0.030773 1.782936 0.225767 v -0.057529 1.797016 0.189845 v -0.089435 1.804814 0.131241 v -0.124267 1.811475 0.061137 v -0.169496 1.801613 -0.002586 v -0.203903 1.793766 -0.056224 v -0.032356 1.740167 0.086580 v -0.025976 1.740167 0.085311 v -0.020567 1.740167 0.081697 v -0.016953 1.740167 0.076288 v -0.015684 1.740167 0.069908 v -0.020567 1.751956 0.069908 v -0.021465 1.751059 0.076288 v -0.024020 1.748503 0.081697 v -0.027845 1.744678 0.085311 v -0.032356 1.746547 0.085311 v -0.032356 1.751956 0.081697 v -0.032356 1.755570 0.076288 v -0.032356 1.756839 0.069908 v -0.044145 1.751956 0.069908 v -0.043248 1.751059 0.076288 v -0.040692 1.748503 0.081697 v -0.036868 1.744678 0.085311 v -0.038736 1.740167 0.085311 v -0.044145 1.740167 0.081697 v -0.047759 1.740167 0.076288 v -0.049028 1.740167 0.069908 v -0.044145 1.728378 0.069908 v -0.043248 1.729275 0.076288 v -0.040692 1.731831 0.081697 v -0.036868 1.735656 0.085311 v -0.032356 1.733787 0.085311 v -0.032356 1.728378 0.081697 v -0.032356 1.724764 0.076288 v -0.032356 1.723495 0.069908 v -0.020567 1.728378 0.069908 v -0.021465 1.729275 0.076288 v -0.024020 1.731831 0.081697 v -0.027845 1.735656 0.085311 v 0.044282 1.751956 0.069841 v 0.043385 1.751058 0.076221 v 0.040829 1.748503 0.081630 v 0.037004 1.744678 0.085244 v 0.032493 1.746547 0.085244 v 0.032493 1.751956 0.081630 v 0.032493 1.755570 0.076221 v 0.032493 1.756839 0.069841 v 0.020704 1.751956 0.069841 v 0.021601 1.751059 0.076221 v 0.024157 1.748503 0.081630 v 0.027982 1.744678 0.085244 v 0.032493 1.740167 0.086513 v 0.026113 1.740167 0.085244 v 0.020704 1.740167 0.081630 v 0.017090 1.740167 0.076221 v 0.015821 1.740167 0.069841 v 0.020704 1.728378 0.069841 v 0.021601 1.729275 0.076221 v 0.024157 1.731831 0.081630 v 0.027982 1.735656 0.085244 v 0.032493 1.733787 0.085244 v 0.032493 1.728378 0.081630 v 0.032493 1.724764 0.076221 v 0.032493 1.723495 0.069841 v 0.044282 1.728378 0.069841 v 0.043385 1.729275 0.076221 v 0.040829 1.731831 0.081630 v 0.037004 1.735656 0.085244 v 0.038873 1.740167 0.085244 v 0.044282 1.740167 0.081630 v 0.047896 1.740167 0.076221 v 0.049165 1.740167 0.069841 v -0.100955 0.562082 -0.055519 v -0.139085 0.568177 -0.073664 v -0.177215 0.574273 -0.055519 v -0.193009 0.576798 -0.011716 v -0.177215 0.574273 0.032088 v -0.139085 0.568177 0.053507 v -0.100955 0.562082 0.032088 v -0.085161 0.559557 -0.011716 v -0.081718 0.630116 -0.061458 v -0.128839 0.636041 -0.079377 v -0.175961 0.641966 -0.061458 v -0.195479 0.644420 -0.005828 v -0.175961 0.641966 0.049802 v -0.128839 0.636678 0.070935 v -0.081718 0.630116 0.049802 v -0.062199 0.627662 -0.005828 v -0.046760 0.778096 -0.068404 v -0.107066 0.784189 -0.097090 v -0.167371 0.790282 -0.068404 v -0.192350 0.792806 0.000850 v -0.167371 0.790282 0.070104 v -0.107066 0.784189 0.100473 v -0.046760 0.778096 0.070104 v -0.021781 0.775572 0.000850 v -0.097675 0.876684 -0.106817 v -0.165198 0.882608 -0.075282 v -0.193168 0.885063 0.000850 v -0.165198 0.882608 0.076982 v -0.097675 0.876684 0.111042 v -0.000000 0.868305 0.000850 v -0.117521 0.480258 -0.093913 v -0.158176 0.484839 -0.114096 v -0.198830 0.484932 -0.093318 v -0.215669 0.480481 -0.043750 v -0.198830 0.474096 0.005572 v -0.158176 0.469515 0.025755 v -0.117522 0.469422 0.004977 v -0.100682 0.473872 -0.044591 v -0.145262 0.534017 -0.085305 v -0.110702 0.528678 -0.066932 v -0.179822 0.537111 -0.066647 v -0.194137 0.536148 -0.021887 v -0.179822 0.531693 0.022755 v -0.145262 0.526355 0.041128 v -0.110702 0.523260 0.022470 v -0.096387 0.524223 -0.022290 v -0.080202 0.951451 -0.140820 v -0.152695 0.957376 -0.100630 v -0.182723 0.959830 -0.003602 v -0.152695 0.957376 0.093426 v -0.080202 0.951451 0.133616 v -0.063169 1.040780 -0.133246 v -0.130693 1.046705 -0.092997 v -0.158662 1.049159 0.004173 v -0.130693 1.046705 0.101343 v -0.063169 1.040780 0.141593 v -0.058120 1.123998 -0.103349 v -0.125903 1.123998 -0.066587 v -0.153979 1.123998 0.022164 v -0.125903 1.123998 0.110916 v -0.058120 1.123998 0.147678 v 0.000000 0.953011 -0.127545 v 0.000000 1.039823 -0.129410 v 0.000000 1.123998 -0.098510 v -0.065920 0.700805 -0.063973 v -0.118799 0.706729 -0.091123 v -0.171678 0.712654 -0.063973 v -0.193581 0.715108 -0.002489 v -0.171678 0.712654 0.058995 v -0.118799 0.706729 0.084462 v -0.065920 0.700805 0.058995 v -0.044016 0.698351 -0.002489 v 0.100955 0.562082 -0.055519 v 0.139085 0.568177 -0.073664 v 0.177215 0.574273 -0.055519 v 0.193009 0.576798 -0.011716 v 0.177215 0.574273 0.032088 v 0.139085 0.568177 0.053507 v 0.100955 0.562082 0.032088 v 0.085161 0.559557 -0.011716 v 0.081718 0.630116 -0.061458 v 0.128839 0.636041 -0.079377 v 0.175961 0.641966 -0.061458 v 0.195479 0.644420 -0.005828 v 0.175961 0.641966 0.049802 v 0.128839 0.636678 0.070935 v 0.081718 0.630116 0.049802 v 0.062199 0.627662 -0.005828 v 0.046760 0.778096 -0.068404 v 0.107066 0.784189 -0.097090 v 0.167371 0.790282 -0.068404 v 0.192350 0.792806 0.000850 v 0.167371 0.790282 0.070104 v 0.107066 0.784189 0.100473 v 0.046760 0.778096 0.070104 v 0.021781 0.775572 0.000850 v -0.000000 0.870759 -0.075282 v 0.097675 0.876684 -0.106817 v 0.165198 0.882608 -0.075282 v 0.193168 0.885063 0.000850 v 0.165198 0.882608 0.076982 v 0.097675 0.876684 0.111042 v -0.000000 0.876858 0.083081 v 0.117521 0.480258 -0.093913 v 0.158176 0.484839 -0.114096 v 0.198830 0.484932 -0.093318 v 0.215669 0.480481 -0.043750 v 0.198830 0.474096 0.005572 v 0.158176 0.469515 0.025755 v 0.117522 0.469422 0.004977 v 0.100682 0.473872 -0.044591 v 0.145262 0.534017 -0.085305 v 0.110702 0.528678 -0.066932 v 0.179822 0.537111 -0.066647 v 0.194137 0.536148 -0.021887 v 0.179822 0.531693 0.022755 v 0.145262 0.526355 0.041128 v 0.110702 0.523260 0.022470 v 0.096387 0.524223 -0.022290 v 0.080202 0.951451 -0.140820 v 0.152695 0.957376 -0.100630 v 0.182723 0.959830 -0.003602 v 0.152695 0.957376 0.093426 v 0.080202 0.951451 0.133616 v 0.063169 1.040780 -0.133246 v 0.130693 1.046705 -0.092997 v 0.158662 1.049159 0.004173 v 0.130693 1.046705 0.101343 v 0.063169 1.040780 0.141593 v 0.058120 1.123998 -0.103349 v 0.125903 1.123998 -0.066587 v 0.153979 1.123998 0.022164 v 0.125903 1.123998 0.110916 v 0.058120 1.123998 0.147678 v -0.000000 0.940066 0.136277 v 0.000000 1.038324 0.144000 v 0.000000 1.123998 0.147301 v 0.065920 0.700805 -0.063973 v 0.118799 0.706729 -0.091123 v 0.171678 0.712654 -0.063973 v 0.193581 0.715108 -0.002489 v 0.171678 0.712654 0.058995 v 0.118799 0.706729 0.084462 v 0.065920 0.700805 0.058995 v 0.044016 0.698351 -0.002489 v 0.134275 1.530679 -0.176386 v 0.172047 1.568379 -0.134707 v 0.183198 1.581694 -0.087722 v 0.183899 1.582353 -0.037207 v 0.175968 1.570682 0.009387 v 0.154211 1.545088 0.050300 v 0.127586 1.516320 0.085776 v 0.090502 1.480065 0.118043 v 0.048199 1.439660 0.139210 v 0.004523 1.397720 0.152934 v -0.046749 1.346165 0.168389 v -0.100793 1.286092 0.157968 v -0.142447 1.245674 0.120062 v -0.172509 1.215475 0.065591 v -0.184084 1.200966 0.010595 v -0.189708 1.193441 -0.037206 v -0.183778 1.199644 -0.083800 v -0.161577 1.220932 -0.127256 v -0.125529 1.256296 -0.164504 v -0.082149 1.299133 -0.193113 v -0.040545 1.343281 -0.203115 v 0.004932 1.390315 -0.209998 v 0.049917 1.437272 -0.211098 v 0.091361 1.483013 -0.199357 v 0.085005 1.564355 -0.174013 v 0.115650 1.595672 -0.131694 v 0.128002 1.611723 -0.087199 v 0.130140 1.610316 -0.037207 v 0.123674 1.601299 0.009387 v 0.103645 1.579924 0.048363 v 0.078677 1.552097 0.085776 v 0.047546 1.519726 0.111321 v 0.006099 1.478006 0.130594 v -0.039131 1.432221 0.142984 v -0.086835 1.384427 0.149293 v -0.134336 1.337502 0.138490 v -0.172510 1.300595 0.106263 v -0.197874 1.272699 0.058544 v -0.209579 1.258838 0.009387 v -0.212954 1.250003 -0.037206 v -0.207024 1.256206 -0.083800 v -0.189637 1.274392 -0.127219 v -0.162299 1.301960 -0.167083 v -0.125744 1.339687 -0.198121 v -0.084020 1.384709 -0.206740 v -0.038853 1.432207 -0.209998 v 0.006131 1.479163 -0.211098 v 0.048350 1.524587 -0.199694 v 0.080027 1.559096 -0.168589 v 0.109462 1.589176 -0.127940 v 0.121327 1.604594 -0.085200 v 0.123380 1.603243 -0.037181 v 0.117169 1.594581 0.007574 v 0.097931 1.574050 0.045012 v 0.073948 1.547321 0.080948 v 0.044027 1.516185 0.105412 v 0.004235 1.476154 0.123997 v -0.039210 1.432176 0.135898 v -0.085042 1.386244 0.141913 v -0.130640 1.341176 0.131492 v -0.167326 1.305744 0.100627 v -0.191689 1.278949 0.054791 v -0.202932 1.265635 0.007574 v -0.206174 1.257149 -0.037181 v -0.200478 1.263107 -0.081936 v -0.183777 1.280576 -0.123641 v -0.157525 1.307029 -0.161982 v -0.122402 1.343267 -0.191843 v -0.082329 1.386534 -0.199939 v -0.038943 1.432166 -0.203012 v 0.004265 1.477266 -0.204210 v 0.044818 1.520897 -0.193256 v 0.128970 1.525091 -0.170732 v 0.165253 1.561306 -0.130695 v 0.175966 1.574095 -0.085562 v 0.176639 1.574728 -0.037037 v 0.169020 1.563518 0.007721 v 0.148120 1.538932 0.047022 v 0.122544 1.511298 0.081100 v 0.086903 1.476429 0.112024 v 0.046286 1.437658 0.132429 v 0.004330 1.397371 0.145612 v -0.044931 1.347823 0.160413 v -0.096818 1.290121 0.150358 v -0.136849 1.251315 0.114035 v -0.165727 1.222306 0.061711 v -0.176846 1.208368 0.008882 v -0.182248 1.201140 -0.037037 v -0.176552 1.207098 -0.081795 v -0.155226 1.227548 -0.123538 v -0.120598 1.261518 -0.159318 v -0.078927 1.302668 -0.186801 v -0.038970 1.345081 -0.196253 v 0.004724 1.390260 -0.202879 v 0.047935 1.435364 -0.204077 v 0.087747 1.479303 -0.192798 v -0.236368 0.092607 -0.020215 v -0.244447 0.050649 0.055733 v -0.160035 0.050649 0.068862 v -0.158087 0.096969 -0.009511 v -0.231907 0.050962 0.096377 v -0.171927 0.050962 0.109907 v -0.198856 0.056750 0.063817 v -0.200434 0.099614 -0.008311 v -0.203658 0.053735 0.117253 v -0.228066 0.128577 -0.048921 v -0.191707 0.128577 -0.030516 v -0.155310 0.128577 -0.040374 v -0.243809 0.048939 -0.062636 v -0.259032 0.034710 0.052294 v -0.229486 0.128577 -0.086203 v -0.215595 0.128577 -0.123264 v -0.215978 0.054773 -0.140594 v -0.166330 0.128577 -0.141207 v -0.168375 0.056821 -0.160469 v -0.138088 0.128577 -0.118289 v -0.138284 0.053982 -0.136358 v -0.140126 0.128577 -0.082307 v -0.145418 0.049313 -0.053791 v -0.142801 0.033877 0.063946 v -0.241657 0.038506 0.108880 v -0.163953 0.037606 0.125343 v -0.203658 0.036372 0.132175 v -0.240419 0.028025 -0.064598 v -0.255085 0.016737 0.039452 v -0.214742 0.028524 -0.135022 v -0.170136 0.028524 -0.153646 v -0.141940 0.028524 -0.131052 v -0.153248 0.027295 -0.055831 v -0.145661 0.016737 0.050411 v -0.238718 0.016737 0.097813 v -0.164969 0.016737 0.113922 v -0.202729 0.016737 0.120323 v -0.242952 0.028025 -0.066538 v -0.258034 0.016737 0.042949 v -0.215666 0.028524 -0.141348 v -0.168821 0.028524 -0.160906 v -0.139209 0.028524 -0.137178 v -0.150897 0.027295 -0.057712 v -0.143524 0.016737 0.054426 v -0.240914 0.016737 0.103692 v -0.164210 0.016737 0.120066 v -0.203423 0.016737 0.126789 v -0.258034 -0.001088 0.025696 v -0.242952 0.013555 -0.070778 v -0.215666 0.014054 -0.141348 v -0.168821 0.014054 -0.160906 v -0.139209 0.014054 -0.137178 v -0.150897 0.012826 -0.061952 v -0.143524 -0.001088 0.037173 v -0.240914 -0.000249 0.103692 v -0.164210 -0.000249 0.120066 v -0.203423 -0.000249 0.126789 v -0.242952 -0.000311 -0.070778 v -0.215666 0.000188 -0.141348 v -0.168821 0.000188 -0.160906 v -0.139209 0.000188 -0.137178 v -0.150897 -0.001041 -0.061952 v -0.240407 0.067287 0.012779 v -0.157989 0.070033 0.027636 v -0.199645 0.074798 0.025643 v -0.254610 0.041825 -0.006398 v -0.148123 0.042231 -0.000025 v -0.250941 0.022381 -0.013800 v -0.154169 0.022381 -0.007738 v -0.253682 0.022381 -0.013022 v -0.151925 0.022381 -0.006671 v -0.253682 0.005814 -0.021702 v -0.151925 0.005814 -0.015351 v -0.218563 0.209613 -0.046429 v -0.188733 0.208412 -0.021345 v -0.148752 0.208412 -0.032174 v -0.230231 0.208412 -0.082515 v -0.214972 0.208412 -0.125426 v -0.160857 0.208412 -0.142935 v -0.129834 0.208412 -0.119961 v -0.132073 0.208412 -0.078236 v -0.184486 0.296737 -0.005237 v -0.225084 0.299002 -0.034678 v -0.140596 0.296697 -0.019914 v -0.236935 0.298150 -0.081582 v -0.217650 0.297630 -0.136721 v -0.158346 0.295603 -0.157599 v -0.110046 0.294732 -0.129811 v -0.112876 0.294808 -0.076172 v -0.180417 0.388284 0.013613 v -0.219940 0.392383 -0.019552 v -0.129895 0.386004 -0.002073 v -0.236443 0.395326 -0.069991 v -0.219625 0.400030 -0.135825 v -0.144725 0.400515 -0.158651 v -0.091043 0.394958 -0.126268 v -0.093351 0.389803 -0.062979 v -0.141647 0.491328 -0.114189 v -0.205701 0.490563 -0.094984 v -0.172100 0.480332 0.031533 v -0.088130 0.486878 -0.030961 v -0.124555 0.485816 0.028877 v -0.085756 0.492816 -0.089075 v -0.138819 0.500966 -0.123683 v -0.212734 0.500023 -0.101320 v -0.229349 0.492623 -0.040656 v -0.212929 0.490855 0.006320 v -0.174315 0.487937 0.044232 v -0.078510 0.469649 -0.030733 v -0.118959 0.469167 0.037975 v -0.075626 0.476194 -0.096815 v -0.135258 0.486616 -0.136305 v -0.219656 0.487755 -0.110271 v -0.239055 0.480310 -0.041331 v -0.221618 0.477597 0.013127 v -0.176187 0.472900 0.055532 v -0.073827 0.311152 -0.055639 v -0.116017 0.309592 0.028478 v -0.066537 0.324125 -0.137977 v -0.244434 0.339411 -0.148584 v -0.277813 0.321962 -0.063759 v -0.262464 0.316457 0.009962 v -0.191486 0.312070 0.054821 v -0.178630 0.357606 -0.193864 v -0.123421 0.350867 -0.185934 v -0.118294 0.398249 0.026084 v -0.078910 0.396001 -0.044382 v -0.074782 0.405104 -0.114011 v -0.225539 0.420431 -0.125449 v -0.254013 0.412894 -0.052038 v -0.236933 0.409543 0.008791 v -0.180250 0.404852 0.047154 v -0.153892 0.427512 -0.158986 v -0.129930 0.424512 -0.155542 v -0.204864 0.482881 -0.001814 v -0.129203 0.478420 0.018134 v -0.219844 0.485703 -0.041760 v -0.095093 0.485847 -0.085518 v -0.097055 0.480678 -0.033262 v -0.055529 1.493171 0.092907 v -0.055632 1.451355 0.129636 v -0.040152 1.385805 0.146414 v -0.031399 1.323361 0.152706 v -0.026148 1.252309 0.159674 v -0.023899 1.183441 0.163047 v -0.023899 1.119257 0.159816 v -0.035756 1.024998 0.164243 v -0.057075 0.882970 0.166144 v -0.087342 0.767263 0.175068 v -0.130256 0.600958 0.203083 v -0.263084 0.602740 0.125120 v -0.303530 0.629328 -0.012713 v -0.279738 0.659615 -0.142583 v -0.180476 0.680835 -0.218781 v -0.052293 0.696431 -0.229468 v -0.000000 0.903639 -0.186417 v -0.000000 1.019671 -0.164335 v 0.000000 1.119257 -0.127489 v -0.021313 0.802760 -0.209410 v 0.000000 1.183441 -0.137360 v 0.000000 1.376735 -0.198452 v 0.000000 1.451759 -0.198452 v 0.000000 1.524567 -0.186393 v -0.200106 0.766720 0.128702 v -0.256106 0.773491 0.005349 v -0.222970 0.788889 -0.105964 v -0.139229 0.802427 -0.183604 v -0.166305 0.883253 0.130780 v -0.216473 0.887417 0.005856 v -0.194182 0.897068 -0.096436 v -0.113426 0.903290 -0.164762 v -0.136724 1.018924 0.134398 v -0.186892 1.015852 0.017915 v -0.164600 1.017062 -0.083171 v -0.086115 1.020203 -0.143668 v -0.121152 1.119257 0.137785 v -0.172670 1.119257 0.018168 v -0.149779 1.119257 -0.074245 v -0.077684 1.119257 -0.119280 v -0.120262 1.183441 0.143953 v -0.173867 1.183441 0.019491 v -0.162576 1.183441 -0.083776 v -0.083964 1.183441 -0.137626 v -0.120061 1.252504 0.134398 v -0.185320 1.251011 0.010953 v -0.181651 1.243637 -0.097475 v -0.101614 1.250648 -0.152374 v -0.130367 1.318038 0.132279 v -0.212258 1.300892 0.010329 v -0.206866 1.296449 -0.103040 v -0.124560 1.308405 -0.178665 v -0.309009 1.485512 -0.042012 v -0.299405 1.454274 0.011206 v -0.262364 1.400470 0.013574 v -0.246013 1.343623 -0.013905 v -0.232733 1.329661 -0.096472 v -0.249925 1.351043 -0.156010 v -0.280159 1.425431 -0.159697 v -0.300754 1.477066 -0.123508 v -0.358489 1.441829 -0.053517 v -0.341332 1.415725 -0.016209 v -0.312568 1.371026 0.007202 v -0.295059 1.326406 -0.025422 v -0.284217 1.313647 -0.093763 v -0.293527 1.334403 -0.159059 v -0.329213 1.395204 -0.158847 v -0.350392 1.432640 -0.118576 v -0.153061 1.396596 0.117845 v -0.234974 1.401947 0.041187 v -0.232898 1.345370 -0.160297 v -0.135208 1.361853 -0.200577 v -0.418271 1.393451 -0.067665 v -0.404607 1.371110 -0.028798 v -0.378331 1.331015 -0.009391 v -0.358364 1.296543 -0.036435 v -0.352312 1.285127 -0.093088 v -0.362546 1.300656 -0.147216 v -0.392129 1.351058 -0.147040 v -0.412726 1.386348 -0.113657 v -0.238603 1.451604 0.050911 v -0.247663 1.426305 -0.187326 v -0.137532 1.441997 -0.201822 v -0.161963 1.461164 0.112142 v -0.249717 1.546276 -0.020199 v -0.242352 1.535791 -0.122329 v -0.164602 1.516542 0.061273 v -0.126600 1.523267 -0.170931 v -0.096686 1.597695 -0.035012 v -0.091318 1.612576 -0.091144 v -0.079937 1.584289 0.006058 v -0.052429 1.559129 0.039906 v -0.059511 1.620940 -0.125205 v -0.127213 1.655769 -0.030739 v -0.121658 1.675305 -0.095090 v -0.109973 1.639888 0.018116 v -0.080687 1.612360 0.057116 v -0.088142 1.685365 -0.137990 v 0.000000 1.693757 -0.156027 v -0.165594 1.623511 -0.109647 v -0.170923 1.602495 -0.021107 v -0.104190 1.560981 0.096990 v -0.146253 1.584960 0.045949 v -0.097500 1.639982 -0.170080 v 0.000000 1.645165 -0.191679 v -0.168111 1.573843 -0.106737 v -0.178915 1.570728 -0.027279 v -0.128327 1.552606 0.035805 v -0.057487 1.531572 0.066407 v -0.100267 1.576621 -0.151559 v -0.470204 1.368917 -0.069429 v -0.454158 1.346519 -0.036405 v -0.426572 1.304602 -0.020104 v -0.408229 1.267183 -0.043107 v -0.405618 1.253222 -0.091268 v -0.419175 1.267909 -0.137238 v -0.448776 1.321324 -0.137073 v -0.467372 1.359826 -0.108665 v -0.509538 1.307247 -0.023719 v -0.521968 1.326629 -0.055033 v -0.487954 1.270359 -0.008292 v -0.472451 1.236567 -0.030094 v -0.469302 1.223250 -0.075754 v -0.479316 1.235488 -0.119334 v -0.503579 1.283145 -0.119189 v -0.519343 1.317980 -0.092259 v -0.576228 1.275383 -0.040033 v -0.563878 1.261239 -0.011934 v -0.544363 1.233828 0.001865 v -0.532909 1.208638 -0.017657 v -0.533244 1.198450 -0.058594 v -0.544480 1.207245 -0.097677 v -0.564582 1.242551 -0.097592 v -0.575978 1.268567 -0.073467 v -0.621947 1.243404 -0.024871 v -0.606588 1.231470 0.001990 v -0.584973 1.207811 0.013254 v -0.574175 1.185285 -0.008347 v -0.577637 1.175574 -0.050069 v -0.592578 1.182565 -0.088309 v -0.614130 1.213647 -0.085271 v -0.624485 1.236964 -0.058985 v -0.548048 1.283423 -0.000028 v -0.569435 1.303588 -0.033291 v -0.519235 1.245224 0.013920 v -0.506848 1.210827 -0.012829 v -0.513994 1.197634 -0.064495 v -0.535664 1.210644 -0.111848 v -0.562988 1.259448 -0.108086 v -0.574545 1.294888 -0.075536 v -0.518948 1.363826 -0.037351 v -0.490553 1.334002 0.007940 v -0.453133 1.278392 0.026932 v -0.438403 1.229340 -0.009490 v -0.449396 1.211424 -0.079838 v -0.478726 1.231261 -0.144314 v -0.513263 1.301594 -0.139192 v -0.526753 1.352042 -0.094871 v 0.055529 1.493171 0.092907 v 0.055632 1.451355 0.129636 v 0.040152 1.385805 0.146414 v 0.031399 1.323361 0.152706 v 0.026148 1.252309 0.159674 v 0.023899 1.183441 0.163047 v 0.023899 1.119257 0.159816 v 0.035756 1.024998 0.164243 v 0.057075 0.882970 0.166144 v 0.087342 0.767263 0.175068 v 0.130256 0.600958 0.203083 v 0.263084 0.602740 0.125120 v 0.303530 0.629328 -0.012713 v 0.279738 0.659615 -0.142583 v 0.180476 0.680835 -0.218781 v 0.052293 0.696431 -0.229468 v 0.021313 0.802760 -0.209410 v 0.000000 1.254463 -0.158657 v -0.000000 1.315609 -0.183981 v 0.200106 0.766720 0.128702 v 0.256106 0.773491 0.005349 v 0.222970 0.788889 -0.105964 v 0.139229 0.802427 -0.183604 v 0.166305 0.883253 0.130780 v 0.216473 0.887417 0.005856 v 0.194182 0.897068 -0.096436 v 0.113426 0.903290 -0.164762 v 0.136724 1.018924 0.134398 v 0.186892 1.015852 0.017915 v 0.164600 1.017062 -0.083171 v 0.086115 1.020203 -0.143668 v 0.121152 1.119257 0.137785 v 0.172670 1.119257 0.018168 v 0.149779 1.119257 -0.074245 v 0.077684 1.119257 -0.119280 v 0.120262 1.183441 0.143953 v 0.173867 1.183441 0.019491 v 0.162576 1.183441 -0.083776 v 0.083964 1.183441 -0.137626 v 0.120061 1.252504 0.134398 v 0.185320 1.251011 0.010953 v 0.181651 1.243637 -0.097475 v 0.101614 1.250648 -0.152374 v 0.130367 1.318038 0.132279 v 0.212258 1.300892 0.010329 v 0.206866 1.296449 -0.103040 v 0.124560 1.308405 -0.178665 v 0.309009 1.485512 -0.042012 v 0.299405 1.454274 0.011206 v 0.262364 1.400470 0.013574 v 0.246013 1.343623 -0.013905 v 0.232733 1.329661 -0.096472 v 0.249925 1.351043 -0.156010 v 0.280159 1.425431 -0.159697 v 0.300754 1.477066 -0.123508 v 0.358489 1.441829 -0.053517 v 0.341332 1.415725 -0.016209 v 0.312568 1.371026 0.007202 v 0.295059 1.326406 -0.025422 v 0.284217 1.313647 -0.093763 v 0.293527 1.334403 -0.159059 v 0.329213 1.395204 -0.158847 v 0.350392 1.432640 -0.118576 v 0.153061 1.396596 0.117845 v 0.234974 1.401947 0.041187 v 0.232898 1.345370 -0.160297 v 0.135208 1.361853 -0.200577 v 0.418271 1.393451 -0.067665 v 0.404607 1.371110 -0.028798 v 0.378331 1.331015 -0.009391 v 0.358364 1.296543 -0.036435 v 0.352312 1.285127 -0.093088 v 0.362546 1.300656 -0.147216 v 0.392129 1.351058 -0.147040 v 0.412726 1.386348 -0.113657 v 0.238603 1.451604 0.050911 v 0.247663 1.426305 -0.187326 v 0.137532 1.441997 -0.201822 v 0.161963 1.461164 0.112142 v 0.249717 1.546276 -0.020199 v 0.242352 1.535791 -0.122329 v 0.164602 1.516542 0.061273 v 0.126600 1.523267 -0.170931 v 0.096686 1.597695 -0.035012 v 0.091318 1.612576 -0.091144 v 0.079937 1.584289 0.006058 v 0.052429 1.559129 0.039906 v 0.059511 1.620940 -0.125205 v 0.000000 1.628451 -0.141560 v 0.127213 1.655769 -0.030739 v 0.121658 1.675305 -0.095090 v 0.109973 1.639888 0.018116 v 0.080687 1.612360 0.057116 v 0.088142 1.685365 -0.137990 v 0.165594 1.623511 -0.109647 v 0.170923 1.602495 -0.021107 v 0.104190 1.560981 0.096990 v 0.146253 1.584960 0.045949 v 0.097500 1.639982 -0.170080 v 0.168111 1.573843 -0.106737 v 0.178915 1.570728 -0.027279 v 0.128327 1.552606 0.035805 v 0.057487 1.531572 0.066407 v 0.000000 1.584163 -0.163783 v 0.100267 1.576621 -0.151559 v 0.470204 1.368917 -0.069429 v 0.454158 1.346519 -0.036405 v 0.426572 1.304602 -0.020104 v 0.408229 1.267183 -0.043107 v 0.405618 1.253222 -0.091268 v 0.419175 1.267909 -0.137238 v 0.448776 1.321324 -0.137073 v 0.467372 1.359826 -0.108665 v 0.509538 1.307247 -0.023719 v 0.521968 1.326629 -0.055033 v 0.487954 1.270359 -0.008292 v 0.472451 1.236567 -0.030094 v 0.469302 1.223250 -0.075754 v 0.479316 1.235488 -0.119334 v 0.503579 1.283145 -0.119189 v 0.519343 1.317980 -0.092259 v 0.576228 1.275383 -0.040033 v 0.563878 1.261239 -0.011934 v 0.544363 1.233828 0.001865 v 0.532909 1.208638 -0.017657 v 0.533244 1.198450 -0.058594 v 0.544480 1.207245 -0.097677 v 0.564582 1.242551 -0.097592 v 0.575978 1.268567 -0.073467 v 0.621947 1.243404 -0.024871 v 0.606588 1.231470 0.001990 v 0.584973 1.207811 0.013254 v 0.574175 1.185285 -0.008347 v 0.577637 1.175574 -0.050069 v 0.592578 1.182565 -0.088309 v 0.614130 1.213647 -0.085271 v 0.624485 1.236964 -0.058985 v 0.548048 1.283423 -0.000028 v 0.569435 1.303588 -0.033291 v 0.519235 1.245224 0.013920 v 0.506848 1.210827 -0.012829 v 0.513994 1.197634 -0.064495 v 0.535664 1.210644 -0.111848 v 0.562988 1.259448 -0.108086 v 0.574545 1.294888 -0.075536 v 0.518948 1.363826 -0.037351 v 0.490553 1.334002 0.007940 v 0.453133 1.278392 0.026932 v 0.438403 1.229340 -0.009490 v 0.449396 1.211424 -0.079838 v 0.478726 1.231261 -0.144314 v 0.513263 1.301594 -0.139192 v 0.526753 1.352042 -0.094871 v 0.088152 1.183441 -0.144985 v 0.170685 1.183441 -0.088437 v 0.182540 1.183441 0.020003 v 0.126261 1.183441 0.150700 v 0.081559 1.119257 -0.128551 v 0.157250 1.119257 -0.078428 v 0.181283 1.119257 0.018614 v 0.127195 1.119257 0.144223 v 0.025091 1.119257 0.167358 v 0.025091 1.183441 0.170750 v -0.088152 1.183441 -0.144985 v -0.170685 1.183441 -0.088437 v -0.182540 1.183441 0.020003 v -0.126261 1.183441 0.150700 v -0.081559 1.119257 -0.128551 v -0.157250 1.119257 -0.078428 v -0.181283 1.119257 0.018614 v -0.127195 1.119257 0.144223 v -0.000000 1.183441 -0.144198 v -0.000000 1.119257 -0.134141 v -0.025091 1.119257 0.167358 v -0.025091 1.183441 0.170750 v 0.032665 1.515867 0.061271 v 0.000046 1.506738 0.057291 v 0.009008 1.509201 0.059572 v 0.014802 1.510792 0.060556 v 0.022246 1.512831 0.061265 v 0.004174 1.507801 0.058344 v 0.047380 1.520089 0.058843 v 0.086181 1.529302 0.036547 v 0.104020 1.533417 0.016887 v 0.127030 1.565279 -0.112151 v 0.140316 1.569182 -0.078594 v 0.140484 1.563938 -0.046658 v 0.132480 1.553798 -0.020614 v 0.119583 1.541506 -0.002499 v 0.103516 1.555723 -0.139450 v 0.674888 1.110830 -0.003673 v 0.612610 1.157199 0.002101 v 0.674545 1.117330 0.019286 v 0.706858 1.099530 0.023730 v 0.710139 1.097504 0.045483 v 0.669417 1.155014 0.039740 v 0.647492 1.157972 0.056044 v 0.661499 1.177832 0.004751 v 0.635999 1.142938 0.053810 v 0.729386 1.080314 0.059558 v 0.719635 1.088437 0.052730 v 0.744902 1.067028 -0.001493 v 0.726869 1.071270 -0.000494 v 0.736876 1.060203 0.001079 v 0.743075 1.053009 0.001969 v 0.645372 1.134767 0.068654 v 0.662357 1.126353 0.097420 v 0.657118 1.139621 0.080730 v 0.732353 1.052126 -0.034486 v 0.713486 1.062962 -0.030949 v 0.718813 1.054992 -0.030781 v 0.724174 1.048125 -0.032724 v 0.754945 1.052232 0.000265 v 0.753210 1.051807 0.042524 v 0.743049 1.070876 0.068770 v 0.748944 1.075721 0.067038 v 0.631187 1.182153 -0.060137 v 0.608335 1.195320 -0.067070 v 0.559506 1.202946 -0.041688 v 0.562544 1.220983 -0.017718 v 0.669640 1.104069 -0.024403 v 0.627276 1.139434 0.009177 v 0.714807 1.090642 0.027332 v 0.695859 1.107043 0.027280 v 0.727431 1.078475 0.031722 v 0.696927 1.098358 0.001796 v 0.703119 1.103989 0.038953 v 0.633594 1.210668 -0.010901 v 0.647811 1.194846 -0.002174 v 0.652695 1.145887 0.071041 v 0.712533 1.113137 0.036251 v 0.719345 1.105960 0.042952 v 0.728522 1.096502 0.050164 v 0.737676 1.087669 0.056634 v 0.705954 1.119005 0.023964 v 0.716645 1.109843 0.021743 v 0.725072 1.099576 0.024312 v 0.747342 1.073881 0.033310 v 0.736533 1.086177 0.029001 v 0.696296 1.140563 0.016160 v 0.708298 1.111171 -0.002280 v 0.696457 1.132692 -0.009182 v 0.708394 1.090153 -0.032321 v 0.703739 1.097623 -0.031627 v 0.715028 1.083526 -0.002103 v 0.706621 1.091983 -0.003424 v 0.724658 1.092521 -0.005640 v 0.716293 1.102370 -0.006407 v 0.735778 1.079242 -0.003190 v 0.698534 1.082554 -0.028880 v 0.690590 1.094611 -0.022870 v 0.694321 1.089775 -0.029227 v 0.705918 1.072451 -0.029914 v 0.715476 1.079874 -0.032419 v 0.650145 1.128751 0.079602 v 0.661139 1.136280 0.088389 v 0.664916 1.135274 0.096804 v 0.655866 1.126043 0.089251 v 0.727975 1.061423 -0.033305 v 0.722408 1.069734 -0.032870 v 0.750177 1.058739 -0.000720 v 0.748876 1.047540 0.001710 v 0.760269 1.057284 0.040446 v 0.754922 1.065134 0.037294 v 0.746595 1.058580 0.040406 v 0.738799 1.066683 0.037092 v 0.736467 1.074060 0.064212 v 0.744538 1.081398 0.061437 v 0.615434 1.228177 -0.023378 v 0.590023 1.247385 -0.040312 v 0.661068 1.157391 -0.045104 v 0.664961 1.169456 -0.023274 v 0.587070 1.181785 -0.037609 v 0.559761 1.211244 -0.028605 v 0.585196 1.196178 -0.011534 v 0.633854 1.137880 -0.012377 v 0.601909 1.175540 -0.005262 v 0.578364 1.210416 -0.074700 v 0.608685 1.163981 -0.034361 v 0.621257 1.146775 -0.032980 v 0.633379 1.131144 -0.031188 v 0.703711 1.088645 -0.016576 v 0.712716 1.079507 -0.014927 v 0.725322 1.067865 -0.012271 v 0.735923 1.057540 -0.009999 v 0.742203 1.050307 -0.008593 v 0.748456 1.045616 -0.006070 v 0.755212 1.050032 -0.008025 v 0.750431 1.057000 -0.010678 v 0.744125 1.064766 -0.012315 v 0.733938 1.075834 -0.014611 v 0.722202 1.088450 -0.018332 v 0.712501 1.098042 -0.021141 v 0.700170 1.105421 -0.026439 v 0.690631 1.125018 -0.031907 v 0.646118 1.169501 -0.050893 v 0.562504 1.198289 -0.055965 v 0.591163 1.179253 -0.050869 v 0.612975 1.162797 -0.047605 v 0.625289 1.146584 -0.048665 v 0.636769 1.132086 -0.048725 v 0.668763 1.105041 -0.043369 v 0.681188 1.094929 -0.040700 v 0.687931 1.088314 -0.040485 v 0.693752 1.080715 -0.040848 v 0.701580 1.070576 -0.041451 v 0.709330 1.060843 -0.041866 v 0.715570 1.053672 -0.041779 v 0.721939 1.047881 -0.041068 v 0.729140 1.052229 -0.043852 v 0.724430 1.060406 -0.044051 v 0.718454 1.068123 -0.043548 v 0.711056 1.077947 -0.043821 v 0.703314 1.088221 -0.043762 v 0.697523 1.096518 -0.043640 v 0.691512 1.105148 -0.044287 v 0.680400 1.116818 -0.047024 v 0.649712 1.143939 -0.055466 v 0.635474 1.155394 -0.056575 v 0.619869 1.167972 -0.057263 v 0.597704 1.182203 -0.061944 v 0.568559 1.198926 -0.068300 v 0.585402 1.188237 -0.024350 v 0.604116 1.167808 -0.019721 v 0.618151 1.151636 -0.015785 v 0.707523 1.094909 0.010309 v 0.715423 1.085616 0.013751 v 0.727984 1.074069 0.020076 v 0.739291 1.062430 0.025747 v 0.746843 1.054016 0.029661 v 0.753531 1.048433 0.033598 v 0.760509 1.053722 0.031717 v 0.755461 1.060904 0.027049 v 0.748087 1.069597 0.022795 v 0.737262 1.081977 0.017412 v 0.726102 1.095113 0.011160 v 0.717710 1.104636 0.006157 v 0.651211 1.185025 -0.031012 v 0.635891 1.199200 -0.039893 v 0.615718 1.214473 -0.051091 v 0.587649 1.231131 -0.065039 v 0.580376 1.244796 -0.016765 v 0.601626 1.222414 -0.000946 v 0.617504 1.203980 0.011000 v 0.630214 1.188406 0.019663 v 0.645689 1.174979 0.034668 v 0.687072 1.141182 0.038127 v 0.701374 1.129937 0.043063 v 0.709271 1.120481 0.048463 v 0.716513 1.111768 0.053868 v 0.726257 1.101887 0.060552 v 0.735765 1.092832 0.066725 v 0.742516 1.086052 0.071015 v 0.747885 1.079823 0.074469 v 0.741875 1.074101 0.076535 v 0.734655 1.079178 0.074287 v 0.727307 1.085463 0.069924 v 0.717230 1.094135 0.063806 v 0.707014 1.103360 0.056808 v 0.698643 1.110361 0.051242 v 0.688986 1.116561 0.045938 v 0.674013 1.125840 0.039401 v 0.623163 1.149869 0.034216 v 0.613844 1.170335 0.018360 v 0.604447 1.187261 0.007201 v 0.588880 1.207248 -0.000362 v 0.568976 1.232436 -0.011231 v 0.679594 1.158810 0.010768 v 0.680715 1.150920 -0.015581 v 0.676328 1.141581 -0.037838 v 0.665879 1.130452 -0.051364 v 0.652273 1.117288 -0.046885 v 0.650507 1.117073 -0.028105 v 0.655369 1.126643 -0.009846 v 0.653305 1.130948 0.012378 v 0.652383 1.133142 0.035792 v 0.651913 1.134051 0.048879 v 0.656959 1.129360 0.062812 v 0.662192 1.124307 0.073167 v 0.667196 1.122067 0.083716 v 0.670769 1.123390 0.093898 v 0.674276 1.132866 0.093584 v 0.670998 1.133584 0.083875 v 0.667633 1.136297 0.075134 v 0.663312 1.140577 0.065521 v 0.661870 1.147834 0.051705 v 0.066200 1.524831 0.051173 v -0.000140 1.518403 -0.153647 v 0.013576 1.521601 -0.156987 v 0.030145 1.528860 -0.161555 v 0.051938 1.537383 -0.163007 v 0.077462 1.546570 -0.156404 v -0.032671 1.515867 0.061272 v -0.009013 1.509201 0.059572 v -0.014807 1.510792 0.060556 v -0.022251 1.512831 0.061265 v -0.004179 1.507801 0.058344 v -0.047385 1.520089 0.058843 v -0.086187 1.529302 0.036547 v -0.104025 1.533417 0.016887 v -0.127035 1.565279 -0.112151 v -0.140321 1.569182 -0.078594 v -0.140489 1.563938 -0.046658 v -0.132486 1.553798 -0.020614 v -0.119588 1.541506 -0.002499 v -0.103521 1.555723 -0.139450 v -0.674908 1.110830 -0.003673 v -0.612625 1.157199 0.002101 v -0.674578 1.117330 0.019286 v -0.706865 1.099530 0.023730 v -0.710172 1.097507 0.045484 v -0.669610 1.155001 0.039719 v -0.647512 1.157971 0.056042 v -0.661505 1.177832 0.004751 v -0.636011 1.142938 0.053810 v -0.729412 1.080313 0.059557 v -0.719668 1.088437 0.052730 v -0.744914 1.067028 -0.001493 v -0.726889 1.071270 -0.000494 v -0.736885 1.060203 0.001079 v -0.743082 1.053009 0.001969 v -0.645423 1.134767 0.068654 v -0.662370 1.126353 0.097420 v -0.657138 1.139620 0.080730 v -0.732395 1.052120 -0.034486 v -0.713543 1.062960 -0.030947 v -0.718822 1.054992 -0.030781 v -0.724193 1.048124 -0.032724 v -0.755011 1.052230 0.000263 v -0.753218 1.051807 0.042524 v -0.743069 1.070876 0.068770 v -0.748987 1.075720 0.067041 v -0.631193 1.182153 -0.060137 v -0.608340 1.195320 -0.067070 v -0.559511 1.202946 -0.041688 v -0.562549 1.220983 -0.017718 v -0.669655 1.104069 -0.024403 v -0.627285 1.139434 0.009177 v -0.714824 1.090642 0.027332 v -0.695874 1.107042 0.027280 v -0.727443 1.078475 0.031723 v -0.696946 1.098358 0.001796 v -0.703138 1.103988 0.038953 v -0.633599 1.210668 -0.010901 v -0.647816 1.194846 -0.002174 v -0.652935 1.145912 0.071046 v -0.712544 1.113137 0.036251 v -0.719353 1.105960 0.042952 v -0.728530 1.096502 0.050164 v -0.737684 1.087669 0.056634 v -0.706039 1.119006 0.023963 v -0.716664 1.109843 0.021743 v -0.725084 1.099576 0.024312 v -0.747346 1.073881 0.033310 v -0.736545 1.086177 0.029001 v -0.696304 1.140563 0.016160 v -0.708312 1.111171 -0.002280 v -0.696464 1.132692 -0.009182 v -0.708408 1.090153 -0.032321 v -0.703756 1.097623 -0.031627 v -0.715046 1.083527 -0.002103 v -0.706627 1.091983 -0.003424 v -0.724670 1.092521 -0.005639 v -0.716310 1.102371 -0.006407 v -0.735788 1.079242 -0.003190 v -0.698594 1.082554 -0.028880 v -0.690607 1.094611 -0.022870 v -0.694353 1.089775 -0.029227 v -0.705983 1.072452 -0.029916 v -0.715489 1.079874 -0.032419 v -0.650156 1.128751 0.079602 v -0.661221 1.136286 0.088391 v -0.665107 1.135262 0.096802 v -0.655875 1.126043 0.089251 v -0.727985 1.061423 -0.033305 v -0.722420 1.069735 -0.032870 v -0.750205 1.058737 -0.000720 v -0.748885 1.047540 0.001710 v -0.760363 1.057276 0.040441 v -0.754941 1.065133 0.037294 v -0.746603 1.058580 0.040406 v -0.738816 1.066683 0.037093 v -0.736501 1.074059 0.064212 v -0.744545 1.081398 0.061437 v -0.615439 1.228176 -0.023378 v -0.590028 1.247385 -0.040312 v -0.661077 1.157391 -0.045104 v -0.664968 1.169456 -0.023274 v -0.587108 1.181786 -0.037609 v -0.559767 1.211244 -0.028605 v -0.585218 1.196178 -0.011534 v -0.633879 1.137881 -0.012377 v -0.601942 1.175540 -0.005261 v -0.578369 1.210416 -0.074700 v -0.608698 1.163982 -0.034361 v -0.621275 1.146775 -0.032980 v -0.633387 1.131144 -0.031187 v -0.703722 1.088645 -0.016576 v -0.712738 1.079508 -0.014927 v -0.725348 1.067866 -0.012270 v -0.735938 1.057540 -0.009999 v -0.742232 1.050307 -0.008593 v -0.748464 1.045616 -0.006070 v -0.755247 1.050032 -0.008025 v -0.750448 1.057000 -0.010678 v -0.744135 1.064765 -0.012314 v -0.733948 1.075834 -0.014611 v -0.722212 1.088450 -0.018333 v -0.712516 1.098042 -0.021142 v -0.700181 1.105421 -0.026440 v -0.690639 1.125018 -0.031907 v -0.646125 1.169501 -0.050893 v -0.562509 1.198289 -0.055965 v -0.591175 1.179253 -0.050869 v -0.612984 1.162797 -0.047605 v -0.625322 1.146585 -0.048662 v -0.636816 1.132088 -0.048720 v -0.668772 1.105041 -0.043369 v -0.681213 1.094929 -0.040701 v -0.687948 1.088314 -0.040485 v -0.693788 1.080716 -0.040849 v -0.701636 1.070577 -0.041453 v -0.709365 1.060843 -0.041866 v -0.715577 1.053672 -0.041779 v -0.721946 1.047881 -0.041068 v -0.729147 1.052229 -0.043852 v -0.724438 1.060406 -0.044051 v -0.718462 1.068123 -0.043548 v -0.711063 1.077947 -0.043821 v -0.703320 1.088221 -0.043762 v -0.697532 1.096518 -0.043640 v -0.691530 1.105148 -0.044287 v -0.680408 1.116818 -0.047024 v -0.649726 1.143939 -0.055466 v -0.635483 1.155394 -0.056575 v -0.619877 1.167972 -0.057263 v -0.597710 1.182203 -0.061944 v -0.568564 1.198926 -0.068300 v -0.585425 1.188238 -0.024350 v -0.604139 1.167808 -0.019720 v -0.618167 1.151636 -0.015785 v -0.707533 1.094909 0.010309 v -0.715452 1.085618 0.013752 v -0.728003 1.074068 0.020076 v -0.739337 1.062427 0.025747 v -0.746857 1.054017 0.029662 v -0.753558 1.048432 0.033598 v -0.760582 1.053724 0.031725 v -0.755472 1.060903 0.027049 v -0.748096 1.069598 0.022795 v -0.737275 1.081977 0.017413 v -0.726112 1.095113 0.011160 v -0.717731 1.104637 0.006156 v -0.651217 1.185025 -0.031012 v -0.635896 1.199200 -0.039893 v -0.615722 1.214473 -0.051091 v -0.587654 1.231131 -0.065039 v -0.580381 1.244796 -0.016766 v -0.601631 1.222414 -0.000946 v -0.617509 1.203980 0.011000 v -0.630220 1.188406 0.019663 v -0.645694 1.174979 0.034668 v -0.687078 1.141182 0.038127 v -0.701396 1.129938 0.043064 v -0.709294 1.120480 0.048462 v -0.716520 1.111768 0.053868 v -0.726269 1.101887 0.060552 v -0.735777 1.092833 0.066725 v -0.742548 1.086050 0.071014 v -0.747970 1.079823 0.074468 v -0.741887 1.074101 0.076535 v -0.734661 1.079178 0.074287 v -0.727315 1.085463 0.069923 v -0.717241 1.094135 0.063806 v -0.707020 1.103360 0.056808 v -0.698654 1.110362 0.051242 v -0.689008 1.116561 0.045938 v -0.674040 1.125840 0.039400 v -0.623180 1.149869 0.034216 v -0.613861 1.170335 0.018360 v -0.604456 1.187261 0.007201 v -0.588886 1.207248 -0.000362 v -0.568981 1.232436 -0.011231 v -0.679600 1.158810 0.010768 v -0.680724 1.150920 -0.015581 v -0.676335 1.141581 -0.037838 v -0.665889 1.130452 -0.051364 v -0.652305 1.117288 -0.046884 v -0.650516 1.117073 -0.028105 v -0.655411 1.126643 -0.009845 v -0.653438 1.130962 0.012381 v -0.652550 1.133137 0.035798 v -0.652047 1.134045 0.048879 v -0.657034 1.129361 0.062811 v -0.662200 1.124307 0.073166 v -0.667215 1.122067 0.083716 v -0.670780 1.123390 0.093898 v -0.674303 1.132869 0.093584 v -0.671023 1.133583 0.083875 v -0.667644 1.136297 0.075134 v -0.663358 1.140578 0.065521 v -0.661928 1.147833 0.051706 v -0.066206 1.524831 0.051173 v -0.013581 1.521601 -0.156987 v -0.030150 1.528860 -0.161555 v -0.051943 1.537383 -0.163007 v -0.077467 1.546570 -0.156404 v 0.016440 1.733953 0.092814 v 0.015479 1.744801 0.094338 v 0.022064 1.748498 0.095013 v 0.031923 1.750293 0.093778 v 0.041516 1.749222 0.088251 v 0.048488 1.744817 0.081332 v 0.048449 1.732886 0.080714 v 0.042298 1.728872 0.085420 v 0.032368 1.727176 0.089015 v 0.022517 1.729830 0.091192 v 0.011617 1.730172 0.100463 v 0.010992 1.750319 0.102809 v 0.021362 1.754496 0.104559 v 0.034352 1.757105 0.100859 v 0.046749 1.755080 0.090966 v 0.054885 1.748453 0.078446 v 0.055073 1.728446 0.075963 v 0.047762 1.722077 0.084227 v 0.033745 1.717348 0.091640 v 0.019883 1.723545 0.095647 v 0.005542 1.731350 0.109217 v 0.003955 1.751374 0.104446 v -0.000033 1.731820 0.112244 v -0.000105 1.751439 0.104896 v 0.011277 1.712724 0.113847 v 0.005793 1.713540 0.119628 v -0.000010 1.714100 0.122622 v 0.010974 1.720073 0.108118 v 0.005564 1.722013 0.114535 v -0.000038 1.723076 0.117868 v 0.016942 1.715278 0.101556 v 0.024824 1.707887 0.096007 v 0.031870 1.698761 0.092161 v 0.040197 1.681297 0.083590 v 0.030561 1.678311 0.091347 v 0.023189 1.694570 0.096951 v 0.018821 1.701539 0.101010 v 0.016750 1.709550 0.107386 v 0.010757 1.766776 0.105238 v 0.021538 1.769955 0.103235 v 0.036005 1.771319 0.097074 v 0.050902 1.766775 0.085155 v 0.060005 1.755731 0.069133 v 0.004166 1.765125 0.105608 v -0.000087 1.764755 0.106067 v 0.042235 1.671245 0.079756 v 0.033290 1.645675 0.087024 v 0.023829 1.641648 0.097000 v 0.012188 1.648334 0.102717 v 0.000008 1.650845 0.105491 v 0.000043 1.654477 0.104249 v 0.012162 1.653005 0.100362 v 0.021983 1.650285 0.094783 v 0.029301 1.652337 0.090062 v 0.035372 1.669856 0.086431 v 0.000017 1.635961 0.107843 v 0.016596 1.619360 0.058151 v -0.000035 1.615885 0.064632 v 0.025469 1.624636 0.049003 v 0.035695 1.638153 0.079040 v 0.026228 1.631845 0.092068 v 0.000031 1.626929 0.104330 v 0.050788 1.739956 0.078037 v 0.056690 1.740914 0.071917 v 0.013528 1.740929 0.093875 v 0.008377 1.742533 0.099944 v 0.003869 1.743630 0.103772 v -0.000034 1.743795 0.104751 v 0.026682 1.667361 0.091784 v 0.019117 1.667678 0.097280 v 0.011413 1.669253 0.102415 v 0.000059 1.669251 0.104884 v 0.026193 1.658541 0.092529 v 0.000054 1.658008 0.106506 v 0.012084 1.657874 0.102816 v 0.020448 1.657203 0.096986 v 0.027022 1.672997 0.093577 v 0.020145 1.676308 0.100352 v 0.018876 1.669444 0.098634 v 0.011636 1.679527 0.108647 v 0.000025 1.680331 0.109883 v 0.000030 1.670997 0.106078 v 0.010806 1.670935 0.104084 v 0.030894 1.668774 0.090028 v 0.026374 1.666418 0.091716 v 0.029954 1.661002 0.089655 v 0.033811 1.656891 0.086084 v 0.039465 1.652656 0.079524 v 0.010589 1.683931 0.107093 v -0.000056 1.684216 0.108220 v 0.023845 1.666535 0.093650 v 0.024028 1.668292 0.094088 v -0.000036 1.706416 0.126284 v 0.006607 1.706497 0.123249 v 0.011152 1.706459 0.116966 v 0.014360 1.704753 0.110387 v 0.014849 1.700772 0.105939 v 0.011544 1.697742 0.105502 v 0.013785 1.695702 0.103717 v 0.007485 1.696371 0.107887 v 0.007592 1.693601 0.107562 v 0.004247 1.695858 0.110665 v -0.000073 1.692964 0.110126 v 0.020020 1.681538 0.099803 v 0.015806 1.691922 0.101714 v 0.008225 1.690942 0.106929 v 0.000007 1.690820 0.108241 v 0.013676 1.637056 0.105332 v 0.014754 1.628197 0.101139 v 0.008131 1.616610 0.063153 v 0.000053 1.664440 0.108414 v 0.012267 1.664506 0.104780 v 0.019951 1.664088 0.098265 v 0.024738 1.663814 0.093677 v 0.027689 1.664449 0.091295 v 0.028215 1.667919 0.091592 v 0.025251 1.669996 0.094481 v 0.019731 1.672104 0.100195 v 0.011260 1.674506 0.106957 v 0.000034 1.674524 0.109320 v 0.035604 1.632976 0.035981 v 0.048029 1.646799 0.019060 v 0.061889 1.665129 0.002744 v 0.065894 1.680989 -0.008410 v 0.070251 1.685087 0.002219 v 0.045012 1.649855 0.063508 v 0.049767 1.674830 0.068819 v 0.049602 1.685919 0.074742 v 0.043287 1.705239 0.087040 v 0.054608 1.713185 0.077067 v 0.063460 1.723354 0.060011 v 0.062580 1.744276 0.060069 v 0.058550 1.682395 0.053832 v 0.058485 1.693517 0.061171 v 0.066773 1.693695 0.034745 v 0.066196 1.703305 0.042174 v 0.009686 1.643924 0.106047 v 0.000003 1.644385 0.107544 v 0.003994 1.697403 0.117907 v -0.000093 1.696027 0.119655 v 0.005509 1.700325 0.121720 v -0.000081 1.699480 0.124631 v 0.008564 1.701084 0.115837 v 0.011250 1.701146 0.110996 v 0.011209 1.700174 0.107676 v 0.009250 1.699946 0.105040 v 0.005696 1.699643 0.114717 v 0.004806 1.698991 0.109536 v 0.006359 1.698992 0.106701 v 0.006177 1.701333 0.111647 v 0.005263 1.701355 0.108796 v 0.006007 1.701244 0.106868 v 0.007585 1.702590 0.106285 v 0.009251 1.702575 0.107954 v 0.008477 1.702143 0.110056 v 0.007672 1.701306 0.112274 v 0.006667 1.702509 0.109000 v 0.005152 1.700781 0.110806 v 0.004549 1.698954 0.112277 v 0.003329 1.696164 0.114065 v -0.000090 1.694631 0.114814 v 0.021837 1.737594 0.086253 v 0.020275 1.739011 0.085949 v 0.021592 1.740106 0.086528 v 0.025422 1.742281 0.087338 v 0.031555 1.743967 0.087440 v 0.037824 1.743250 0.086457 v 0.041881 1.741717 0.084111 v 0.043163 1.740476 0.082369 v 0.041668 1.738321 0.083256 v 0.037765 1.736606 0.085061 v 0.031964 1.735716 0.086364 v 0.025909 1.736576 0.086677 v 0.032153 1.732865 0.087641 v 0.024682 1.733953 0.088036 v 0.019644 1.735680 0.087783 v 0.017496 1.739742 0.087959 v 0.018864 1.742605 0.087640 v 0.023800 1.745699 0.088220 v 0.031516 1.746377 0.089036 v 0.039002 1.745482 0.086631 v 0.044082 1.742891 0.082938 v 0.045832 1.740236 0.080902 v 0.044092 1.736379 0.082290 v 0.039462 1.733871 0.085429 v 0.022404 1.737726 0.084972 v 0.021954 1.740060 0.085420 v 0.025368 1.742523 0.086120 v 0.031372 1.744508 0.086249 v 0.037616 1.743875 0.085027 v 0.041484 1.742346 0.083176 v 0.042448 1.740651 0.081731 v 0.040869 1.737930 0.082242 v 0.037351 1.736270 0.083567 v 0.031849 1.735163 0.084875 v 0.025985 1.736292 0.085109 v 0.022726 1.737138 0.084041 v 0.022044 1.739008 0.084359 v 0.022144 1.740324 0.084669 v 0.025069 1.742093 0.084169 v 0.031153 1.743902 0.083249 v 0.037458 1.743570 0.082281 v 0.040698 1.742257 0.081774 v 0.041019 1.740608 0.080540 v 0.040371 1.737388 0.080969 v 0.037359 1.735746 0.081271 v 0.031690 1.733979 0.082093 v 0.025410 1.735014 0.083248 v 0.031258 1.740910 0.080416 v 0.037411 1.740976 0.079929 v 0.024765 1.739979 0.082100 v 0.013064 1.786527 0.099713 v 0.024720 1.787806 0.094843 v 0.039612 1.786279 0.085965 v 0.053593 1.778310 0.072103 v 0.063618 1.763804 0.055698 v 0.005313 1.785273 0.101430 v -0.000040 1.784905 0.101766 v 0.069849 1.728509 0.035613 v 0.067525 1.749283 0.044865 v 0.069880 1.704149 0.015914 v 0.069980 1.712265 0.021754 v 0.063120 1.652046 -0.043685 v 0.063008 1.674361 -0.046297 v 0.058976 1.634891 -0.002158 v 0.029928 1.541102 0.033169 v 0.048332 1.537609 0.028333 v 0.065688 1.620044 -0.039398 v 0.046793 1.528980 0.043839 v 0.025353 1.524534 0.046158 v -0.000048 1.613263 0.052220 v 0.000002 1.535384 0.030170 v 0.045401 1.638162 0.011128 v 0.031140 1.628047 0.028755 v 0.006394 1.614430 0.051175 v 0.013189 1.617340 0.047018 v 0.020781 1.621769 0.039483 v 0.021818 1.541617 0.034247 v 0.017871 1.523124 0.045326 v 0.015043 1.540731 0.032244 v 0.008479 1.540329 0.028899 v 0.034863 1.526842 0.046472 v 0.038922 1.539646 0.031007 v 0.064868 1.628958 -0.017339 v 0.068341 1.654261 -0.026186 v 0.069373 1.675792 -0.030955 v -0.000122 1.597369 0.036863 v 0.004866 1.597711 0.035834 v 0.010364 1.598515 0.032876 v 0.016194 1.599834 0.027885 v 0.023492 1.601210 0.021346 v 0.034423 1.600878 0.016177 v 0.047989 1.598148 0.012879 v 0.058352 1.591414 0.003279 v 0.063578 1.580765 -0.009546 v -0.000004 1.566682 0.024621 v 0.003601 1.566476 0.024446 v 0.007553 1.564867 0.024184 v 0.012312 1.562691 0.024284 v 0.018551 1.562169 0.024909 v 0.026544 1.562406 0.026839 v 0.036683 1.561321 0.026377 v 0.046650 1.557509 0.022140 v 0.054687 1.551300 0.016522 v 0.073030 1.758262 0.018127 v 0.068029 1.771070 0.041876 v 0.065141 1.712848 -0.004466 v 0.073107 1.717853 0.005914 v 0.059566 1.789673 0.058214 v 0.068132 1.697142 -0.017676 v 0.045207 1.801670 0.071881 v 0.030267 1.807253 0.081848 v -0.000032 1.810426 0.092106 v 0.017393 1.809724 0.088634 v 0.007525 1.810372 0.091503 v 0.075382 1.763160 -0.000406 v 0.074420 1.781548 0.004536 v 0.070293 1.807330 0.013168 v 0.058346 1.828248 0.021167 v 0.041753 1.841151 0.027304 v 0.025715 1.848985 0.031977 v 0.011688 1.853004 0.034738 v -0.000045 1.854177 0.035574 v 0.069759 1.702470 -0.021451 v 0.076863 1.748759 -0.022851 v 0.076214 1.734640 -0.035953 v 0.074620 1.725802 -0.035785 v 0.073190 1.716841 -0.031757 v 0.076942 1.742609 -0.031703 v 0.076281 1.720511 -0.006288 v 0.072562 1.732126 0.018956 v 0.071177 1.754128 0.030026 v 0.071046 1.776379 0.028348 v 0.064527 1.798235 0.043434 v 0.049998 1.813690 0.055664 v 0.034880 1.822871 0.065535 v 0.020838 1.827950 0.072250 v 0.009365 1.830476 0.075801 v -0.000033 1.831153 0.076778 v 0.070919 1.710329 0.003169 v 0.065640 1.691155 -0.015431 v 0.070634 1.690918 0.008014 v 0.066767 1.678397 0.022452 v 0.056647 1.662779 0.040915 v 0.072247 1.720858 -0.007201 v 0.074912 1.732527 0.002836 v 0.075896 1.740303 0.004206 v 0.076434 1.750853 -0.003857 v 0.067814 1.682050 -0.004032 v 0.064286 1.667920 0.008371 v 0.052101 1.650279 0.026220 v 0.039832 1.636121 0.044662 v 0.029329 1.627494 0.059672 v 0.019541 1.622101 0.070804 v 0.010024 1.619549 0.077228 v 0.000015 1.618811 0.079257 v 0.071620 1.709066 -0.026083 v 0.060788 1.530576 0.036368 v 0.075430 1.534566 0.022602 v 0.057237 1.536253 0.026327 v 0.100306 1.554460 -0.010734 v 0.064605 1.543825 0.015811 v 0.078590 1.567150 -0.015633 v 0.068113 1.698900 -0.033429 v 0.067511 1.707214 -0.034044 v 0.072331 1.743066 -0.033188 v 0.073759 1.751898 -0.028361 v 0.068296 1.715531 -0.034993 v 0.069271 1.724566 -0.035956 v 0.070643 1.734196 -0.035757 v 0.074989 1.759156 -0.020124 v 0.069015 1.689374 -0.032719 v 0.075628 1.762365 -0.010271 v 0.075342 1.781265 -0.007950 v 0.072496 1.808179 -0.003886 v 0.061768 1.830952 0.000108 v 0.045307 1.845691 0.003215 v 0.028351 1.854537 0.005435 v 0.012963 1.859086 0.006719 v -0.000061 1.860283 0.007078 v -0.000230 1.688992 -0.093111 v -0.000290 1.788235 -0.108869 v 0.060154 1.763633 -0.080008 v 0.065012 1.818998 -0.044311 v 0.074245 1.770705 -0.031645 v 0.075491 1.777991 -0.020273 v 0.072928 1.796076 -0.038015 v 0.073628 1.804859 -0.021311 v 0.063929 1.828358 -0.022211 v 0.068877 1.735349 -0.048209 v 0.072646 1.760114 -0.040338 v 0.071072 1.747939 -0.045843 v 0.064210 1.802779 -0.061622 v 0.070342 1.782480 -0.050941 v 0.065477 1.750265 -0.064323 v 0.067762 1.766621 -0.059519 v 0.062446 1.783468 -0.073207 v -0.000070 1.852281 -0.057453 v 0.033397 1.845215 -0.053288 v 0.050801 1.835601 -0.049425 v 0.048165 1.844258 -0.022996 v 0.030846 1.853708 -0.023926 v 0.016113 1.850606 -0.056290 v 0.014451 1.859137 -0.024854 v -0.000069 1.860602 -0.025234 v 0.034035 1.782497 -0.102196 v 0.035259 1.828087 -0.077296 v 0.052199 1.818518 -0.070858 v 0.049278 1.774579 -0.093016 v 0.051606 1.796924 -0.085165 v 0.035500 1.805825 -0.093529 v -0.000114 1.834591 -0.083489 v 0.017288 1.833013 -0.081681 v 0.016834 1.786892 -0.107275 v 0.017430 1.810189 -0.098312 v -0.000225 1.811400 -0.099945 v 0.041588 1.677303 -0.075621 v 0.051134 1.727628 -0.080832 v 0.064123 1.712345 -0.047994 v 0.066245 1.723488 -0.048520 v 0.058460 1.720116 -0.065133 v 0.062011 1.734618 -0.065733 v 0.055962 1.744715 -0.082023 v 0.063127 1.701350 -0.047602 v 0.063175 1.689497 -0.047362 v 0.046828 1.711698 -0.078268 v 0.055125 1.706358 -0.063619 v 0.054023 1.675254 -0.063166 v 0.053212 1.692068 -0.062668 v 0.042852 1.695734 -0.075625 v -0.000301 1.746679 -0.109680 v 0.027626 1.740944 -0.101918 v 0.040563 1.734637 -0.093729 v 0.044964 1.753551 -0.094829 v 0.031096 1.760520 -0.104153 v 0.013885 1.745159 -0.107758 v 0.015607 1.764973 -0.110046 v -0.000355 1.766522 -0.111703 v 0.019183 1.684644 -0.089803 v 0.024547 1.722875 -0.098107 v 0.036450 1.717302 -0.089902 v 0.030659 1.680693 -0.085473 v 0.032466 1.700086 -0.086016 v 0.021594 1.704793 -0.093737 v -0.000239 1.728580 -0.105104 v 0.012187 1.727068 -0.103302 v 0.009029 1.687848 -0.092277 v 0.010591 1.708549 -0.098352 v -0.000257 1.709927 -0.099816 v 0.000020 1.520793 0.043117 v 0.011564 1.522348 0.044505 v 0.005722 1.521526 0.043516 v 0.088852 1.575770 -0.132210 v 0.052399 1.610813 -0.118401 v 0.054458 1.628022 -0.092308 v 0.066325 1.628566 -0.066877 v 0.117309 1.585153 -0.077249 v 0.110944 1.567968 -0.026859 v 0.071153 1.595545 -0.124303 v 0.090386 1.605099 -0.072884 v 0.086150 1.583089 -0.028796 v 0.056826 1.653671 -0.063934 v 0.044108 1.655068 -0.079535 v 0.082673 1.604229 -0.100343 v 0.107148 1.583321 -0.107287 v 0.008800 1.661542 -0.092443 v -0.000168 1.662050 -0.092204 v 0.032176 1.658535 -0.091053 v 0.019401 1.660454 -0.093737 v 0.009332 1.634341 -0.100294 v -0.000038 1.633839 -0.098851 v 0.034686 1.635475 -0.101899 v 0.020422 1.635603 -0.102955 v 0.009940 1.609310 -0.111180 v -0.000007 1.608010 -0.109334 v 0.035436 1.615047 -0.117918 v 0.021072 1.612992 -0.115948 v -0.000167 1.538961 -0.145279 v 0.012223 1.541756 -0.147605 v 0.026971 1.548670 -0.151627 v 0.011210 1.564107 -0.134853 v -0.000133 1.562011 -0.133070 v 0.024171 1.569839 -0.139188 v 0.039722 1.577489 -0.141734 v 0.045780 1.557310 -0.152380 v 0.056400 1.585571 -0.137865 v 0.067302 1.566757 -0.146987 v 0.047841 1.598169 -0.130298 v 0.035728 1.596246 -0.130847 v 0.021966 1.591440 -0.127763 v 0.010810 1.587211 -0.122010 v -0.000120 1.585393 -0.119345 v -0.000037 1.844370 0.058206 v 0.010542 1.843437 0.057247 v 0.023309 1.840075 0.053884 v 0.038387 1.833530 0.047842 v 0.054304 1.822209 0.039422 v 0.067169 1.803566 0.028582 v 0.072953 1.779848 0.016483 v 0.074419 1.761748 0.008789 v 0.076858 1.747115 0.001483 v 0.070263 1.697215 0.012293 v 0.066949 1.685391 0.028486 v 0.057849 1.672101 0.047424 v 0.048762 1.663781 0.064639 v 0.042141 1.661730 0.077786 v 0.035892 1.662841 0.084875 v 0.031603 1.664481 0.088914 v 0.028961 1.665901 0.090707 v 0.027337 1.666702 0.091190 v 0.076444 1.750926 -0.011798 v -0.000060 1.582050 0.030366 v 0.004349 1.582245 0.029321 v 0.008983 1.582512 0.026869 v 0.014108 1.582812 0.023709 v 0.021065 1.582801 0.020904 v 0.030804 1.582183 0.020995 v 0.042676 1.580210 0.019778 v 0.053207 1.574617 0.012935 v 0.060546 1.565681 0.004096 v 0.072045 1.554122 0.000164 v 0.088515 1.542491 0.005471 v 0.005047 1.550807 0.024670 v 0.000005 1.546272 0.025246 v 0.000011 1.554737 0.023735 v 0.002948 1.556099 0.023814 v 0.000038 1.620693 0.093186 v 0.012678 1.621893 0.090585 v 0.023411 1.625304 0.082758 v 0.033717 1.631166 0.069837 v 0.043489 1.640605 0.053099 v 0.055302 1.654711 0.033052 v 0.065942 1.672331 0.015393 v 0.075455 1.724851 -0.016817 v 0.077993 1.733473 -0.008316 v 0.079963 1.744299 -0.010268 v 0.084202 1.747008 -0.020146 v 0.085646 1.751063 -0.016501 v 0.083964 1.745605 -0.005179 v 0.081348 1.737237 -0.002213 v 0.078995 1.737107 -0.006182 v 0.084682 1.745129 -0.027761 v 0.084040 1.741523 -0.031278 v 0.083199 1.735492 -0.033578 v 0.083904 1.736295 -0.037335 v 0.085018 1.743047 -0.034405 v 0.085676 1.749057 -0.028961 v 0.078676 1.730526 -0.002604 v 0.075795 1.721044 -0.010951 v 0.079993 1.754382 -0.014894 v 0.079132 1.735682 -0.036989 v 0.080014 1.743198 -0.034047 v 0.080149 1.751023 -0.027795 v 0.079769 1.752202 -0.006075 v 0.081446 1.728202 -0.034219 v 0.080140 1.719759 -0.031680 v 0.078578 1.711046 -0.027873 v 0.076120 1.703245 -0.022557 v 0.077901 1.701380 -0.027078 v 0.079617 1.709586 -0.032314 v 0.080856 1.718512 -0.036285 v 0.082016 1.727798 -0.038393 v 0.073498 1.701292 -0.026845 v 0.075137 1.709569 -0.030627 v 0.076303 1.718309 -0.034170 v 0.077470 1.727204 -0.036750 v 0.076110 1.696404 -0.015891 v 0.075880 1.688338 -0.007354 v 0.073928 1.685553 -0.014040 v 0.076520 1.693935 -0.021143 v 0.072249 1.694338 -0.021957 v 0.070352 1.688843 -0.014849 v 0.070346 1.703779 0.001170 v 0.066794 1.705050 -0.005241 v 0.072327 1.697160 -0.002218 v 0.071613 1.702806 -0.009547 v 0.065883 1.680467 -0.012559 v 0.061759 1.661888 -0.003208 v 0.066269 1.658645 -0.012145 v 0.068068 1.679207 -0.019108 v 0.067028 1.690578 -0.021785 v 0.066656 1.697872 -0.022940 v 0.067306 1.704369 -0.024368 v 0.068792 1.711066 -0.026890 v 0.070821 1.718288 -0.031645 v 0.072122 1.727282 -0.034617 v 0.073296 1.735921 -0.033303 v 0.074178 1.743700 -0.027957 v 0.074239 1.750685 -0.020016 v 0.074834 1.753667 -0.010693 v 0.075364 1.753619 -0.002392 v 0.075229 1.751125 0.004691 v 0.074194 1.744087 0.011074 v 0.073994 1.733394 0.008456 v 0.072240 1.741722 0.023885 v 0.068946 1.738042 0.039760 v 0.063459 1.734822 0.057789 v 0.056725 1.734630 0.071748 v 0.050466 1.736369 0.078056 v 0.045657 1.738340 0.080686 v 0.043110 1.739510 0.082231 v 0.042455 1.739267 0.081454 v 0.040939 1.738749 0.080182 v 0.037268 1.737952 0.079761 v 0.031389 1.737208 0.080290 v 0.025047 1.737210 0.081703 v 0.022245 1.738082 0.083969 v 0.020310 1.738237 0.085922 v 0.017843 1.737850 0.088142 v 0.013913 1.737745 0.093533 v 0.008596 1.737475 0.099514 v 0.004697 1.738221 0.105789 v -0.000022 1.738311 0.107677 v 0.020758 1.738982 0.084846 v 0.021009 1.738169 0.084825 v 0.083884 1.728019 -0.027851 v 0.082049 1.720263 -0.025463 v 0.077695 1.713336 -0.021535 v 0.074529 1.707918 -0.016279 v 0.066732 1.712506 -0.009922 v 0.072644 1.714223 -0.014272 v 0.075886 1.719035 -0.018948 v 0.080961 1.727326 -0.020681 v 0.079311 1.722572 -0.021539 v 0.079757 1.739110 -0.015888 v 0.085101 1.742390 -0.024457 v 0.085178 1.734921 -0.027381 v 0.082519 1.732832 -0.018880 v 0.075867 1.729004 -0.012865 v 0.077068 1.726102 -0.005939 v 0.074239 1.726985 -0.002165 v 0.073959 1.726861 0.002416 v 0.072695 1.724917 0.012615 v 0.069925 1.720304 0.029019 v 0.065473 1.712886 0.050301 v 0.057624 1.703466 0.068903 v 0.048045 1.695440 0.081280 v 0.036614 1.690315 0.087982 v 0.027304 1.687463 0.094477 v 0.018346 1.687018 0.100441 v 0.009477 1.687585 0.106700 v -0.000053 1.687639 0.107997 v 0.090927 1.597029 -0.047136 v 0.117632 1.578977 -0.049122 v -0.000134 1.607983 0.045941 v 0.006127 1.608663 0.044819 v 0.012520 1.610448 0.041153 v 0.019181 1.612788 0.034426 v 0.027034 1.616058 0.024984 v 0.038442 1.617996 0.013932 v 0.053218 1.615282 0.006285 v 0.061660 1.608403 -0.006838 v 0.065602 1.597407 -0.023120 v 0.026164 1.666994 0.090746 v 0.023149 1.667386 0.092814 v 0.025362 1.667083 0.091259 v 0.023135 1.667651 0.093041 v 0.025489 1.667201 0.091265 v 0.000041 1.670299 0.103229 v 0.010442 1.669980 0.101243 v 0.018221 1.668650 0.096556 v 0.018678 1.668534 0.095726 v 0.010660 1.669179 0.099465 v 0.000064 1.668598 0.101684 v -0.016542 1.733953 0.092813 v -0.015484 1.744801 0.094338 v -0.022069 1.748498 0.095013 v -0.031927 1.750293 0.093778 v -0.041523 1.749222 0.088251 v -0.048497 1.744817 0.081332 v -0.048467 1.732886 0.080713 v -0.042318 1.728872 0.085420 v -0.032400 1.727176 0.089015 v -0.022610 1.729828 0.091188 v -0.011625 1.730172 0.100463 v -0.011005 1.750319 0.102809 v -0.021379 1.754496 0.104559 v -0.034375 1.757106 0.100859 v -0.046776 1.755080 0.090966 v -0.054908 1.748453 0.078446 v -0.055157 1.728446 0.075963 v -0.047877 1.722077 0.084230 v -0.033859 1.717347 0.091638 v -0.019945 1.723545 0.095648 v -0.005558 1.731350 0.109217 v -0.004015 1.751374 0.104444 v -0.011457 1.712728 0.113853 v -0.005815 1.713540 0.119628 v -0.010982 1.720073 0.108118 v -0.005593 1.722012 0.114534 v -0.016982 1.715278 0.101556 v -0.024901 1.707887 0.096006 v -0.031904 1.698761 0.092161 v -0.040209 1.681297 0.083590 v -0.030639 1.678311 0.091348 v -0.023311 1.694571 0.096956 v -0.018928 1.701537 0.101013 v -0.016962 1.709539 0.107375 v -0.010763 1.766776 0.105238 v -0.021546 1.769955 0.103235 v -0.036087 1.771319 0.097074 v -0.050916 1.766775 0.085155 v -0.060022 1.755731 0.069133 v -0.004196 1.765125 0.105607 v -0.042283 1.671245 0.079756 v -0.033294 1.645675 0.087024 v -0.023843 1.641648 0.097000 v -0.012206 1.648334 0.102717 v -0.012180 1.653005 0.100362 v -0.021988 1.650285 0.094783 v -0.029306 1.652337 0.090062 v -0.035388 1.669856 0.086431 v -0.016602 1.619360 0.058151 v -0.025474 1.624636 0.049003 v -0.035703 1.638153 0.079040 v -0.026234 1.631845 0.092068 v -0.050793 1.739956 0.078037 v -0.056709 1.740914 0.071917 v -0.013574 1.740929 0.093875 v -0.008409 1.742533 0.099944 v -0.003882 1.743630 0.103772 v -0.026687 1.667361 0.091784 v -0.019123 1.667678 0.097280 v -0.011419 1.669253 0.102415 v -0.026213 1.658541 0.092529 v -0.012117 1.657874 0.102816 v -0.020521 1.657203 0.096986 v -0.027027 1.672997 0.093577 v -0.020150 1.676308 0.100352 v -0.018881 1.669444 0.098634 v -0.011641 1.679527 0.108647 v -0.010811 1.670935 0.104084 v -0.030902 1.668774 0.090028 v -0.026379 1.666418 0.091716 v -0.029959 1.661002 0.089655 v -0.033816 1.656891 0.086084 v -0.039478 1.652656 0.079524 v -0.010594 1.683931 0.107093 v -0.023851 1.666535 0.093650 v -0.024032 1.668292 0.094088 v -0.006619 1.706497 0.123248 v -0.011264 1.706459 0.116965 v -0.014554 1.704753 0.110388 v -0.014923 1.700773 0.105940 v -0.011562 1.697742 0.105502 v -0.013797 1.695702 0.103717 v -0.007490 1.696371 0.107887 v -0.007597 1.693601 0.107562 v -0.004252 1.695858 0.110665 v -0.020025 1.681538 0.099803 v -0.015811 1.691922 0.101714 v -0.008231 1.690942 0.106929 v -0.013687 1.637056 0.105332 v -0.014759 1.628197 0.101139 v -0.008136 1.616610 0.063153 v -0.012272 1.664506 0.104780 v -0.019982 1.664088 0.098265 v -0.024751 1.663814 0.093677 v -0.027694 1.664449 0.091295 v -0.028220 1.667919 0.091592 v -0.025256 1.669996 0.094481 v -0.019736 1.672104 0.100195 v -0.011265 1.674506 0.106957 v -0.035610 1.632976 0.035981 v -0.048034 1.646799 0.019060 v -0.061928 1.665128 0.002743 v -0.065957 1.680984 -0.008410 v -0.070460 1.685080 0.002219 v -0.045017 1.649855 0.063508 v -0.049780 1.674830 0.068819 v -0.049637 1.685919 0.074742 v -0.043342 1.705239 0.087040 v -0.054704 1.713185 0.077066 v -0.063626 1.723354 0.060011 v -0.062615 1.744277 0.060070 v -0.058563 1.682395 0.053832 v -0.058500 1.693517 0.061171 v -0.066917 1.693695 0.034745 v -0.066305 1.703305 0.042174 v -0.009701 1.643924 0.106047 v -0.003999 1.697403 0.117907 v -0.005514 1.700325 0.121720 v -0.008569 1.701084 0.115837 v -0.011268 1.701146 0.110996 v -0.011231 1.700174 0.107676 v -0.009263 1.699946 0.105040 v -0.005701 1.699643 0.114717 v -0.004811 1.698991 0.109536 v -0.006364 1.698992 0.106701 v -0.006182 1.701333 0.111647 v -0.005268 1.701355 0.108796 v -0.006012 1.701244 0.106868 v -0.007591 1.702590 0.106285 v -0.009266 1.702575 0.107954 v -0.008487 1.702143 0.110057 v -0.007678 1.701306 0.112274 v -0.006673 1.702509 0.109000 v -0.005157 1.700781 0.110806 v -0.004555 1.698954 0.112277 v -0.003334 1.696164 0.114065 v -0.021851 1.737594 0.086253 v -0.020283 1.739011 0.085949 v -0.021599 1.740106 0.086528 v -0.025427 1.742281 0.087338 v -0.031560 1.743967 0.087440 v -0.037829 1.743250 0.086457 v -0.041888 1.741717 0.084111 v -0.043171 1.740476 0.082369 v -0.041684 1.738321 0.083256 v -0.037792 1.736606 0.085061 v -0.031990 1.735716 0.086364 v -0.025928 1.736576 0.086677 v -0.032185 1.732865 0.087641 v -0.024712 1.733953 0.088036 v -0.019678 1.735680 0.087784 v -0.017509 1.739742 0.087959 v -0.018869 1.742605 0.087640 v -0.023805 1.745699 0.088220 v -0.031521 1.746377 0.089036 v -0.039007 1.745482 0.086631 v -0.044087 1.742891 0.082938 v -0.045838 1.740236 0.080902 v -0.044112 1.736378 0.082289 v -0.039494 1.733870 0.085429 v -0.022416 1.737726 0.084972 v -0.021962 1.740060 0.085420 v -0.025373 1.742523 0.086120 v -0.031377 1.744508 0.086249 v -0.037621 1.743875 0.085027 v -0.041489 1.742346 0.083176 v -0.042455 1.740651 0.081731 v -0.040885 1.737930 0.082242 v -0.037377 1.736271 0.083567 v -0.031874 1.735163 0.084875 v -0.026000 1.736292 0.085110 v -0.022737 1.737138 0.084041 v -0.022052 1.739008 0.084359 v -0.022150 1.740324 0.084669 v -0.025074 1.742093 0.084169 v -0.031158 1.743902 0.083249 v -0.037463 1.743570 0.082281 v -0.040703 1.742257 0.081774 v -0.041026 1.740608 0.080540 v -0.040387 1.737388 0.080969 v -0.037370 1.735746 0.081271 v -0.031695 1.733979 0.082093 v -0.025418 1.735014 0.083247 v -0.031263 1.740910 0.080416 v -0.037416 1.740976 0.079929 v -0.024770 1.739979 0.082100 v -0.013069 1.786527 0.099713 v -0.024759 1.787806 0.094843 v -0.039835 1.786281 0.085968 v -0.053661 1.778309 0.072102 v -0.063634 1.763804 0.055698 v -0.005318 1.785273 0.101430 v -0.070145 1.728509 0.035612 v -0.067574 1.749283 0.044865 v -0.069933 1.704149 0.015914 v -0.070041 1.712265 0.021753 v -0.063126 1.652046 -0.043685 v -0.063014 1.674361 -0.046297 v -0.058981 1.634891 -0.002158 v -0.029933 1.541102 0.033169 v -0.048337 1.537609 0.028333 v -0.065693 1.620044 -0.039398 v -0.046798 1.528980 0.043839 v -0.025358 1.524534 0.046158 v -0.045406 1.638162 0.011128 v -0.031166 1.628047 0.028755 v -0.006441 1.614429 0.051175 v -0.013224 1.617340 0.047018 v -0.020806 1.621769 0.039483 v -0.021823 1.541617 0.034247 v -0.017876 1.523124 0.045326 v -0.015049 1.540731 0.032244 v -0.008485 1.540329 0.028899 v -0.034868 1.526842 0.046472 v -0.038927 1.539646 0.031007 v -0.064873 1.628958 -0.017339 v -0.068348 1.654261 -0.026186 v -0.069381 1.675792 -0.030955 v -0.005057 1.597715 0.035828 v -0.010495 1.598517 0.032872 v -0.016260 1.599836 0.027883 v -0.023527 1.601210 0.021346 v -0.034439 1.600878 0.016177 v -0.047995 1.598148 0.012879 v -0.058357 1.591414 0.003279 v -0.063583 1.580765 -0.009546 v -0.003614 1.566476 0.024446 v -0.007563 1.564867 0.024184 v -0.012320 1.562691 0.024284 v -0.018559 1.562169 0.024909 v -0.026556 1.562406 0.026839 v -0.036690 1.561321 0.026377 v -0.046656 1.557509 0.022140 v -0.054692 1.551300 0.016522 v -0.073035 1.758262 0.018127 v -0.068084 1.771070 0.041875 v -0.065477 1.712833 -0.004526 v -0.073198 1.717853 0.005915 v -0.059684 1.789672 0.058213 v -0.068208 1.697144 -0.017675 v -0.045375 1.801670 0.071882 v -0.030359 1.807252 0.081847 v -0.017400 1.809724 0.088634 v -0.007537 1.810372 0.091503 v -0.075387 1.763160 -0.000406 v -0.074425 1.781548 0.004536 v -0.070298 1.807330 0.013168 v -0.058351 1.828248 0.021167 v -0.041758 1.841151 0.027304 v -0.025720 1.848985 0.031977 v -0.011693 1.853004 0.034738 v -0.069789 1.702471 -0.021450 v -0.076874 1.748758 -0.022851 v -0.076228 1.734640 -0.035953 v -0.074637 1.725803 -0.035785 v -0.073208 1.716842 -0.031757 v -0.076955 1.742609 -0.031703 v -0.076349 1.720509 -0.006286 v -0.072818 1.732124 0.018954 v -0.071191 1.754128 0.030026 v -0.071070 1.776379 0.028348 v -0.064633 1.798235 0.043433 v -0.050047 1.813689 0.055665 v -0.034947 1.822871 0.065535 v -0.020846 1.827950 0.072250 v -0.009370 1.830476 0.075801 v -0.070968 1.710329 0.003169 v -0.065724 1.691149 -0.015432 v -0.070868 1.690916 0.008013 v -0.066931 1.678397 0.022452 v -0.056658 1.662779 0.040915 v -0.072473 1.720847 -0.007229 v -0.075044 1.732521 0.002831 v -0.075944 1.740302 0.004206 v -0.076442 1.750853 -0.003857 v -0.067962 1.682032 -0.004031 v -0.064396 1.667919 0.008371 v -0.052123 1.650279 0.026220 v -0.039838 1.636121 0.044662 v -0.029334 1.627494 0.059672 v -0.019546 1.622101 0.070804 v -0.010029 1.619549 0.077228 v -0.071660 1.709066 -0.026082 v -0.060794 1.530576 0.036368 v -0.075436 1.534566 0.022602 v -0.057243 1.536253 0.026327 v -0.100311 1.554460 -0.010734 v -0.064610 1.543825 0.015811 v -0.078595 1.567150 -0.015633 v -0.068128 1.698900 -0.033429 v -0.067528 1.707215 -0.034044 v -0.072343 1.743066 -0.033188 v -0.073771 1.751898 -0.028361 v -0.068313 1.715531 -0.034993 v -0.069285 1.724566 -0.035955 v -0.070656 1.734196 -0.035756 v -0.074998 1.759156 -0.020123 v -0.069027 1.689374 -0.032719 v -0.075634 1.762365 -0.010271 v -0.075347 1.781265 -0.007950 v -0.072501 1.808179 -0.003886 v -0.061773 1.830952 0.000108 v -0.045312 1.845691 0.003215 v -0.028356 1.854537 0.005435 v -0.012969 1.859086 0.006719 v -0.060159 1.763633 -0.080008 v -0.065017 1.818998 -0.044311 v -0.074253 1.770705 -0.031645 v -0.075498 1.777990 -0.020273 v -0.072933 1.796076 -0.038015 v -0.073633 1.804859 -0.021311 v -0.063934 1.828358 -0.022211 v -0.068889 1.735349 -0.048209 v -0.072655 1.760113 -0.040338 v -0.071083 1.747939 -0.045842 v -0.064215 1.802779 -0.061622 v -0.070347 1.782480 -0.050941 v -0.065484 1.750265 -0.064323 v -0.067768 1.766621 -0.059519 v -0.062451 1.783468 -0.073207 v -0.033402 1.845215 -0.053288 v -0.050806 1.835601 -0.049425 v -0.048170 1.844258 -0.022996 v -0.030851 1.853708 -0.023926 v -0.016118 1.850606 -0.056290 v -0.014456 1.859137 -0.024854 v -0.034040 1.782497 -0.102196 v -0.035264 1.828087 -0.077296 v -0.052204 1.818518 -0.070858 v -0.049283 1.774579 -0.093016 v -0.051611 1.796924 -0.085165 v -0.035505 1.805825 -0.093529 v -0.017293 1.833013 -0.081681 v -0.016839 1.786892 -0.107275 v -0.017435 1.810189 -0.098312 v -0.041593 1.677303 -0.075621 v -0.051139 1.727628 -0.080832 v -0.064135 1.712345 -0.047994 v -0.066256 1.723488 -0.048520 v -0.058466 1.720116 -0.065133 v -0.062018 1.734618 -0.065733 v -0.055967 1.744715 -0.082023 v -0.063136 1.701350 -0.047602 v -0.063182 1.689497 -0.047362 v -0.046833 1.711698 -0.078268 v -0.055131 1.706358 -0.063619 v -0.054028 1.675254 -0.063166 v -0.053219 1.692068 -0.062668 v -0.042857 1.695734 -0.075625 v -0.027631 1.740944 -0.101918 v -0.040568 1.734637 -0.093729 v -0.044969 1.753551 -0.094829 v -0.031101 1.760520 -0.104154 v -0.013891 1.745159 -0.107758 v -0.015613 1.764973 -0.110046 v -0.019188 1.684644 -0.089803 v -0.024552 1.722875 -0.098107 v -0.036455 1.717302 -0.089902 v -0.030664 1.680693 -0.085473 v -0.032471 1.700086 -0.086016 v -0.021599 1.704793 -0.093737 v -0.012196 1.727068 -0.103302 v -0.009080 1.687851 -0.092276 v -0.010639 1.708551 -0.098348 v -0.011570 1.522348 0.044505 v -0.005727 1.521526 0.043516 v -0.088857 1.575770 -0.132210 v -0.052404 1.610813 -0.118401 v -0.054463 1.628022 -0.092308 v -0.066330 1.628566 -0.066877 v -0.117314 1.585153 -0.077249 v -0.110949 1.567968 -0.026859 v -0.071158 1.595545 -0.124303 v -0.090391 1.605099 -0.072884 v -0.086155 1.583089 -0.028796 v -0.056832 1.653671 -0.063934 v -0.044114 1.655068 -0.079535 v -0.082679 1.604229 -0.100343 v -0.107153 1.583321 -0.107287 v -0.008852 1.661546 -0.092446 v -0.032181 1.658535 -0.091053 v -0.019406 1.660454 -0.093737 v -0.009337 1.634341 -0.100294 v -0.034691 1.635475 -0.101899 v -0.020427 1.635603 -0.102955 v -0.009945 1.609310 -0.111180 v -0.035441 1.615047 -0.117918 v -0.021077 1.612992 -0.115948 v -0.012228 1.541756 -0.147605 v -0.026976 1.548670 -0.151627 v -0.011215 1.564107 -0.134853 v -0.024177 1.569839 -0.139188 v -0.039728 1.577489 -0.141734 v -0.045785 1.557310 -0.152380 v -0.056405 1.585571 -0.137865 v -0.067307 1.566757 -0.146987 v -0.047846 1.598169 -0.130298 v -0.035734 1.596246 -0.130847 v -0.021972 1.591440 -0.127763 v -0.010815 1.587211 -0.122010 v -0.010547 1.843437 0.057247 v -0.023314 1.840075 0.053884 v -0.038393 1.833530 0.047842 v -0.054313 1.822208 0.039422 v -0.067212 1.803566 0.028582 v -0.072967 1.779848 0.016483 v -0.074424 1.761748 0.008789 v -0.076870 1.747116 0.001483 v -0.070455 1.697214 0.012293 v -0.067097 1.685391 0.028486 v -0.057857 1.672101 0.047424 v -0.048770 1.663781 0.064639 v -0.042186 1.661730 0.077786 v -0.035897 1.662841 0.084875 v -0.031608 1.664481 0.088914 v -0.028966 1.665901 0.090707 v -0.027342 1.666702 0.091190 v -0.076454 1.750925 -0.011798 v -0.004418 1.582246 0.029320 v -0.009036 1.582513 0.026868 v -0.014137 1.582812 0.023708 v -0.021082 1.582801 0.020904 v -0.030821 1.582183 0.020995 v -0.042688 1.580210 0.019778 v -0.053214 1.574617 0.012935 v -0.060551 1.565681 0.004096 v -0.072050 1.554122 0.000164 v -0.088520 1.542491 0.005471 v -0.005053 1.550807 0.024670 v -0.002954 1.556098 0.023814 v -0.012683 1.621893 0.090585 v -0.023416 1.625304 0.082758 v -0.033722 1.631166 0.069837 v -0.043494 1.640605 0.053099 v -0.055321 1.654711 0.033052 v -0.066106 1.672330 0.015393 v -0.075517 1.724859 -0.016819 v -0.078670 1.733459 -0.008283 v -0.080172 1.744300 -0.010265 v -0.084499 1.746982 -0.020146 v -0.085683 1.751063 -0.016501 v -0.084054 1.745605 -0.005176 v -0.081515 1.737245 -0.002214 v -0.079660 1.737020 -0.006208 v -0.084852 1.745111 -0.027748 v -0.084122 1.741521 -0.031275 v -0.083280 1.735492 -0.033575 v -0.083984 1.736294 -0.037333 v -0.085079 1.743047 -0.034405 v -0.085706 1.749057 -0.028962 v -0.078916 1.730537 -0.002625 v -0.076132 1.721060 -0.010974 v -0.080004 1.754382 -0.014894 v -0.079147 1.735682 -0.036988 v -0.080027 1.743198 -0.034047 v -0.080160 1.751023 -0.027795 v -0.079774 1.752202 -0.006074 v -0.081502 1.728201 -0.034217 v -0.080237 1.719759 -0.031680 v -0.078697 1.711049 -0.027874 v -0.076225 1.703248 -0.022558 v -0.077965 1.701381 -0.027078 v -0.079663 1.709587 -0.032315 v -0.080938 1.718514 -0.036284 v -0.082085 1.727799 -0.038394 v -0.073533 1.701293 -0.026844 v -0.075165 1.709569 -0.030627 v -0.076325 1.718310 -0.034170 v -0.077493 1.727205 -0.036750 v -0.076222 1.696404 -0.015892 v -0.076092 1.688368 -0.007369 v -0.074101 1.685568 -0.014038 v -0.076624 1.693936 -0.021143 v -0.072325 1.694338 -0.021955 v -0.070498 1.688837 -0.014849 v -0.070426 1.703779 0.001171 v -0.066925 1.705056 -0.005257 v -0.072508 1.697161 -0.002220 v -0.071798 1.702803 -0.009550 v -0.065924 1.680465 -0.012559 v -0.061767 1.661888 -0.003208 v -0.066277 1.658645 -0.012145 v -0.068091 1.679207 -0.019108 v -0.067062 1.690576 -0.021785 v -0.066676 1.697872 -0.022940 v -0.067325 1.704370 -0.024367 v -0.068812 1.711066 -0.026890 v -0.070837 1.718289 -0.031645 v -0.072137 1.727283 -0.034617 v -0.073310 1.735921 -0.033303 v -0.074192 1.743699 -0.027957 v -0.074250 1.750684 -0.020016 v -0.074842 1.753666 -0.010693 v -0.075370 1.753619 -0.002392 v -0.075238 1.751125 0.004691 v -0.074252 1.744084 0.011073 v -0.074165 1.733387 0.008455 v -0.072430 1.741721 0.023884 v -0.069200 1.738042 0.039761 v -0.063621 1.734822 0.057789 v -0.056749 1.734630 0.071748 v -0.050472 1.736369 0.078056 v -0.045665 1.738340 0.080686 v -0.043123 1.739510 0.082231 v -0.042468 1.739267 0.081454 v -0.040950 1.738749 0.080182 v -0.037273 1.737952 0.079761 v -0.031394 1.737208 0.080290 v -0.025052 1.737210 0.081703 v -0.022254 1.738082 0.083969 v -0.020320 1.738237 0.085922 v -0.017866 1.737850 0.088142 v -0.013983 1.737746 0.093533 v -0.008616 1.737475 0.099515 v -0.004702 1.738221 0.105789 v -0.020766 1.738982 0.084846 v -0.021019 1.738169 0.084825 v -0.084278 1.728020 -0.027854 v -0.082458 1.720284 -0.025477 v -0.078181 1.713315 -0.021514 v -0.074896 1.707891 -0.016325 v -0.066973 1.712496 -0.009964 v -0.072887 1.714231 -0.014278 v -0.076042 1.719061 -0.018970 v -0.081428 1.727332 -0.020669 v -0.079601 1.722572 -0.021521 v -0.080443 1.739083 -0.015822 v -0.085414 1.742389 -0.024470 v -0.085530 1.734919 -0.027400 v -0.083075 1.732831 -0.018878 v -0.076001 1.729014 -0.012865 v -0.077444 1.726111 -0.005953 v -0.074448 1.726965 -0.002171 v -0.074107 1.726852 0.002416 v -0.072884 1.724916 0.012616 v -0.070101 1.720304 0.029020 v -0.065569 1.712886 0.050301 v -0.057647 1.703466 0.068903 v -0.048119 1.695440 0.081280 v -0.036626 1.690315 0.087982 v -0.027440 1.687463 0.094481 v -0.018351 1.687018 0.100441 v -0.009482 1.687585 0.106700 v -0.090933 1.597029 -0.047136 v -0.117637 1.578977 -0.049122 v -0.006319 1.608667 0.044813 v -0.012645 1.610453 0.041146 v -0.019239 1.612790 0.034423 v -0.027059 1.616058 0.024984 v -0.038452 1.617996 0.013932 v -0.053223 1.615282 0.006285 v -0.061665 1.608403 -0.006838 v -0.065607 1.597407 -0.023120 v -0.026169 1.666994 0.090746 v -0.023154 1.667386 0.092814 v -0.025367 1.667083 0.091259 v -0.023140 1.667651 0.093041 v -0.025494 1.667201 0.091265 v -0.010447 1.669980 0.101243 v -0.018226 1.668650 0.096556 v -0.018683 1.668534 0.095726 v -0.010665 1.669179 0.099465 v 0.240419 0.028025 -0.064598 v 0.214742 0.028524 -0.135022 v 0.170136 0.028524 -0.153646 v 0.141940 0.028524 -0.131052 v 0.153248 0.027295 -0.055831 v 0.255085 0.016737 0.039452 v 0.238718 0.016737 0.097813 v 0.145661 0.016737 0.050411 v 0.164969 0.016737 0.113922 v 0.202729 0.016737 0.120323 v 0.242952 0.028025 -0.066538 v 0.215666 0.028524 -0.141348 v 0.168821 0.028524 -0.160906 v 0.139209 0.028524 -0.137178 v 0.150897 0.027295 -0.057712 v 0.258034 0.016737 0.042949 v 0.240914 0.016737 0.103692 v 0.143524 0.016737 0.054426 v 0.164210 0.016737 0.120066 v 0.203423 0.016737 0.126789 v 0.242952 0.013555 -0.070778 v 0.242952 0.013555 -0.070778 v 0.242952 0.013555 -0.070778 v 0.215666 0.014054 -0.141348 v 0.168821 0.014054 -0.160906 v 0.139209 0.014054 -0.137178 v 0.150897 0.012826 -0.061952 v 0.150897 0.012826 -0.061952 v 0.150897 0.012826 -0.061952 v 0.258034 -0.001088 0.025696 v 0.240914 -0.000249 0.103692 v 0.143524 -0.001088 0.037173 v 0.164210 -0.000249 0.120066 v 0.203423 -0.000249 0.126789 v 0.242952 -0.000311 -0.070778 v 0.242952 -0.000311 -0.070778 v 0.215666 0.000188 -0.141348 v 0.168821 0.000188 -0.160906 v 0.139209 0.000188 -0.137178 v 0.150897 -0.001041 -0.061952 v 0.150897 -0.001041 -0.061952 v 0.250941 0.022381 -0.013800 v 0.154169 0.022381 -0.007738 v 0.253682 0.022381 -0.013022 v 0.151925 0.022381 -0.006671 v 0.253682 0.005814 -0.021702 v 0.151925 0.005814 -0.015351 v 0.141647 0.491328 -0.114189 v 0.205701 0.490563 -0.094984 v 0.204864 0.482881 -0.001814 v 0.219844 0.485703 -0.041760 v 0.095093 0.485847 -0.085518 v 0.097055 0.480678 -0.033262 v 0.129203 0.478420 0.018134 v 0.172100 0.480332 0.031533 v 0.134275 1.530679 -0.176386 v 0.172047 1.568379 -0.134707 v 0.183198 1.581694 -0.087722 v 0.183899 1.582353 -0.037207 v 0.175968 1.570682 0.009387 v 0.154211 1.545088 0.050300 v 0.127586 1.516320 0.085776 v 0.090502 1.480065 0.118043 v 0.048199 1.439660 0.139210 v 0.004523 1.397720 0.152934 v -0.046749 1.346165 0.168389 v -0.100793 1.286092 0.157968 v -0.142447 1.245674 0.120062 v -0.172509 1.215475 0.065591 v -0.184084 1.200966 0.010595 v -0.189708 1.193441 -0.037206 v -0.183778 1.199644 -0.083800 v -0.161577 1.220932 -0.127256 v -0.125529 1.256296 -0.164504 v -0.082149 1.299133 -0.193113 v -0.040545 1.343281 -0.203115 v 0.004932 1.390315 -0.209998 v 0.049917 1.437272 -0.211098 v 0.091361 1.483013 -0.199357 v 0.085005 1.564355 -0.174013 v 0.115650 1.595672 -0.131694 v 0.128002 1.611723 -0.087199 v 0.130140 1.610316 -0.037207 v 0.123674 1.601299 0.009387 v 0.103645 1.579924 0.048363 v 0.078677 1.552097 0.085776 v 0.047546 1.519726 0.111321 v 0.006099 1.478006 0.130594 v -0.039131 1.432221 0.142984 v -0.086835 1.384427 0.149293 v -0.134336 1.337502 0.138490 v -0.172510 1.300595 0.106263 v -0.197874 1.272699 0.058544 v -0.209579 1.258838 0.009387 v -0.212954 1.250003 -0.037206 v -0.207024 1.256206 -0.083800 v -0.189637 1.274392 -0.127219 v -0.162299 1.301960 -0.167083 v -0.125744 1.339687 -0.198121 v -0.084020 1.384709 -0.206740 v -0.038853 1.432207 -0.209998 v 0.006131 1.479163 -0.211098 v 0.048350 1.524587 -0.199694 v -0.240419 0.028025 -0.064598 v -0.214742 0.028524 -0.135022 v -0.170136 0.028524 -0.153646 v -0.141940 0.028524 -0.131052 v -0.153248 0.027295 -0.055831 v -0.255085 0.016737 0.039452 v -0.238718 0.016737 0.097813 v -0.145661 0.016737 0.050411 v -0.164969 0.016737 0.113922 v -0.202729 0.016737 0.120323 v -0.242952 0.028025 -0.066538 v -0.215666 0.028524 -0.141348 v -0.168821 0.028524 -0.160906 v -0.139209 0.028524 -0.137178 v -0.150897 0.027295 -0.057712 v -0.258034 0.016737 0.042949 v -0.240914 0.016737 0.103692 v -0.143524 0.016737 0.054426 v -0.164210 0.016737 0.120066 v -0.203423 0.016737 0.126789 v -0.242952 0.013555 -0.070778 v -0.242952 0.013555 -0.070778 v -0.242952 0.013555 -0.070778 v -0.215666 0.014054 -0.141348 v -0.168821 0.014054 -0.160906 v -0.139209 0.014054 -0.137178 v -0.150897 0.012826 -0.061952 v -0.150897 0.012826 -0.061952 v -0.150897 0.012826 -0.061952 v -0.258034 -0.001088 0.025696 v -0.240914 -0.000249 0.103692 v -0.143524 -0.001088 0.037173 v -0.164210 -0.000249 0.120066 v -0.203423 -0.000249 0.126789 v -0.242952 -0.000311 -0.070778 v -0.242952 -0.000311 -0.070778 v -0.215666 0.000188 -0.141348 v -0.168821 0.000188 -0.160906 v -0.139209 0.000188 -0.137178 v -0.150897 -0.001041 -0.061952 v -0.150897 -0.001041 -0.061952 v -0.250941 0.022381 -0.013800 v -0.154169 0.022381 -0.007738 v -0.253682 0.022381 -0.013022 v -0.151925 0.022381 -0.006671 v -0.253682 0.005814 -0.021702 v -0.151925 0.005814 -0.015351 v -0.141647 0.491328 -0.114189 v -0.205701 0.490563 -0.094984 v -0.204864 0.482881 -0.001814 v -0.219844 0.485703 -0.041760 v -0.095093 0.485847 -0.085518 v -0.097055 0.480678 -0.033262 v -0.129203 0.478420 0.018134 v -0.172100 0.480332 0.031533 v -0.121152 1.119257 0.137785 v -0.172670 1.119257 0.018168 v -0.149779 1.119257 -0.074245 v -0.077684 1.119257 -0.119280 v 0.000000 1.119257 -0.127489 v -0.023899 1.119257 0.159816 v -0.120262 1.183441 0.143953 v -0.173867 1.183441 0.019491 v -0.162576 1.183441 -0.083776 v -0.083964 1.183441 -0.137626 v -0.023899 1.183441 0.163047 v 0.000000 1.183441 -0.137360 v -0.096686 1.597695 -0.035012 v -0.091318 1.612576 -0.091144 v -0.079937 1.584289 0.006058 v -0.052429 1.559129 0.039906 v -0.059511 1.620940 -0.125205 v -0.621947 1.243404 -0.024871 v -0.606588 1.231470 0.001990 v -0.584973 1.207811 0.013254 v -0.574175 1.185285 -0.008347 v -0.577637 1.175574 -0.050069 v -0.592578 1.182565 -0.088309 v -0.614130 1.213647 -0.085271 v -0.624485 1.236964 -0.058985 v 0.121152 1.119257 0.137785 v 0.172670 1.119257 0.018168 v 0.149779 1.119257 -0.074245 v 0.077684 1.119257 -0.119280 v 0.023899 1.119257 0.159816 v 0.120262 1.183441 0.143953 v 0.173867 1.183441 0.019491 v 0.162576 1.183441 -0.083776 v 0.083964 1.183441 -0.137626 v 0.023899 1.183441 0.163047 v 0.096686 1.597695 -0.035012 v 0.091318 1.612576 -0.091144 v 0.079937 1.584289 0.006058 v 0.052429 1.559129 0.039906 v 0.059511 1.620940 -0.125205 v 0.000000 1.628451 -0.141560 v 0.621947 1.243404 -0.024871 v 0.606588 1.231470 0.001990 v 0.584973 1.207811 0.013254 v 0.574175 1.185285 -0.008347 v 0.577637 1.175574 -0.050069 v 0.592578 1.182565 -0.088309 v 0.614130 1.213647 -0.085271 v 0.624485 1.236964 -0.058985 v 0.081559 1.119257 -0.128551 v -0.000000 1.119257 -0.134141 v 0.088152 1.183441 -0.144985 v -0.000000 1.183441 -0.144198 v 0.126261 1.183441 0.150700 v 0.025091 1.183441 0.170750 v 0.170685 1.183441 -0.088437 v 0.182540 1.183441 0.020003 v 0.127195 1.119257 0.144223 v 0.025091 1.119257 0.167358 v 0.157250 1.119257 -0.078428 v 0.181283 1.119257 0.018614 v -0.088152 1.183441 -0.144985 v -0.126261 1.183441 0.150700 v -0.025091 1.183441 0.170750 v -0.170685 1.183441 -0.088437 v -0.182540 1.183441 0.020003 v -0.127195 1.119257 0.144223 v -0.025091 1.119257 0.167358 v -0.081559 1.119257 -0.128551 v -0.157250 1.119257 -0.078428 v -0.181283 1.119257 0.018614 vt 0.449580 0.858975 vt 0.446811 0.893259 vt 0.433140 0.885831 vt 0.433670 0.864937 vt 0.423531 0.880869 vt 0.423529 0.868245 vt 0.415835 0.877335 vt 0.415702 0.871436 vt 0.414515 0.889573 vt 0.411628 0.881619 vt 0.417651 0.899717 vt 0.421685 0.914018 vt 0.390458 0.911080 vt 0.397272 0.898573 vt 0.402067 0.889322 vt 0.405591 0.881662 vt 0.393393 0.880542 vt 0.401227 0.877410 vt 0.383359 0.883769 vt 0.370935 0.888385 vt 0.370935 0.858506 vt 0.384257 0.863504 vt 0.393621 0.868148 vt 0.401411 0.871385 vt 0.402268 0.859233 vt 0.405556 0.867002 vt 0.398851 0.849239 vt 0.392501 0.835844 vt 0.424993 0.835030 vt 0.419584 0.849580 vt 0.414715 0.859212 vt 0.411644 0.867121 vt 0.417597 0.956332 vt 0.417576 0.950144 vt 0.425355 0.946640 vt 0.425700 0.959173 vt 0.435322 0.942917 vt 0.435382 0.963543 vt 0.449377 0.937962 vt 0.448665 0.969820 vt 0.421195 0.978216 vt 0.426245 0.991732 vt 0.416986 0.968612 vt 0.413252 0.960694 vt 0.407161 0.960617 vt 0.404125 0.968616 vt 0.400574 0.978915 vt 0.395539 0.991932 vt 0.385867 0.964205 vt 0.370935 0.970769 vt 0.395308 0.959573 vt 0.403059 0.956313 vt 0.402858 0.950352 vt 0.395254 0.947339 vt 0.385027 0.943389 vt 0.370935 0.936984 vt 0.399532 0.928313 vt 0.394459 0.914018 vt 0.403739 0.938160 vt 0.407142 0.946172 vt 0.413099 0.945981 vt 0.416326 0.938377 vt 0.420413 0.928266 vt 0.426931 0.914702 vt 0.408574 0.874372 vt 0.410219 0.953325 vt 0.226562 0.171042 vt 0.229618 0.153547 vt 0.213794 0.153209 vt 0.216627 0.161433 vt 0.226410 0.138889 vt 0.215327 0.145725 vt 0.269719 0.159499 vt 0.266410 0.180819 vt 0.294307 0.186604 vt 0.294055 0.158409 vt 0.270713 0.134889 vt 0.298036 0.132710 vt 0.258461 0.210978 vt 0.301739 0.207512 vt 0.274770 0.242260 vt 0.305605 0.227598 vt 0.281959 0.262495 vt 0.310855 0.250656 vt 0.315800 0.072344 vt 0.289002 0.056629 vt 0.282673 0.071246 vt 0.311929 0.088777 vt 0.264747 0.101276 vt 0.307225 0.106799 vt 0.211147 0.163031 vt 0.222831 0.178025 vt 0.224252 0.130297 vt 0.210242 0.143682 vt 0.208454 0.152443 vt 0.251082 0.216519 vt 0.263951 0.244894 vt 0.269636 0.264314 vt 0.277026 0.053942 vt 0.272423 0.068261 vt 0.256811 0.095031 vt 0.205182 0.168917 vt 0.220732 0.186309 vt 0.223728 0.122074 vt 0.205147 0.138166 vt 0.199559 0.152688 vt 0.185011 0.084085 vt 0.144484 0.073736 vt 0.144751 0.075541 vt 0.185416 0.086719 vt 0.205403 0.102002 vt 0.204551 0.106150 vt 0.183202 0.143308 vt 0.201722 0.135143 vt 0.201745 0.130825 vt 0.184485 0.141155 vt 0.141493 0.143342 vt 0.142525 0.142035 vt 0.080099 0.067409 vt 0.041460 0.079404 vt 0.041565 0.082754 vt 0.078547 0.069709 vt 0.036207 0.141284 vt 0.078817 0.152441 vt 0.076744 0.150829 vt 0.036267 0.138918 vt 0.020677 0.121887 vt 0.023290 0.118459 vt 0.019815 0.100613 vt 0.021521 0.105052 vt 0.185416 0.086719 vt 0.144751 0.075541 vt 0.144483 0.084078 vt 0.181939 0.092531 vt 0.204551 0.106150 vt 0.198910 0.111854 vt 0.184485 0.141155 vt 0.201745 0.130825 vt 0.197755 0.123798 vt 0.182580 0.134913 vt 0.142525 0.142035 vt 0.143589 0.133666 vt 0.078547 0.069709 vt 0.041565 0.082754 vt 0.047086 0.090558 vt 0.089323 0.078817 vt 0.036267 0.138918 vt 0.076744 0.150829 vt 0.086438 0.141007 vt 0.041116 0.132335 vt 0.023290 0.118459 vt 0.032271 0.112484 vt 0.021521 0.105052 vt 0.181939 0.092531 vt 0.144483 0.084078 vt 0.147047 0.088006 vt 0.179083 0.097193 vt 0.198910 0.111854 vt 0.193071 0.117160 vt 0.182580 0.134913 vt 0.197755 0.123798 vt 0.181037 0.130160 vt 0.143589 0.133666 vt 0.147328 0.129616 vt 0.147047 0.088006 vt 0.147328 0.129616 vt 0.181037 0.130160 vt 0.179083 0.097193 vt 0.193071 0.117160 vt 0.144483 0.084078 vt 0.143589 0.133666 vt 0.147328 0.129616 vt 0.147047 0.088006 vt 0.089323 0.078817 vt 0.047086 0.090558 vt 0.041116 0.132335 vt 0.086438 0.141007 vt 0.032271 0.112484 vt 0.245305 0.176883 vt 0.247368 0.156147 vt 0.245837 0.135881 vt 0.241382 0.192418 vt 0.247015 0.117246 vt 0.237166 0.200209 vt 0.243328 0.108719 vt 0.114084 0.067095 vt 0.113248 0.068587 vt 0.114585 0.148101 vt 0.113698 0.146940 vt 0.113248 0.068587 vt 0.116087 0.079310 vt 0.113698 0.146940 vt 0.116115 0.135892 vt 0.116115 0.135892 vt 0.116087 0.079310 vt 0.143589 0.133666 vt 0.144483 0.084078 vt 0.343837 0.181822 vt 0.345261 0.160962 vt 0.347096 0.138981 vt 0.343165 0.201512 vt 0.343765 0.223779 vt 0.344299 0.249475 vt 0.350237 0.094115 vt 0.349758 0.076974 vt 0.348735 0.114152 vt 0.387991 0.183482 vt 0.389992 0.161674 vt 0.391106 0.141087 vt 0.385108 0.203688 vt 0.383523 0.228087 vt 0.381637 0.254339 vt 0.389239 0.091371 vt 0.385619 0.069439 vt 0.389769 0.113982 vt 0.427126 0.183782 vt 0.429698 0.163320 vt 0.429738 0.142056 vt 0.424347 0.204292 vt 0.422566 0.229611 vt 0.424840 0.259782 vt 0.427811 0.091080 vt 0.427593 0.066817 vt 0.429128 0.114476 vt 0.081919 0.177576 vt 0.080241 0.180500 vt 0.099685 0.188250 vt 0.099913 0.184162 vt 0.067501 0.169108 vt 0.064900 0.171780 vt 0.059214 0.153117 vt 0.054794 0.154109 vt 0.171154 0.153117 vt 0.159295 0.172265 vt 0.162213 0.175238 vt 0.175881 0.153828 vt 0.143601 0.181366 vt 0.144924 0.184881 vt 0.129579 0.184219 vt 0.129740 0.188639 vt 0.113974 0.191097 vt 0.114198 0.186685 vt 0.077311 0.185604 vt 0.098367 0.193721 vt 0.060508 0.176299 vt 0.048772 0.156819 vt 0.165918 0.178906 vt 0.182692 0.155388 vt 0.146747 0.189406 vt 0.130229 0.193924 vt 0.113448 0.196200 vt 0.064600 0.205653 vt 0.090460 0.213946 vt 0.049088 0.231773 vt 0.082019 0.244697 vt 0.044529 0.192489 vt 0.023834 0.214521 vt 0.157185 0.209804 vt 0.180567 0.195484 vt 0.170991 0.240628 vt 0.199169 0.220714 vt 0.136177 0.215367 vt 0.144044 0.248982 vt 0.112772 0.216603 vt 0.112056 0.250004 vt 0.199159 0.173643 vt 0.218280 0.197160 vt 0.030243 0.171420 vt 0.008089 0.190338 vt 0.464533 0.182687 vt 0.467609 0.163910 vt 0.467580 0.145645 vt 0.461994 0.199903 vt 0.460739 0.222485 vt 0.465593 0.250101 vt 0.465044 0.099687 vt 0.467609 0.076893 vt 0.466213 0.121048 vt 0.161560 0.402243 vt 0.155588 0.395509 vt 0.134626 0.410605 vt 0.146864 0.425262 vt 0.127497 0.391275 vt 0.152830 0.385975 vt 0.127264 0.371100 vt 0.153383 0.375493 vt 0.134716 0.352237 vt 0.156932 0.365158 vt 0.145395 0.334369 vt 0.162411 0.356153 vt 0.160447 0.323865 vt 0.169799 0.350020 vt 0.177475 0.320161 vt 0.178468 0.347729 vt 0.194608 0.323042 vt 0.186891 0.350662 vt 0.210291 0.333563 vt 0.193747 0.356991 vt 0.223414 0.349480 vt 0.199287 0.365151 vt 0.230882 0.369006 vt 0.203745 0.374332 vt 0.231524 0.389807 vt 0.205469 0.384522 vt 0.224638 0.409261 vt 0.203005 0.394667 vt 0.212389 0.424654 vt 0.197149 0.402034 vt 0.197372 0.433287 vt 0.188865 0.405252 vt 0.179699 0.435910 vt 0.179384 0.405997 vt 0.162040 0.433331 vt 0.169888 0.405254 vt 0.165387 0.397638 vt 0.160572 0.392283 vt 0.158418 0.384595 vt 0.158964 0.376281 vt 0.161637 0.368049 vt 0.165965 0.360925 vt 0.171743 0.356015 vt 0.178593 0.354261 vt 0.185319 0.356439 vt 0.190856 0.361326 vt 0.195230 0.367767 vt 0.198608 0.375152 vt 0.199761 0.383347 vt 0.197879 0.391455 vt 0.193207 0.397352 vt 0.186697 0.400151 vt 0.179298 0.400776 vt 0.171898 0.400197 vt 0.178814 0.379350 vt 0.179769 0.441150 vt 0.221479 0.443653 vt 0.256818 0.438950 vt 0.289823 0.435602 vt 0.137780 0.444611 vt 0.102166 0.444863 vt 0.077709 0.444123 vt 0.083017 0.411236 vt 0.097247 0.382934 vt 0.118968 0.348422 vt 0.138683 0.313766 vt 0.160629 0.287922 vt 0.180188 0.263330 vt 0.198518 0.285300 vt 0.216421 0.311488 vt 0.239706 0.345595 vt 0.261990 0.379891 vt 0.277767 0.406096 vt 0.258659 0.459445 vt 0.284835 0.453304 vt 0.264043 0.483454 vt 0.221478 0.504250 vt 0.223490 0.459014 vt 0.181117 0.514996 vt 0.180077 0.458023 vt 0.140539 0.505936 vt 0.136318 0.460481 vt 0.097191 0.485804 vt 0.100655 0.461818 vt 0.077873 0.458646 vt 0.144974 0.275250 vt 0.161388 0.256884 vt 0.128621 0.256115 vt 0.126787 0.303834 vt 0.086241 0.279508 vt 0.104546 0.338805 vt 0.055299 0.310343 vt 0.082787 0.376723 vt 0.042576 0.351978 vt 0.068614 0.405298 vt 0.044158 0.400261 vt 0.061831 0.429301 vt 0.208879 0.272663 vt 0.192460 0.255496 vt 0.225038 0.253308 vt 0.268938 0.274082 vt 0.229092 0.300701 vt 0.302213 0.302785 vt 0.254202 0.335934 vt 0.315656 0.344933 vt 0.276573 0.372608 vt 0.317865 0.392224 vt 0.293549 0.400188 vt 0.301058 0.427072 vt 0.198759 0.014828 vt 0.222350 0.014086 vt 0.225351 0.035722 vt 0.202970 0.036062 vt 0.181802 0.015068 vt 0.184866 0.037201 vt 0.165781 0.039107 vt 0.163527 0.016516 vt 0.147732 0.040863 vt 0.146627 0.019020 vt 0.127089 0.021494 vt 0.129597 0.044996 vt 0.109381 0.048515 vt 0.107302 0.025406 vt 0.089500 0.053020 vt 0.084308 0.031079 vt 0.066071 0.058633 vt 0.061110 0.037042 vt 0.042124 0.064867 vt 0.037762 0.043424 vt 0.010834 0.050893 vt 0.017458 0.072394 vt 0.511165 0.043447 vt 0.536993 0.053487 vt 0.527229 0.073598 vt 0.505665 0.063756 vt 0.489710 0.035104 vt 0.484654 0.056823 vt 0.463246 0.050665 vt 0.466794 0.027952 vt 0.443529 0.046206 vt 0.446550 0.021943 vt 0.425383 0.041270 vt 0.428544 0.018957 vt 0.412223 0.017650 vt 0.407054 0.038941 vt 0.387963 0.037877 vt 0.393758 0.016432 vt 0.370467 0.015023 vt 0.366871 0.036829 vt 0.345593 0.014298 vt 0.344160 0.035900 vt 0.321358 0.013160 vt 0.320863 0.036705 vt 0.296412 0.036649 vt 0.296765 0.012973 vt 0.272244 0.012915 vt 0.271910 0.035815 vt 0.247906 0.013068 vt 0.247801 0.035841 vt 0.202970 0.036062 vt 0.225351 0.035722 vt 0.226515 0.038974 vt 0.203971 0.039652 vt 0.184866 0.037201 vt 0.185261 0.041150 vt 0.165781 0.039107 vt 0.165701 0.043024 vt 0.147732 0.040863 vt 0.147361 0.044516 vt 0.129597 0.044996 vt 0.129124 0.048490 vt 0.109381 0.048515 vt 0.109030 0.051791 vt 0.089500 0.053020 vt 0.089310 0.056207 vt 0.066071 0.058633 vt 0.066429 0.061755 vt 0.042124 0.064867 vt 0.043392 0.067828 vt 0.017458 0.072394 vt 0.019813 0.075036 vt 0.505665 0.063756 vt 0.527229 0.073598 vt 0.526478 0.076523 vt 0.505443 0.066939 vt 0.484654 0.056823 vt 0.484326 0.060219 vt 0.463246 0.050665 vt 0.462739 0.054336 vt 0.443529 0.046206 vt 0.442909 0.050073 vt 0.425383 0.041270 vt 0.424602 0.045251 vt 0.407054 0.038941 vt 0.406065 0.042638 vt 0.387963 0.037877 vt 0.386843 0.041329 vt 0.366871 0.036829 vt 0.365783 0.040029 vt 0.344160 0.035900 vt 0.343103 0.038850 vt 0.320863 0.036705 vt 0.320106 0.039720 vt 0.296412 0.036649 vt 0.296500 0.039755 vt 0.271910 0.035815 vt 0.272773 0.038876 vt 0.247801 0.035841 vt 0.248977 0.038910 vt 0.222350 0.014086 vt 0.198759 0.014828 vt 0.199687 0.011221 vt 0.223818 0.010772 vt 0.181802 0.015068 vt 0.181553 0.010779 vt 0.163527 0.016516 vt 0.162421 0.012640 vt 0.146627 0.019020 vt 0.144887 0.015688 vt 0.127089 0.021494 vt 0.125141 0.018434 vt 0.107302 0.025406 vt 0.105487 0.022568 vt 0.084308 0.031079 vt 0.082771 0.028292 vt 0.061110 0.037042 vt 0.060037 0.034148 vt 0.037762 0.043424 vt 0.037449 0.040101 vt 0.010834 0.050893 vt 0.011806 0.047081 vt 0.536993 0.053487 vt 0.511165 0.043447 vt 0.513311 0.040731 vt 0.538834 0.050433 vt 0.489710 0.035104 vt 0.491532 0.031973 vt 0.466794 0.027952 vt 0.468347 0.024422 vt 0.446550 0.021943 vt 0.447882 0.017991 vt 0.428544 0.018957 vt 0.429133 0.014719 vt 0.412223 0.017650 vt 0.411628 0.013830 vt 0.393758 0.016432 vt 0.392516 0.012983 vt 0.370467 0.015023 vt 0.369188 0.011747 vt 0.345593 0.014298 vt 0.344564 0.011199 vt 0.321358 0.013160 vt 0.320786 0.010038 vt 0.296765 0.012973 vt 0.297047 0.009849 vt 0.272244 0.012915 vt 0.273167 0.009848 vt 0.247906 0.013068 vt 0.249194 0.009894 vt 0.226562 0.171042 vt 0.216627 0.161433 vt 0.213794 0.153209 vt 0.229618 0.153547 vt 0.215327 0.145725 vt 0.226410 0.138889 vt 0.294307 0.186604 vt 0.266410 0.180819 vt 0.269719 0.159499 vt 0.294055 0.158409 vt 0.270713 0.134889 vt 0.298036 0.132710 vt 0.301739 0.207512 vt 0.258461 0.210978 vt 0.305605 0.227598 vt 0.274770 0.242260 vt 0.310855 0.250656 vt 0.281959 0.262495 vt 0.315800 0.072344 vt 0.311929 0.088777 vt 0.282673 0.071246 vt 0.289002 0.056629 vt 0.307225 0.106799 vt 0.264747 0.101276 vt 0.222831 0.178025 vt 0.211147 0.163031 vt 0.210242 0.143682 vt 0.224252 0.130297 vt 0.208454 0.152443 vt 0.263951 0.244894 vt 0.251082 0.216519 vt 0.269636 0.264314 vt 0.272423 0.068261 vt 0.277026 0.053942 vt 0.256811 0.095031 vt 0.220732 0.186309 vt 0.205182 0.168917 vt 0.205147 0.138166 vt 0.223728 0.122074 vt 0.199559 0.152688 vt 0.185011 0.084085 vt 0.185416 0.086719 vt 0.144751 0.075541 vt 0.144484 0.073736 vt 0.205403 0.102002 vt 0.204551 0.106150 vt 0.183202 0.143308 vt 0.184485 0.141155 vt 0.201745 0.130825 vt 0.201722 0.135143 vt 0.141493 0.143342 vt 0.142525 0.142035 vt 0.080099 0.067409 vt 0.078547 0.069709 vt 0.041565 0.082754 vt 0.041460 0.079404 vt 0.036207 0.141284 vt 0.036267 0.138918 vt 0.076744 0.150829 vt 0.078817 0.152441 vt 0.020677 0.121887 vt 0.023290 0.118459 vt 0.021521 0.105052 vt 0.019815 0.100613 vt 0.185416 0.086719 vt 0.181939 0.092531 vt 0.144483 0.084078 vt 0.144751 0.075541 vt 0.204551 0.106150 vt 0.198910 0.111854 vt 0.184485 0.141155 vt 0.182580 0.134913 vt 0.197755 0.123798 vt 0.201745 0.130825 vt 0.142525 0.142035 vt 0.143589 0.133666 vt 0.078547 0.069709 vt 0.089323 0.078817 vt 0.047086 0.090558 vt 0.041565 0.082754 vt 0.036267 0.138918 vt 0.041116 0.132335 vt 0.086438 0.141007 vt 0.076744 0.150829 vt 0.023290 0.118459 vt 0.032271 0.112484 vt 0.021521 0.105052 vt 0.181939 0.092531 vt 0.179083 0.097193 vt 0.147047 0.088006 vt 0.144483 0.084078 vt 0.198910 0.111854 vt 0.193071 0.117160 vt 0.182580 0.134913 vt 0.181037 0.130160 vt 0.197755 0.123798 vt 0.143589 0.133666 vt 0.147328 0.129616 vt 0.147047 0.088006 vt 0.179083 0.097193 vt 0.181037 0.130160 vt 0.147328 0.129616 vt 0.193071 0.117160 vt 0.144483 0.084078 vt 0.147047 0.088006 vt 0.147328 0.129616 vt 0.143589 0.133666 vt 0.089323 0.078817 vt 0.086438 0.141007 vt 0.041116 0.132335 vt 0.047086 0.090558 vt 0.032271 0.112484 vt 0.245305 0.176883 vt 0.247368 0.156147 vt 0.245837 0.135881 vt 0.241382 0.192418 vt 0.247015 0.117246 vt 0.237166 0.200209 vt 0.243328 0.108719 vt 0.113248 0.068587 vt 0.114084 0.067095 vt 0.113698 0.146940 vt 0.114585 0.148101 vt 0.116087 0.079310 vt 0.113248 0.068587 vt 0.116115 0.135892 vt 0.113698 0.146940 vt 0.116087 0.079310 vt 0.116115 0.135892 vt 0.144483 0.084078 vt 0.143589 0.133666 vt 0.345261 0.160962 vt 0.343837 0.181822 vt 0.347096 0.138981 vt 0.343165 0.201512 vt 0.343765 0.223779 vt 0.344299 0.249475 vt 0.349758 0.076974 vt 0.350237 0.094115 vt 0.348735 0.114152 vt 0.389992 0.161674 vt 0.387991 0.183482 vt 0.391106 0.141087 vt 0.385108 0.203688 vt 0.383523 0.228087 vt 0.381637 0.254339 vt 0.385619 0.069439 vt 0.389239 0.091371 vt 0.389769 0.113982 vt 0.429698 0.163320 vt 0.427126 0.183782 vt 0.429738 0.142056 vt 0.424347 0.204292 vt 0.422566 0.229611 vt 0.424840 0.259782 vt 0.427593 0.066817 vt 0.427811 0.091080 vt 0.429128 0.114476 vt 0.081919 0.177576 vt 0.099913 0.184162 vt 0.099685 0.188250 vt 0.080241 0.180500 vt 0.064900 0.171780 vt 0.067501 0.169108 vt 0.054794 0.154109 vt 0.059214 0.153117 vt 0.171154 0.153117 vt 0.175881 0.153828 vt 0.162213 0.175238 vt 0.159295 0.172265 vt 0.144924 0.184881 vt 0.143601 0.181366 vt 0.129740 0.188639 vt 0.129579 0.184219 vt 0.114198 0.186685 vt 0.113974 0.191097 vt 0.098367 0.193721 vt 0.077311 0.185604 vt 0.060508 0.176299 vt 0.048772 0.156819 vt 0.182692 0.155388 vt 0.165918 0.178906 vt 0.146747 0.189406 vt 0.130229 0.193924 vt 0.113448 0.196200 vt 0.090460 0.213946 vt 0.064600 0.205653 vt 0.082019 0.244697 vt 0.049088 0.231773 vt 0.044529 0.192489 vt 0.023834 0.214521 vt 0.180567 0.195484 vt 0.157185 0.209804 vt 0.199169 0.220714 vt 0.170991 0.240628 vt 0.136177 0.215367 vt 0.144044 0.248982 vt 0.112772 0.216603 vt 0.112056 0.250004 vt 0.199159 0.173643 vt 0.218280 0.197160 vt 0.030243 0.171420 vt 0.008089 0.190338 vt 0.467609 0.163910 vt 0.464533 0.182687 vt 0.467580 0.145645 vt 0.461994 0.199903 vt 0.460739 0.222485 vt 0.465593 0.250101 vt 0.467609 0.076893 vt 0.465044 0.099687 vt 0.466213 0.121048 vt 0.824979 0.045863 vt 0.793239 0.045819 vt 0.793312 0.041673 vt 0.825514 0.041655 vt 0.793893 0.008820 vt 0.827890 0.012944 vt 0.828581 0.017380 vt 0.793815 0.013267 vt 0.939153 0.031665 vt 0.979840 0.032855 vt 0.979755 0.038216 vt 0.938105 0.037188 vt 0.862021 0.016414 vt 0.861433 0.021442 vt 0.898925 0.022267 vt 0.897466 0.027142 vt 0.971485 0.076154 vt 0.930825 0.061364 vt 0.931849 0.055717 vt 0.973626 0.070853 vt 0.857410 0.048076 vt 0.858591 0.043648 vt 0.892122 0.054446 vt 0.892962 0.049427 vt 0.759776 0.011742 vt 0.758929 0.016149 vt 0.608761 0.027123 vt 0.648032 0.026496 vt 0.648918 0.031986 vt 0.609242 0.031905 vt 0.725555 0.014001 vt 0.725965 0.019044 vt 0.688488 0.018531 vt 0.689772 0.023449 vt 0.655359 0.056552 vt 0.615849 0.068576 vt 0.614741 0.063712 vt 0.654567 0.050945 vt 0.761519 0.044736 vt 0.761134 0.040513 vt 0.729040 0.045799 vt 0.728016 0.041333 vt 0.694151 0.050945 vt 0.693483 0.045909 vt 0.654567 0.050945 vt 0.648918 0.031986 vt 0.689772 0.023449 vt 0.693483 0.045909 vt 0.725965 0.019044 vt 0.728016 0.041333 vt 0.758929 0.016149 vt 0.761134 0.040513 vt 0.609242 0.031905 vt 0.614741 0.063712 vt 0.793312 0.041673 vt 0.793815 0.013267 vt 0.931849 0.055717 vt 0.892962 0.049427 vt 0.897466 0.027142 vt 0.938105 0.037188 vt 0.858591 0.043648 vt 0.861433 0.021442 vt 0.825514 0.041655 vt 0.828581 0.017380 vt 0.979755 0.038216 vt 0.973626 0.070853 vt 0.579840 0.033462 vt 0.580275 0.028828 vt 0.586823 0.076152 vt 0.584904 0.071762 vt 0.584904 0.071762 vt 0.579840 0.033462 vt 0.104500 0.536214 vt 0.105732 0.526505 vt 0.114426 0.528654 vt 0.112823 0.537425 vt 0.123022 0.531811 vt 0.122042 0.539539 vt 0.117940 0.538790 vt 0.119592 0.530467 vt 0.078999 0.531783 vt 0.079412 0.521731 vt 0.085663 0.522844 vt 0.084893 0.532679 vt 0.073567 0.511819 vt 0.073483 0.505967 vt 0.079489 0.506681 vt 0.079286 0.513366 vt 0.097178 0.510132 vt 0.095841 0.516649 vt 0.086454 0.514927 vt 0.087336 0.508697 vt 0.094721 0.524391 vt 0.116274 0.521220 vt 0.117835 0.515207 vt 0.123610 0.517489 vt 0.121664 0.523054 vt 0.127950 0.519596 vt 0.125248 0.524531 vt 0.066875 0.504854 vt 0.067932 0.510440 vt 0.062507 0.509670 vt 0.060099 0.504182 vt 0.068905 0.519309 vt 0.064061 0.518154 vt 0.073994 0.520429 vt 0.068891 0.529347 vt 0.063634 0.527723 vt 0.107153 0.519113 vt 0.108756 0.513016 vt 0.057551 0.509684 vt 0.054170 0.504868 vt 0.059161 0.517300 vt 0.073903 0.530789 vt 0.094035 0.534646 vt 0.059014 0.525280 vt 0.259060 0.536214 vt 0.250751 0.537425 vt 0.249134 0.528654 vt 0.257842 0.526505 vt 0.240545 0.531811 vt 0.243975 0.530467 vt 0.245620 0.538790 vt 0.241518 0.539539 vt 0.284575 0.531783 vt 0.278681 0.532679 vt 0.277911 0.522844 vt 0.284162 0.521731 vt 0.289993 0.511819 vt 0.284288 0.513366 vt 0.284071 0.506681 vt 0.290077 0.505967 vt 0.266389 0.510132 vt 0.276224 0.508697 vt 0.277113 0.514927 vt 0.267719 0.516649 vt 0.268846 0.524391 vt 0.247300 0.521220 vt 0.241903 0.523054 vt 0.239964 0.517489 vt 0.245732 0.515207 vt 0.238326 0.524531 vt 0.235624 0.519596 vt 0.296692 0.504854 vt 0.303461 0.504182 vt 0.301060 0.509670 vt 0.295628 0.510440 vt 0.299499 0.518154 vt 0.294662 0.519309 vt 0.289573 0.520429 vt 0.299926 0.527723 vt 0.294662 0.529347 vt 0.256400 0.519113 vt 0.254811 0.513016 vt 0.309390 0.504868 vt 0.306016 0.509684 vt 0.304399 0.517300 vt 0.289671 0.530789 vt 0.269539 0.534646 vt 0.304560 0.525280 vt 0.390760 0.520514 vt 0.336134 0.535945 vt 0.335878 0.477450 vt 0.390759 0.473676 vt 0.445641 0.477449 vt 0.445386 0.535945 vt 0.343131 0.407459 vt 0.390759 0.403233 vt 0.438387 0.407458 vt 0.351461 0.343819 vt 0.390758 0.342970 vt 0.430055 0.343818 vt 0.354191 0.290140 vt 0.390758 0.289390 vt 0.427326 0.290140 vt 0.061135 0.632763 vt 0.063655 0.610363 vt 0.074736 0.604595 vt 0.074295 0.625763 vt 0.057369 0.540426 vt 0.054492 0.558794 vt 0.042242 0.552452 vt 0.048822 0.539355 vt 0.054597 0.585583 vt 0.042970 0.582979 vt 0.047058 0.631419 vt 0.050852 0.610538 vt 0.131163 0.634639 vt 0.131289 0.642297 vt 0.127040 0.641562 vt 0.130316 0.626757 vt 0.050124 0.642451 vt 0.057110 0.643109 vt 0.048927 0.650662 vt 0.056221 0.651355 vt 0.132479 0.652342 vt 0.127229 0.651796 vt 0.133949 0.669849 vt 0.128335 0.670255 vt 0.036621 0.626190 vt 0.039169 0.607948 vt 0.053351 0.669968 vt 0.046939 0.669324 vt 0.116911 0.654701 vt 0.121209 0.653091 vt 0.122651 0.671501 vt 0.117541 0.671431 vt 0.116400 0.645923 vt 0.119935 0.643452 vt 0.115805 0.640568 vt 0.115833 0.633351 vt 0.041486 0.640001 vt 0.039323 0.647533 vt 0.033065 0.643837 vt 0.034997 0.635591 vt 0.035396 0.661631 vt 0.029509 0.658880 vt 0.133074 0.651670 vt 0.131856 0.642248 vt 0.135118 0.639469 vt 0.137316 0.649507 vt 0.140382 0.665733 vt 0.135139 0.670927 vt 0.143609 0.603412 vt 0.139311 0.594319 vt 0.146451 0.595663 vt 0.144869 0.605106 vt 0.137239 0.609873 vt 0.142727 0.607563 vt 0.127712 0.610888 vt 0.127341 0.591435 vt 0.149804 0.580648 vt 0.140410 0.565570 vt 0.156902 0.571541 vt 0.151134 0.553271 vt 0.157896 0.596188 vt 0.154214 0.606100 vt 0.162481 0.589545 vt 0.127362 0.566830 vt 0.127726 0.552592 vt 0.137974 0.552347 vt 0.032470 0.570638 vt 0.035130 0.586003 vt 0.026485 0.596818 vt 0.023293 0.584953 vt 0.151071 0.614773 vt 0.157665 0.612183 vt 0.154424 0.622046 vt 0.160913 0.617671 vt 0.158785 0.626792 vt 0.165001 0.620695 vt 0.165932 0.626386 vt 0.139010 0.625182 vt 0.140998 0.635612 vt 0.143273 0.679698 vt 0.138800 0.685942 vt 0.145870 0.662072 vt 0.148292 0.676597 vt 0.141516 0.692179 vt 0.144757 0.686026 vt 0.032274 0.675015 vt 0.030748 0.681700 vt 0.025036 0.679628 vt 0.026443 0.672845 vt 0.104276 0.587774 vt 0.104024 0.606135 vt 0.095127 0.600451 vt 0.095092 0.583609 vt 0.092334 0.630229 vt 0.097136 0.634639 vt 0.095806 0.645678 vt 0.090850 0.642066 vt 0.099901 0.636536 vt 0.099208 0.647064 vt 0.094539 0.614724 vt 0.085950 0.614808 vt 0.086069 0.599660 vt 0.093440 0.623019 vt 0.085733 0.622543 vt 0.085404 0.629431 vt 0.085103 0.640974 vt 0.101056 0.645741 vt 0.105347 0.647771 vt 0.105067 0.665502 vt 0.099845 0.665614 vt 0.105354 0.637397 vt 0.101259 0.635451 vt 0.105179 0.681651 vt 0.100272 0.684353 vt 0.110835 0.649801 vt 0.110604 0.666377 vt 0.115602 0.654288 vt 0.115693 0.667847 vt 0.064194 0.650067 vt 0.071187 0.647365 vt 0.070571 0.666342 vt 0.063858 0.668309 vt 0.115546 0.645545 vt 0.111787 0.641156 vt 0.110107 0.682428 vt 0.109568 0.689204 vt 0.105186 0.688441 vt 0.114055 0.686180 vt 0.112312 0.693663 vt 0.069682 0.683618 vt 0.063508 0.685242 vt 0.101448 0.628682 vt 0.102645 0.619771 vt 0.063284 0.691626 vt 0.069717 0.690331 vt 0.063340 0.696659 vt 0.069220 0.696008 vt 0.110429 0.698472 vt 0.108532 0.694573 vt 0.098662 0.628374 vt 0.100391 0.632343 vt 0.077802 0.634716 vt 0.079020 0.645678 vt 0.164371 0.608921 vt 0.161452 0.602992 vt 0.165939 0.596447 vt 0.168557 0.604168 vt 0.167094 0.615970 vt 0.169978 0.615697 vt 0.018890 0.590553 vt 0.022565 0.603237 vt 0.009825 0.603846 vt 0.014683 0.596510 vt 0.018211 0.608592 vt 0.011855 0.613422 vt 0.093426 0.661645 vt 0.091774 0.666300 vt 0.089205 0.664536 vt 0.089506 0.658698 vt 0.092670 0.671522 vt 0.086748 0.665649 vt 0.096345 0.668043 vt 0.080371 0.662765 vt 0.079923 0.656969 vt 0.085145 0.652370 vt 0.085397 0.659265 vt 0.098641 0.660777 vt 0.094483 0.655982 vt 0.081253 0.668995 vt 0.105781 0.693901 vt 0.103562 0.697737 vt 0.129420 0.701776 vt 0.129350 0.694769 vt 0.133788 0.697695 vt 0.131142 0.705381 vt 0.134488 0.689029 vt 0.129147 0.687265 vt 0.124520 0.706935 vt 0.126228 0.701958 vt 0.042746 0.700852 vt 0.043446 0.693642 vt 0.049739 0.694069 vt 0.048633 0.701293 vt 0.121188 0.701209 vt 0.124800 0.695938 vt 0.119102 0.693250 vt 0.123967 0.688602 vt 0.145499 0.696547 vt 0.146577 0.691080 vt 0.028998 0.688861 vt 0.024077 0.687447 vt 0.149328 0.690240 vt 0.151652 0.693257 vt 0.149202 0.683527 vt 0.094567 0.553677 vt 0.094679 0.565171 vt 0.086615 0.563519 vt 0.086636 0.551990 vt 0.066105 0.562595 vt 0.066924 0.545564 vt 0.076465 0.548805 vt 0.076794 0.562609 vt 0.076094 0.583224 vt 0.065965 0.585912 vt 0.115385 0.611665 vt 0.113712 0.551605 vt 0.114468 0.566676 vt 0.103422 0.566830 vt 0.102302 0.553894 vt 0.115196 0.590889 vt 0.086433 0.581922 vt 0.076976 0.629452 vt 0.089891 0.652531 vt 0.050859 0.686894 vt 0.044328 0.686348 vt 0.142888 0.645587 vt 0.064404 0.641961 vt 0.071285 0.639014 vt 0.145870 0.623131 vt 0.147487 0.633050 vt 0.148999 0.642234 vt 0.145009 0.543016 vt 0.151743 0.659720 vt 0.153647 0.676317 vt 0.101553 0.692361 vt 0.153563 0.684745 vt 0.302124 0.632763 vt 0.288957 0.625763 vt 0.288516 0.604595 vt 0.299597 0.610363 vt 0.305883 0.540426 vt 0.314430 0.539355 vt 0.321010 0.552452 vt 0.308760 0.558794 vt 0.320282 0.582979 vt 0.308655 0.585583 vt 0.316194 0.631419 vt 0.312400 0.610538 vt 0.232089 0.634639 vt 0.232936 0.626757 vt 0.236212 0.641562 vt 0.231963 0.642297 vt 0.313128 0.642451 vt 0.306142 0.643109 vt 0.314325 0.650662 vt 0.307031 0.651355 vt 0.236023 0.651796 vt 0.230773 0.652342 vt 0.234917 0.670255 vt 0.229303 0.669849 vt 0.324083 0.607948 vt 0.326631 0.626190 vt 0.316313 0.669324 vt 0.309901 0.669968 vt 0.246341 0.654701 vt 0.245704 0.671431 vt 0.240601 0.671501 vt 0.242043 0.653091 vt 0.243317 0.643452 vt 0.246852 0.645923 vt 0.247419 0.633351 vt 0.247447 0.640568 vt 0.321766 0.640001 vt 0.328248 0.635591 vt 0.330187 0.643837 vt 0.323929 0.647533 vt 0.333743 0.658880 vt 0.327856 0.661631 vt 0.230178 0.651670 vt 0.225936 0.649507 vt 0.228127 0.639469 vt 0.231396 0.642248 vt 0.228113 0.670927 vt 0.222870 0.665733 vt 0.219643 0.603412 vt 0.218383 0.605106 vt 0.216801 0.595663 vt 0.223941 0.594319 vt 0.226013 0.609873 vt 0.220518 0.607563 vt 0.235911 0.591435 vt 0.235540 0.610888 vt 0.213448 0.580648 vt 0.222842 0.565570 vt 0.206350 0.571541 vt 0.212118 0.553271 vt 0.209038 0.606100 vt 0.205356 0.596188 vt 0.200771 0.589545 vt 0.235890 0.566830 vt 0.225271 0.552347 vt 0.235526 0.552592 vt 0.330782 0.570638 vt 0.339959 0.584953 vt 0.336767 0.596818 vt 0.328122 0.586003 vt 0.212181 0.614773 vt 0.208828 0.622046 vt 0.205587 0.612183 vt 0.204467 0.626792 vt 0.202339 0.617671 vt 0.197320 0.626386 vt 0.198251 0.620695 vt 0.222254 0.635612 vt 0.224235 0.625182 vt 0.224452 0.685942 vt 0.219979 0.679698 vt 0.214953 0.676597 vt 0.217382 0.662072 vt 0.221736 0.692179 vt 0.218495 0.686026 vt 0.330978 0.675015 vt 0.336809 0.672845 vt 0.338216 0.679628 vt 0.332504 0.681700 vt 0.258976 0.587774 vt 0.268160 0.583609 vt 0.268125 0.600451 vt 0.259228 0.606135 vt 0.270918 0.630229 vt 0.272402 0.642066 vt 0.267446 0.645678 vt 0.266116 0.634639 vt 0.264044 0.647064 vt 0.263351 0.636536 vt 0.277197 0.599660 vt 0.277302 0.614808 vt 0.268713 0.614724 vt 0.277519 0.622543 vt 0.269812 0.623019 vt 0.277834 0.629431 vt 0.278149 0.640974 vt 0.262196 0.645741 vt 0.263407 0.665614 vt 0.258185 0.665502 vt 0.257905 0.647771 vt 0.257898 0.637397 vt 0.261993 0.635451 vt 0.262980 0.684353 vt 0.258073 0.681651 vt 0.252648 0.666377 vt 0.252417 0.649801 vt 0.247566 0.667847 vt 0.247650 0.654288 vt 0.299058 0.650067 vt 0.299394 0.668309 vt 0.292681 0.666342 vt 0.292065 0.647365 vt 0.251465 0.641156 vt 0.247706 0.645545 vt 0.253145 0.682428 vt 0.258066 0.688441 vt 0.253684 0.689204 vt 0.250940 0.693663 vt 0.249197 0.686180 vt 0.299744 0.685242 vt 0.293570 0.683618 vt 0.260607 0.619771 vt 0.261804 0.628682 vt 0.299968 0.691626 vt 0.293535 0.690331 vt 0.299912 0.696659 vt 0.294032 0.696008 vt 0.254720 0.694573 vt 0.252823 0.698472 vt 0.262861 0.632343 vt 0.264590 0.628374 vt 0.285450 0.634716 vt 0.284232 0.645678 vt 0.198881 0.608921 vt 0.194695 0.604168 vt 0.197313 0.596447 vt 0.201800 0.602992 vt 0.196158 0.615970 vt 0.193274 0.615697 vt 0.344362 0.590553 vt 0.340687 0.603237 vt 0.353427 0.603846 vt 0.351397 0.613422 vt 0.345041 0.608592 vt 0.348569 0.596510 vt 0.269826 0.661645 vt 0.273746 0.658698 vt 0.274047 0.664536 vt 0.271478 0.666300 vt 0.276504 0.665649 vt 0.270596 0.671522 vt 0.266907 0.668043 vt 0.282881 0.662765 vt 0.277855 0.659265 vt 0.278100 0.652370 vt 0.283329 0.656969 vt 0.264611 0.660777 vt 0.268769 0.655982 vt 0.281999 0.668995 vt 0.257464 0.693901 vt 0.259690 0.697737 vt 0.233832 0.701776 vt 0.232110 0.705381 vt 0.229464 0.697695 vt 0.233902 0.694769 vt 0.228764 0.689029 vt 0.234105 0.687265 vt 0.238732 0.706935 vt 0.237031 0.701958 vt 0.320506 0.700852 vt 0.314619 0.701293 vt 0.313513 0.694069 vt 0.319806 0.693642 vt 0.238452 0.695938 vt 0.242064 0.701209 vt 0.239285 0.688602 vt 0.244150 0.693250 vt 0.217753 0.696547 vt 0.216675 0.691080 vt 0.339175 0.687447 vt 0.334254 0.688861 vt 0.211600 0.693257 vt 0.213917 0.690240 vt 0.214050 0.683527 vt 0.268685 0.553677 vt 0.276616 0.551990 vt 0.276637 0.563519 vt 0.268573 0.565171 vt 0.297147 0.562595 vt 0.286458 0.562609 vt 0.286787 0.548805 vt 0.296328 0.545564 vt 0.297294 0.585912 vt 0.287158 0.583224 vt 0.247853 0.611665 vt 0.249540 0.551605 vt 0.260950 0.553894 vt 0.259830 0.566830 vt 0.248784 0.566676 vt 0.248056 0.590889 vt 0.276819 0.581922 vt 0.286276 0.629452 vt 0.273347 0.652531 vt 0.318924 0.686348 vt 0.312393 0.686894 vt 0.220364 0.645587 vt 0.298848 0.641961 vt 0.291967 0.639014 vt 0.217382 0.623131 vt 0.215765 0.633050 vt 0.214253 0.642234 vt 0.218243 0.543016 vt 0.211509 0.659720 vt 0.209605 0.676317 vt 0.261699 0.692361 vt 0.209689 0.684745 vt 0.195600 0.890617 vt 0.192100 0.890778 vt 0.193563 0.885521 vt 0.196517 0.887992 vt 0.198547 0.897778 vt 0.196720 0.903329 vt 0.192289 0.899066 vt 0.195943 0.894593 vt 0.203405 0.900592 vt 0.202915 0.905597 vt 0.209404 0.899759 vt 0.210279 0.903959 vt 0.214199 0.895839 vt 0.217510 0.897960 vt 0.214997 0.891709 vt 0.218896 0.892017 vt 0.210440 0.883442 vt 0.212687 0.878878 vt 0.216950 0.883526 vt 0.213758 0.887138 vt 0.204196 0.881804 vt 0.204007 0.876106 vt 0.198883 0.884933 vt 0.197224 0.880824 vt 0.189545 0.890813 vt 0.189622 0.885451 vt 0.187879 0.891261 vt 0.187879 0.886228 vt 0.189573 0.877310 vt 0.192205 0.876309 vt 0.187879 0.878157 vt 0.189447 0.870485 vt 0.191456 0.870709 vt 0.187879 0.870926 vt 0.201550 0.851074 vt 0.203335 0.844508 vt 0.208480 0.847497 vt 0.206219 0.853811 vt 0.195152 0.873740 vt 0.199338 0.867804 vt 0.194053 0.869407 vt 0.196818 0.863513 vt 0.199555 0.856919 vt 0.203580 0.859747 vt 0.196860 0.913262 vt 0.192100 0.911414 vt 0.204357 0.913360 vt 0.213576 0.909874 vt 0.224034 0.901404 vt 0.189622 0.910119 vt 0.189671 0.900179 vt 0.187879 0.909132 vt 0.187879 0.899920 vt 0.202222 0.825860 vt 0.198666 0.824257 vt 0.197210 0.818545 vt 0.203909 0.821534 vt 0.193654 0.826651 vt 0.187879 0.827281 vt 0.187879 0.824761 vt 0.192863 0.823284 vt 0.205757 0.833259 vt 0.204847 0.829927 vt 0.208368 0.827687 vt 0.210146 0.833007 vt 0.210286 0.839349 vt 0.205694 0.837354 vt 0.191561 0.819308 vt 0.192555 0.814975 vt 0.192758 0.811363 vt 0.197126 0.813820 vt 0.204959 0.818027 vt 0.198463 0.799582 vt 0.206464 0.801899 vt 0.206422 0.805840 vt 0.198498 0.802074 vt 0.193367 0.800191 vt 0.192933 0.798259 vt 0.218455 0.887460 vt 0.214563 0.889357 vt 0.191764 0.894047 vt 0.195481 0.892346 vt 0.189531 0.894677 vt 0.187879 0.894558 vt 0.187879 0.833924 vt 0.192800 0.833392 vt 0.192674 0.836409 vt 0.187879 0.836626 vt 0.201592 0.834393 vt 0.201347 0.834127 vt 0.202040 0.833238 vt 0.202348 0.833987 vt 0.197910 0.829843 vt 0.201039 0.830452 vt 0.193584 0.829738 vt 0.187879 0.829220 vt 0.197266 0.832580 vt 0.197084 0.834862 vt 0.201403 0.834785 vt 0.202117 0.835002 vt 0.200885 0.836871 vt 0.200528 0.835618 vt 0.192821 0.841414 vt 0.192772 0.843612 vt 0.187879 0.842800 vt 0.187879 0.840847 vt 0.197574 0.837984 vt 0.197707 0.839776 vt 0.203398 0.833525 vt 0.202957 0.831978 vt 0.201284 0.838761 vt 0.203055 0.835597 vt 0.200612 0.832461 vt 0.200269 0.833903 vt 0.197007 0.851207 vt 0.192128 0.851704 vt 0.192555 0.849240 vt 0.198057 0.846797 vt 0.187879 0.845138 vt 0.192814 0.846076 vt 0.187879 0.848190 vt 0.189440 0.865403 vt 0.187879 0.865263 vt 0.192961 0.865697 vt 0.194319 0.862365 vt 0.191064 0.866019 vt 0.192982 0.858704 vt 0.194074 0.857528 vt 0.191351 0.857633 vt 0.191015 0.856100 vt 0.189825 0.857290 vt 0.187879 0.855085 vt 0.187879 0.850605 vt 0.197994 0.841771 vt 0.195558 0.855015 vt 0.191449 0.854441 vt 0.187879 0.853853 vt 0.187879 0.819175 vt 0.187879 0.813113 vt 0.187879 0.809732 vt 0.187879 0.798966 vt 0.187879 0.797405 vt 0.250809 0.840952 vt 0.259678 0.854406 vt 0.256857 0.857745 vt 0.247309 0.846769 vt 0.216642 0.805203 vt 0.216460 0.811062 vt 0.233078 0.812294 vt 0.232644 0.819805 vt 0.255541 0.831250 vt 0.253868 0.835779 vt 0.214815 0.824628 vt 0.210489 0.865872 vt 0.217510 0.833931 vt 0.217517 0.842842 vt 0.215480 0.852446 vt 0.213226 0.859299 vt 0.227401 0.892871 vt 0.217825 0.872137 vt 0.226771 0.879571 vt 0.227989 0.886557 vt 0.221213 0.865718 vt 0.224356 0.858648 vt 0.231552 0.871948 vt 0.235598 0.865109 vt 0.228213 0.848834 vt 0.241212 0.856457 vt 0.187879 0.858151 vt 0.189461 0.859593 vt 0.189811 0.861693 vt 0.187879 0.861350 vt 0.190525 0.860230 vt 0.191113 0.861798 vt 0.192142 0.862442 vt 0.192737 0.861056 vt 0.192254 0.859684 vt 0.191274 0.860314 vt 0.191694 0.860167 vt 0.191799 0.860923 vt 0.191393 0.860860 vt 0.190931 0.858844 vt 0.190518 0.859236 vt 0.189237 0.858116 vt 0.191575 0.859096 vt 0.191456 0.859740 vt 0.191197 0.859866 vt 0.191862 0.859985 vt 0.191680 0.859698 vt 0.192002 0.860412 vt 0.187879 0.857178 vt 0.198687 0.889742 vt 0.198036 0.890897 vt 0.198358 0.892444 vt 0.200269 0.894901 vt 0.203951 0.896966 vt 0.208494 0.896399 vt 0.211000 0.894229 vt 0.211791 0.892234 vt 0.211336 0.890162 vt 0.208907 0.887117 vt 0.204532 0.885962 vt 0.200591 0.888090 vt 0.211707 0.891261 vt 0.198022 0.891548 vt 0.204763 0.888944 vt 0.201088 0.889777 vt 0.199198 0.890617 vt 0.198771 0.891107 vt 0.198750 0.891506 vt 0.199072 0.892052 vt 0.200864 0.893900 vt 0.204049 0.895433 vt 0.207766 0.894691 vt 0.209656 0.893340 vt 0.210181 0.892619 vt 0.210097 0.892255 vt 0.209796 0.891709 vt 0.207990 0.889840 vt 0.199562 0.891898 vt 0.201102 0.893578 vt 0.204147 0.894936 vt 0.207640 0.894215 vt 0.209264 0.893088 vt 0.209551 0.892605 vt 0.209229 0.891961 vt 0.207528 0.890512 vt 0.204672 0.889763 vt 0.201403 0.890428 vt 0.199744 0.891044 vt 0.209509 0.892346 vt 0.199982 0.891954 vt 0.201361 0.893074 vt 0.204154 0.893984 vt 0.207262 0.893459 vt 0.208557 0.892829 vt 0.208557 0.892465 vt 0.208396 0.891940 vt 0.207052 0.891163 vt 0.204777 0.890568 vt 0.201830 0.891037 vt 0.200472 0.891184 vt 0.208494 0.892255 vt 0.199926 0.891534 vt 0.201305 0.891877 vt 0.207150 0.891786 vt 0.204560 0.892220 vt 0.207304 0.892367 vt 0.204700 0.891366 vt 0.201508 0.891387 vt 0.200150 0.891324 vt 0.199758 0.924448 vt 0.193605 0.924189 vt 0.208844 0.922243 vt 0.220380 0.916566 vt 0.231755 0.905604 vt 0.190280 0.924147 vt 0.187879 0.923797 vt 0.243368 0.881874 vt 0.240470 0.888118 vt 0.237481 0.895433 vt 0.246623 0.877429 vt 0.250564 0.871990 vt 0.254995 0.865123 vt 0.269471 0.845439 vt 0.257851 0.825986 vt 0.262072 0.821415 vt 0.273293 0.842597 vt 0.233575 0.798483 vt 0.256248 0.788781 vt 0.243914 0.794892 vt 0.236522 0.784063 vt 0.248289 0.777539 vt 0.199758 0.712404 vt 0.204882 0.712404 vt 0.209698 0.721952 vt 0.203846 0.722239 vt 0.215683 0.732816 vt 0.209271 0.734202 vt 0.226498 0.787696 vt 0.200941 0.767473 vt 0.206688 0.766598 vt 0.211665 0.779940 vt 0.204231 0.780696 vt 0.216131 0.795984 vt 0.214108 0.788123 vt 0.206303 0.795011 vt 0.205428 0.788144 vt 0.192534 0.793835 vt 0.187879 0.793891 vt 0.192548 0.787878 vt 0.187879 0.787962 vt 0.198666 0.794157 vt 0.198463 0.787794 vt 0.220709 0.778680 vt 0.214101 0.765030 vt 0.222039 0.761642 vt 0.230145 0.774578 vt 0.203524 0.735070 vt 0.199107 0.722498 vt 0.196167 0.712411 vt 0.196027 0.768012 vt 0.198036 0.780752 vt 0.215011 0.712390 vt 0.218203 0.721686 vt 0.222165 0.730723 vt 0.273391 0.782376 vt 0.260042 0.770357 vt 0.282554 0.810957 vt 0.270647 0.815521 vt 0.280902 0.838929 vt 0.291472 0.835912 vt 0.191743 0.768600 vt 0.192513 0.781172 vt 0.187879 0.782026 vt 0.187879 0.769132 vt 0.240890 0.767718 vt 0.231881 0.755811 vt 0.238132 0.749560 vt 0.248037 0.760011 vt 0.191456 0.743680 vt 0.187879 0.740054 vt 0.187879 0.735623 vt 0.193325 0.736358 vt 0.198113 0.735819 vt 0.202418 0.752276 vt 0.198155 0.753102 vt 0.208725 0.750771 vt 0.215774 0.747810 vt 0.223642 0.743659 vt 0.229382 0.739459 vt 0.252951 0.884443 vt 0.250088 0.891002 vt 0.246392 0.898597 vt 0.240078 0.909083 vt 0.259965 0.875567 vt 0.259776 0.869211 vt 0.262681 0.871178 vt 0.263584 0.877268 vt 0.255968 0.880607 vt 0.229095 0.922523 vt 0.192121 0.945273 vt 0.187879 0.948787 vt 0.216502 0.932008 vt 0.205855 0.937937 vt 0.197574 0.942067 vt 0.248394 0.911631 vt 0.238069 0.926485 vt 0.225882 0.937958 vt 0.214087 0.946855 vt 0.203685 0.953659 vt 0.195432 0.959168 vt 0.187879 0.964404 vt 0.260630 0.898947 vt 0.261645 0.897764 vt 0.266153 0.900564 vt 0.265572 0.901642 vt 0.260189 0.881944 vt 0.265467 0.878199 vt 0.262079 0.882735 vt 0.257690 0.885395 vt 0.259804 0.885983 vt 0.259216 0.892864 vt 0.256654 0.893585 vt 0.271725 0.900774 vt 0.271515 0.901810 vt 0.255170 0.913073 vt 0.258684 0.903938 vt 0.264417 0.905751 vt 0.262093 0.913997 vt 0.246035 0.928662 vt 0.255317 0.930055 vt 0.235227 0.941661 vt 0.247043 0.944251 vt 0.223817 0.952441 vt 0.237726 0.956627 vt 0.212764 0.961051 vt 0.227898 0.967386 vt 0.203055 0.968086 vt 0.218483 0.977102 vt 0.193724 0.973504 vt 0.209047 0.986671 vt 0.253119 0.901523 vt 0.255639 0.861000 vt 0.244635 0.851375 vt 0.230439 0.841162 vt 0.231342 0.834309 vt 0.232063 0.826791 vt 0.215746 0.817642 vt 0.205456 0.811811 vt 0.197364 0.807870 vt 0.192835 0.805476 vt 0.187879 0.804307 vt 0.266727 0.849408 vt 0.263850 0.851907 vt 0.227716 0.721672 vt 0.226701 0.712390 vt 0.241037 0.712383 vt 0.239840 0.722960 vt 0.228213 0.728343 vt 0.236074 0.732620 vt 0.251089 0.724955 vt 0.255674 0.712383 vt 0.267658 0.712362 vt 0.262030 0.726418 vt 0.245804 0.738402 vt 0.256395 0.743904 vt 0.278270 0.860552 vt 0.275505 0.856716 vt 0.279180 0.855925 vt 0.281336 0.860349 vt 0.281497 0.893067 vt 0.284038 0.885878 vt 0.284983 0.886270 vt 0.281966 0.893970 vt 0.280839 0.864997 vt 0.283240 0.865158 vt 0.284423 0.878752 vt 0.283114 0.870947 vt 0.284801 0.871150 vt 0.285683 0.879137 vt 0.276947 0.898387 vt 0.276800 0.899549 vt 0.272761 0.852264 vt 0.276821 0.850619 vt 0.270871 0.905821 vt 0.269667 0.914011 vt 0.266181 0.930216 vt 0.261624 0.944951 vt 0.256591 0.958167 vt 0.251047 0.970011 vt 0.245097 0.980952 vt 0.238251 0.990689 vt 0.352071 0.852467 vt 0.359512 0.855491 vt 0.359512 0.870135 vt 0.351553 0.868490 vt 0.347059 0.925799 vt 0.359512 0.929593 vt 0.355494 0.950166 vt 0.342103 0.942970 vt 0.305486 0.895384 vt 0.315496 0.905401 vt 0.310533 0.917462 vt 0.301706 0.905821 vt 0.287265 0.923097 vt 0.291899 0.938217 vt 0.276065 0.943327 vt 0.276541 0.928151 vt 0.276583 0.903770 vt 0.282575 0.898821 vt 0.284108 0.908467 vt 0.276807 0.912387 vt 0.288847 0.884135 vt 0.289169 0.875644 vt 0.296001 0.884457 vt 0.294188 0.893578 vt 0.286880 0.891611 vt 0.290303 0.901887 vt 0.302959 0.928837 vt 0.295595 0.915236 vt 0.325205 0.984802 vt 0.283954 0.990528 vt 0.280762 0.980028 vt 0.313403 0.972958 vt 0.276681 0.956382 vt 0.297170 0.950656 vt 0.304219 0.961702 vt 0.278270 0.968205 vt 0.325282 0.913437 vt 0.335768 0.920304 vt 0.330056 0.935340 vt 0.319724 0.926989 vt 0.320312 0.950019 vt 0.310820 0.939911 vt 0.346373 0.970900 vt 0.332114 0.960113 vt 0.306137 0.835485 vt 0.321369 0.837823 vt 0.321614 0.853951 vt 0.307817 0.849478 vt 0.308426 0.873327 vt 0.320151 0.880502 vt 0.318625 0.892997 vt 0.307488 0.884380 vt 0.288700 0.868497 vt 0.287643 0.862134 vt 0.296484 0.866243 vt 0.296701 0.875315 vt 0.284311 0.848778 vt 0.294209 0.847497 vt 0.286264 0.855862 vt 0.295707 0.857178 vt 0.321005 0.867748 vt 0.308524 0.861917 vt 0.359512 0.897932 vt 0.359512 0.913703 vt 0.349152 0.910553 vt 0.350384 0.896266 vt 0.328775 0.899997 vt 0.330770 0.886795 vt 0.340759 0.892234 vt 0.338911 0.905926 vt 0.333528 0.841911 vt 0.343769 0.847742 vt 0.342957 0.864192 vt 0.332821 0.858690 vt 0.341998 0.878689 vt 0.331869 0.873250 vt 0.359512 0.885024 vt 0.350839 0.883043 vt 0.195187 0.722603 vt 0.193122 0.712411 vt 0.191169 0.722477 vt 0.190077 0.712411 vt 0.187879 0.721728 vt 0.187879 0.712411 vt 0.270178 0.752094 vt 0.281189 0.756238 vt 0.322174 0.726446 vt 0.308972 0.729911 vt 0.307103 0.712341 vt 0.319577 0.712348 vt 0.278816 0.712348 vt 0.275078 0.729183 vt 0.292669 0.712341 vt 0.292886 0.731458 vt 0.283835 0.730520 vt 0.285739 0.712348 vt 0.325422 0.748846 vt 0.311856 0.757169 vt 0.293376 0.759269 vt 0.317855 0.789894 vt 0.330420 0.766906 vt 0.296155 0.788809 vt 0.301153 0.811685 vt 0.319479 0.815381 vt 0.353219 0.829759 vt 0.359512 0.832223 vt 0.334039 0.820603 vt 0.344399 0.825475 vt 0.353716 0.806386 vt 0.359512 0.809487 vt 0.334928 0.797265 vt 0.345330 0.802214 vt 0.354437 0.785470 vt 0.359512 0.788795 vt 0.338687 0.776034 vt 0.346835 0.781305 vt 0.355564 0.723436 vt 0.355445 0.712390 vt 0.359512 0.712390 vt 0.359512 0.722169 vt 0.348781 0.723254 vt 0.348151 0.712383 vt 0.341151 0.724353 vt 0.340724 0.712362 vt 0.354878 0.766906 vt 0.359512 0.771351 vt 0.348242 0.762895 vt 0.355543 0.746431 vt 0.348893 0.743848 vt 0.359512 0.748097 vt 0.341228 0.759325 vt 0.341599 0.742525 vt 0.335628 0.756238 vt 0.333892 0.742875 vt 0.332331 0.724269 vt 0.331505 0.712348 vt 0.187879 0.839776 vt 0.192891 0.839923 vt 0.197490 0.837193 vt 0.200395 0.835303 vt 0.192779 0.837354 vt 0.187879 0.837445 vt 0.197161 0.835611 vt 0.200815 0.834358 vt 0.201004 0.834561 vt 0.187879 0.755790 vt 0.190826 0.755482 vt 0.194375 0.754516 vt 0.190154 0.748377 vt 0.187879 0.745871 vt 0.264186 0.883603 vt 0.267413 0.879711 vt 0.269618 0.882077 vt 0.267056 0.884786 vt 0.264123 0.898072 vt 0.267189 0.895251 vt 0.271641 0.897302 vt 0.270871 0.900928 vt 0.261799 0.892325 vt 0.264662 0.891044 vt 0.262156 0.886389 vt 0.265369 0.886893 vt 0.275624 0.896217 vt 0.276443 0.898681 vt 0.278046 0.892465 vt 0.280006 0.894047 vt 0.279726 0.887642 vt 0.281672 0.888825 vt 0.267525 0.900207 vt 0.282260 0.888111 vt 0.280314 0.893837 vt 0.276583 0.898478 vt 0.271641 0.900795 vt 0.279607 0.881692 vt 0.281777 0.881622 vt 0.278221 0.875749 vt 0.280440 0.875294 vt 0.275785 0.870527 vt 0.278347 0.869512 vt 0.273349 0.866684 vt 0.275806 0.864885 vt 0.277206 0.864045 vt 0.279544 0.868532 vt 0.281455 0.874223 vt 0.282631 0.880782 vt 0.270325 0.863191 vt 0.273265 0.861098 vt 0.269387 0.857486 vt 0.265425 0.859656 vt 0.274217 0.859663 vt 0.270745 0.855596 vt 0.260413 0.865242 vt 0.263157 0.867839 vt 0.262219 0.862449 vt 0.266545 0.866754 vt 0.199464 0.891485 vt 0.199534 0.891268 vt 0.266104 0.871948 vt 0.268988 0.874664 vt 0.270045 0.869344 vt 0.272719 0.872886 vt 0.275932 0.877289 vt 0.276758 0.882959 vt 0.271459 0.878164 vt 0.273762 0.880278 vt 0.273188 0.884254 vt 0.269758 0.891751 vt 0.271760 0.887817 vt 0.274063 0.892542 vt 0.275750 0.887915 vt 0.200213 0.834351 vt 0.200906 0.834876 vt 0.201025 0.834652 vt 0.180158 0.890617 vt 0.179248 0.887992 vt 0.182209 0.885521 vt 0.183672 0.890778 vt 0.177218 0.897778 vt 0.179822 0.894593 vt 0.183476 0.899066 vt 0.179031 0.903329 vt 0.172346 0.900592 vt 0.172836 0.905597 vt 0.166354 0.899759 vt 0.165493 0.903959 vt 0.161573 0.895839 vt 0.158262 0.897960 vt 0.160768 0.891709 vt 0.156869 0.892017 vt 0.165332 0.883442 vt 0.162007 0.887138 vt 0.158815 0.883526 vt 0.163064 0.878878 vt 0.171569 0.881804 vt 0.171758 0.876106 vt 0.176875 0.884933 vt 0.178541 0.880824 vt 0.186150 0.885451 vt 0.186213 0.890813 vt 0.183560 0.876309 vt 0.186192 0.877310 vt 0.184302 0.870709 vt 0.186318 0.870485 vt 0.174215 0.851074 vt 0.169539 0.853811 vt 0.167285 0.847497 vt 0.172430 0.844508 vt 0.180606 0.873740 vt 0.176427 0.867804 vt 0.181712 0.869407 vt 0.178947 0.863513 vt 0.172178 0.859747 vt 0.176217 0.856919 vt 0.183672 0.911414 vt 0.178898 0.913262 vt 0.171408 0.913360 vt 0.162189 0.909874 vt 0.151731 0.901404 vt 0.186087 0.900179 vt 0.186150 0.910119 vt 0.173536 0.825860 vt 0.171856 0.821534 vt 0.178541 0.818545 vt 0.177099 0.824257 vt 0.182097 0.826651 vt 0.182902 0.823284 vt 0.170008 0.833259 vt 0.165605 0.833007 vt 0.167390 0.827687 vt 0.170918 0.829927 vt 0.165479 0.839349 vt 0.170071 0.837354 vt 0.183203 0.814975 vt 0.184204 0.819308 vt 0.178639 0.813820 vt 0.183000 0.811363 vt 0.170813 0.818027 vt 0.177302 0.799582 vt 0.177274 0.802074 vt 0.169343 0.805840 vt 0.169301 0.801899 vt 0.182398 0.800191 vt 0.182839 0.798259 vt 0.161209 0.889357 vt 0.157310 0.887460 vt 0.180270 0.892346 vt 0.183987 0.894047 vt 0.186234 0.894677 vt 0.183091 0.836409 vt 0.182965 0.833392 vt 0.174173 0.834393 vt 0.173417 0.833987 vt 0.173718 0.833238 vt 0.174418 0.834127 vt 0.177862 0.829843 vt 0.174733 0.830452 vt 0.182174 0.829738 vt 0.178681 0.834862 vt 0.178478 0.832580 vt 0.174369 0.834785 vt 0.175244 0.835618 vt 0.174866 0.836871 vt 0.173641 0.835002 vt 0.182930 0.841414 vt 0.183000 0.843612 vt 0.178177 0.837984 vt 0.178051 0.839776 vt 0.172367 0.833525 vt 0.172794 0.831978 vt 0.172703 0.835597 vt 0.174474 0.838761 vt 0.175496 0.833903 vt 0.175160 0.832461 vt 0.178744 0.851207 vt 0.177708 0.846797 vt 0.183224 0.849240 vt 0.183637 0.851704 vt 0.182951 0.846076 vt 0.186332 0.865403 vt 0.181446 0.862365 vt 0.182797 0.865697 vt 0.184708 0.866019 vt 0.181691 0.857528 vt 0.182769 0.858704 vt 0.184750 0.856100 vt 0.184407 0.857633 vt 0.185947 0.857290 vt 0.177764 0.841771 vt 0.180207 0.855015 vt 0.184309 0.854441 vt 0.124956 0.840952 vt 0.128456 0.846769 vt 0.118908 0.857745 vt 0.116080 0.854406 vt 0.159298 0.811062 vt 0.159116 0.805203 vt 0.143121 0.819805 vt 0.142673 0.812294 vt 0.121890 0.835779 vt 0.120210 0.831250 vt 0.160943 0.824628 vt 0.165276 0.865872 vt 0.158262 0.833931 vt 0.160292 0.852446 vt 0.158248 0.842842 vt 0.162532 0.859299 vt 0.148371 0.892871 vt 0.157940 0.872137 vt 0.149001 0.879571 vt 0.147783 0.886557 vt 0.154552 0.865718 vt 0.151416 0.858648 vt 0.144206 0.871948 vt 0.140167 0.865109 vt 0.147559 0.848834 vt 0.134546 0.856457 vt 0.185954 0.861693 vt 0.186311 0.859593 vt 0.184652 0.861798 vt 0.185240 0.860230 vt 0.183616 0.862442 vt 0.183021 0.861056 vt 0.183511 0.859684 vt 0.184484 0.860314 vt 0.184365 0.860860 vt 0.183959 0.860923 vt 0.184078 0.860167 vt 0.186535 0.858116 vt 0.185254 0.859236 vt 0.184820 0.858844 vt 0.184190 0.859096 vt 0.184554 0.859866 vt 0.184302 0.859740 vt 0.184085 0.859698 vt 0.183903 0.859985 vt 0.183756 0.860412 vt 0.177722 0.890897 vt 0.177071 0.889742 vt 0.175510 0.894901 vt 0.177400 0.892444 vt 0.171814 0.896966 vt 0.167271 0.896399 vt 0.164751 0.894229 vt 0.163953 0.892234 vt 0.166858 0.887117 vt 0.164436 0.890162 vt 0.171240 0.885962 vt 0.175181 0.888090 vt 0.164058 0.891261 vt 0.177729 0.891548 vt 0.174677 0.889777 vt 0.171002 0.888944 vt 0.176567 0.890617 vt 0.176994 0.891107 vt 0.176693 0.892052 vt 0.177008 0.891506 vt 0.174901 0.893900 vt 0.171709 0.895433 vt 0.167999 0.894691 vt 0.166102 0.893340 vt 0.165584 0.892619 vt 0.165969 0.891709 vt 0.165675 0.892255 vt 0.167775 0.889840 vt 0.174656 0.893578 vt 0.176196 0.891898 vt 0.171618 0.894936 vt 0.168132 0.894215 vt 0.166508 0.893088 vt 0.166228 0.892605 vt 0.168237 0.890512 vt 0.166536 0.891961 vt 0.171086 0.889763 vt 0.174355 0.890428 vt 0.176021 0.891044 vt 0.166256 0.892346 vt 0.174411 0.893074 vt 0.175790 0.891954 vt 0.171611 0.893984 vt 0.168503 0.893459 vt 0.167208 0.892829 vt 0.167208 0.892465 vt 0.168706 0.891163 vt 0.167369 0.891940 vt 0.170988 0.890568 vt 0.173942 0.891037 vt 0.175293 0.891184 vt 0.167271 0.892255 vt 0.174453 0.891877 vt 0.175839 0.891534 vt 0.168622 0.891786 vt 0.168461 0.892367 vt 0.171205 0.892220 vt 0.174257 0.891387 vt 0.171065 0.891366 vt 0.175615 0.891324 vt 0.182153 0.924189 vt 0.176014 0.924448 vt 0.166921 0.922243 vt 0.155378 0.916566 vt 0.144010 0.905604 vt 0.185485 0.924147 vt 0.135295 0.888118 vt 0.132397 0.881874 vt 0.138284 0.895433 vt 0.129142 0.877429 vt 0.125201 0.871990 vt 0.120770 0.865123 vt 0.106294 0.845439 vt 0.102458 0.842597 vt 0.113700 0.821415 vt 0.117914 0.825986 vt 0.142183 0.798483 vt 0.119517 0.788781 vt 0.127476 0.777539 vt 0.139250 0.784063 vt 0.131858 0.794892 vt 0.166053 0.721952 vt 0.170883 0.712404 vt 0.176014 0.712404 vt 0.171912 0.722239 vt 0.160075 0.732816 vt 0.166494 0.734202 vt 0.149267 0.787696 vt 0.174824 0.767473 vt 0.171527 0.780696 vt 0.164093 0.779940 vt 0.169077 0.766598 vt 0.159627 0.795984 vt 0.161657 0.788123 vt 0.169455 0.795011 vt 0.170337 0.788144 vt 0.183224 0.793835 vt 0.183224 0.787878 vt 0.177092 0.794157 vt 0.177295 0.787794 vt 0.155056 0.778680 vt 0.145620 0.774578 vt 0.153733 0.761642 vt 0.161657 0.765030 vt 0.176665 0.722498 vt 0.172241 0.735070 vt 0.179598 0.712411 vt 0.179738 0.768012 vt 0.177722 0.780752 vt 0.157555 0.721686 vt 0.160747 0.712390 vt 0.153593 0.730723 vt 0.102374 0.782376 vt 0.115716 0.770357 vt 0.105118 0.815521 vt 0.093211 0.810957 vt 0.094870 0.838929 vt 0.084279 0.835912 vt 0.184022 0.768600 vt 0.183252 0.781172 vt 0.134875 0.767718 vt 0.143884 0.755811 vt 0.137633 0.749560 vt 0.127714 0.760011 vt 0.184302 0.743680 vt 0.182440 0.736358 vt 0.177610 0.753102 vt 0.173354 0.752276 vt 0.177645 0.735819 vt 0.167040 0.750771 vt 0.159991 0.747810 vt 0.152130 0.743659 vt 0.146383 0.739459 vt 0.125684 0.891002 vt 0.122807 0.884443 vt 0.135680 0.909083 vt 0.129380 0.898597 vt 0.115800 0.875567 vt 0.112160 0.877268 vt 0.113091 0.871178 vt 0.115996 0.869211 vt 0.119797 0.880607 vt 0.146663 0.922523 vt 0.183651 0.945273 vt 0.159256 0.932008 vt 0.169903 0.937937 vt 0.178177 0.942067 vt 0.137696 0.926485 vt 0.127357 0.911631 vt 0.149883 0.937958 vt 0.161664 0.946855 vt 0.172073 0.953659 vt 0.180340 0.959168 vt 0.115142 0.898947 vt 0.110193 0.901642 vt 0.109619 0.900564 vt 0.114127 0.897764 vt 0.115569 0.881944 vt 0.113679 0.882735 vt 0.110291 0.878199 vt 0.118075 0.885395 vt 0.119111 0.893585 vt 0.116549 0.892864 vt 0.115961 0.885983 vt 0.104257 0.901810 vt 0.104033 0.900774 vt 0.120595 0.913073 vt 0.113679 0.913997 vt 0.111334 0.905751 vt 0.117074 0.903938 vt 0.129716 0.928662 vt 0.120448 0.930055 vt 0.140538 0.941661 vt 0.128729 0.944251 vt 0.151948 0.952441 vt 0.138039 0.956627 vt 0.162994 0.961051 vt 0.147867 0.967386 vt 0.172710 0.968086 vt 0.157289 0.977102 vt 0.182048 0.973504 vt 0.166711 0.986671 vt 0.122646 0.901523 vt 0.120119 0.861000 vt 0.131116 0.851375 vt 0.144430 0.834309 vt 0.145312 0.841162 vt 0.143702 0.826791 vt 0.160019 0.817642 vt 0.170309 0.811811 vt 0.178394 0.807870 vt 0.182930 0.805476 vt 0.111915 0.851907 vt 0.109038 0.849408 vt 0.148035 0.721672 vt 0.135925 0.722960 vt 0.134728 0.712383 vt 0.149050 0.712390 vt 0.139684 0.732620 vt 0.147559 0.728343 vt 0.124676 0.724955 vt 0.113742 0.726418 vt 0.108114 0.712362 vt 0.120098 0.712383 vt 0.119370 0.743904 vt 0.129968 0.738402 vt 0.097495 0.860552 vt 0.094429 0.860349 vt 0.096578 0.855925 vt 0.100253 0.856716 vt 0.094268 0.893067 vt 0.093785 0.893970 vt 0.090789 0.886270 vt 0.091727 0.885878 vt 0.094933 0.864997 vt 0.092525 0.865158 vt 0.091342 0.878752 vt 0.090082 0.879137 vt 0.090978 0.871150 vt 0.092658 0.870947 vt 0.098818 0.898387 vt 0.098965 0.899549 vt 0.098944 0.850619 vt 0.102997 0.852264 vt 0.106105 0.914011 vt 0.104894 0.905821 vt 0.109584 0.930216 vt 0.114148 0.944951 vt 0.119167 0.958167 vt 0.124718 0.970011 vt 0.130668 0.980952 vt 0.137507 0.990689 vt 0.023687 0.852467 vt 0.024198 0.868490 vt 0.016253 0.870135 vt 0.016253 0.855491 vt 0.028706 0.925799 vt 0.033655 0.942970 vt 0.020271 0.950166 vt 0.016253 0.929593 vt 0.070265 0.895384 vt 0.074059 0.905821 vt 0.065225 0.917462 vt 0.060269 0.905401 vt 0.088500 0.923097 vt 0.099224 0.928151 vt 0.099693 0.943327 vt 0.083852 0.938217 vt 0.099168 0.903770 vt 0.098944 0.912387 vt 0.091650 0.908467 vt 0.093176 0.898821 vt 0.086911 0.884135 vt 0.081577 0.893578 vt 0.079764 0.884457 vt 0.086596 0.875644 vt 0.085455 0.901887 vt 0.088871 0.891611 vt 0.072813 0.928837 vt 0.080156 0.915236 vt 0.050560 0.984802 vt 0.062362 0.972958 vt 0.094996 0.980028 vt 0.091811 0.990528 vt 0.099084 0.956382 vt 0.097481 0.968205 vt 0.071553 0.961702 vt 0.078602 0.950656 vt 0.050483 0.913437 vt 0.056041 0.926989 vt 0.045709 0.935340 vt 0.039997 0.920304 vt 0.055453 0.950019 vt 0.064952 0.939911 vt 0.029392 0.970900 vt 0.043637 0.960113 vt 0.069628 0.835485 vt 0.067948 0.849478 vt 0.054151 0.853951 vt 0.054396 0.837823 vt 0.067339 0.873327 vt 0.068284 0.884380 vt 0.057140 0.892997 vt 0.055614 0.880502 vt 0.087058 0.868497 vt 0.079057 0.875315 vt 0.079274 0.866243 vt 0.088129 0.862134 vt 0.091461 0.848778 vt 0.081549 0.847497 vt 0.080051 0.857178 vt 0.089508 0.855862 vt 0.054760 0.867748 vt 0.067248 0.861917 vt 0.016253 0.897932 vt 0.025388 0.896266 vt 0.026613 0.910553 vt 0.016253 0.913703 vt 0.046997 0.899997 vt 0.036854 0.905926 vt 0.035006 0.892234 vt 0.044988 0.886795 vt 0.042244 0.841911 vt 0.042944 0.858690 vt 0.032808 0.864192 vt 0.031996 0.847742 vt 0.033767 0.878689 vt 0.043896 0.873250 vt 0.016253 0.885024 vt 0.024919 0.883043 vt 0.182636 0.712411 vt 0.180571 0.722603 vt 0.184596 0.722477 vt 0.185681 0.712411 vt 0.094569 0.756238 vt 0.105580 0.752094 vt 0.053584 0.726446 vt 0.056181 0.712348 vt 0.068655 0.712341 vt 0.066800 0.729911 vt 0.096949 0.712348 vt 0.100680 0.729183 vt 0.083096 0.712341 vt 0.090019 0.712348 vt 0.091930 0.730520 vt 0.082893 0.731458 vt 0.050329 0.748846 vt 0.063916 0.757169 vt 0.082389 0.759269 vt 0.057896 0.789894 vt 0.045352 0.766906 vt 0.079617 0.788809 vt 0.074612 0.811685 vt 0.056286 0.815381 vt 0.016253 0.832223 vt 0.022532 0.829759 vt 0.031366 0.825475 vt 0.041726 0.820603 vt 0.016253 0.809487 vt 0.022042 0.806386 vt 0.030428 0.802214 vt 0.040830 0.797265 vt 0.016253 0.788795 vt 0.021314 0.785470 vt 0.028923 0.781305 vt 0.037085 0.776034 vt 0.020194 0.723436 vt 0.016253 0.722169 vt 0.016253 0.712390 vt 0.020313 0.712390 vt 0.026991 0.723254 vt 0.027621 0.712383 vt 0.034614 0.724353 vt 0.035041 0.712362 vt 0.020887 0.766906 vt 0.016253 0.771351 vt 0.027509 0.762895 vt 0.026872 0.743848 vt 0.020215 0.746431 vt 0.016253 0.748097 vt 0.034537 0.759325 vt 0.034152 0.742525 vt 0.040123 0.756238 vt 0.043434 0.724269 vt 0.041880 0.742875 vt 0.044246 0.712348 vt 0.182881 0.839923 vt 0.178268 0.837193 vt 0.175370 0.835303 vt 0.182986 0.837354 vt 0.178604 0.835611 vt 0.174957 0.834358 vt 0.174761 0.834561 vt 0.184932 0.755482 vt 0.181397 0.754516 vt 0.185611 0.748377 vt 0.111586 0.883603 vt 0.108709 0.884786 vt 0.106147 0.882077 vt 0.108359 0.879711 vt 0.111649 0.898072 vt 0.104887 0.900928 vt 0.104117 0.897302 vt 0.108569 0.895251 vt 0.113966 0.892325 vt 0.111103 0.891044 vt 0.113609 0.886389 vt 0.110396 0.886893 vt 0.099308 0.898681 vt 0.100134 0.896217 vt 0.095766 0.894047 vt 0.097705 0.892465 vt 0.094100 0.888825 vt 0.096039 0.887642 vt 0.108240 0.900207 vt 0.095451 0.893837 vt 0.093512 0.888111 vt 0.099182 0.898478 vt 0.104124 0.900795 vt 0.093988 0.881622 vt 0.096158 0.881692 vt 0.095318 0.875294 vt 0.097551 0.875749 vt 0.097411 0.869512 vt 0.099980 0.870527 vt 0.099959 0.864885 vt 0.102416 0.866684 vt 0.096214 0.868532 vt 0.098559 0.864045 vt 0.094310 0.874223 vt 0.093134 0.880782 vt 0.102493 0.861098 vt 0.105440 0.863191 vt 0.106371 0.857486 vt 0.110333 0.859656 vt 0.101555 0.859663 vt 0.105027 0.855596 vt 0.115352 0.865242 vt 0.112608 0.867839 vt 0.109220 0.866754 vt 0.113539 0.862449 vt 0.176301 0.891485 vt 0.176238 0.891268 vt 0.106763 0.874664 vt 0.109647 0.871948 vt 0.105720 0.869344 vt 0.103039 0.872886 vt 0.099833 0.877289 vt 0.099014 0.882959 vt 0.104306 0.878164 vt 0.102577 0.884254 vt 0.102003 0.880278 vt 0.106007 0.891751 vt 0.104012 0.887817 vt 0.101702 0.892542 vt 0.100015 0.887915 vt 0.175552 0.834351 vt 0.174859 0.834876 vt 0.174740 0.834652 vt 0.544030 0.162262 vt 0.549261 0.132031 vt 0.569552 0.133746 vt 0.565128 0.165297 vt 0.590469 0.136962 vt 0.586584 0.168012 vt 0.611995 0.141081 vt 0.611089 0.170459 vt 0.632136 0.143478 vt 0.634614 0.171874 vt 0.650241 0.143111 vt 0.654723 0.170813 vt 0.667083 0.138634 vt 0.673887 0.165795 vt 0.683617 0.131504 vt 0.694459 0.156871 vt 0.520951 0.157129 vt 0.530036 0.130503 vt 0.554788 0.226461 vt 0.550990 0.263839 vt 0.511265 0.260442 vt 0.527872 0.222247 vt 0.581740 0.228715 vt 0.580187 0.264878 vt 0.610895 0.229989 vt 0.610391 0.264821 vt 0.639498 0.230168 vt 0.640565 0.265681 vt 0.665604 0.228549 vt 0.670115 0.264962 vt 0.691528 0.222947 vt 0.709501 0.262002 vt 0.718014 0.211348 vt 0.734826 0.243567 vt 0.485372 0.246806 vt 0.499683 0.212835 vt 0.553745 0.116510 vt 0.571592 0.115986 vt 0.557692 0.094239 vt 0.574239 0.091166 vt 0.589810 0.118299 vt 0.591387 0.092237 vt 0.611278 0.122282 vt 0.609938 0.096224 vt 0.631865 0.125001 vt 0.628498 0.100191 vt 0.648231 0.125525 vt 0.645169 0.102324 vt 0.663709 0.122857 vt 0.662400 0.100846 vt 0.680989 0.117072 vt 0.681835 0.096041 vt 0.534742 0.117212 vt 0.538892 0.097462 vt 0.572984 0.292452 vt 0.545412 0.294571 vt 0.608602 0.292211 vt 0.645576 0.294116 vt 0.676301 0.295490 vt 0.572429 0.325163 vt 0.543957 0.325757 vt 0.610055 0.326859 vt 0.649327 0.328919 vt 0.679923 0.329711 vt 0.579387 0.354689 vt 0.549942 0.357964 vt 0.614842 0.355946 vt 0.650225 0.359149 vt 0.679677 0.361865 vt 0.704166 0.330605 vt 0.701904 0.363393 vt 0.706795 0.293105 vt 0.515855 0.297093 vt 0.521022 0.330080 vt 0.528421 0.363393 vt 0.560342 0.195144 vt 0.536553 0.190862 vt 0.584337 0.197363 vt 0.610719 0.199143 vt 0.636650 0.199747 vt 0.659661 0.198110 vt 0.681989 0.192639 vt 0.705440 0.182902 vt 0.511601 0.183989 vt 0.909788 0.132516 vt 0.929971 0.130807 vt 0.935178 0.160878 vt 0.914192 0.163899 vt 0.888982 0.135717 vt 0.892850 0.166601 vt 0.867572 0.139817 vt 0.868476 0.169038 vt 0.847539 0.142203 vt 0.845077 0.170447 vt 0.829532 0.141840 vt 0.825076 0.169393 vt 0.812781 0.137389 vt 0.806016 0.164404 vt 0.796336 0.130300 vt 0.785555 0.155530 vt 0.949094 0.129286 vt 0.958134 0.155769 vt 0.924482 0.224736 vt 0.951254 0.220543 vt 0.967777 0.258534 vt 0.928263 0.261916 vt 0.897674 0.226981 vt 0.899220 0.262952 vt 0.868673 0.228249 vt 0.869176 0.262896 vt 0.840222 0.228429 vt 0.839163 0.263753 vt 0.814256 0.226820 vt 0.809770 0.263038 vt 0.788471 0.221248 vt 0.770595 0.260093 vt 0.762128 0.209711 vt 0.745406 0.241756 vt 0.979293 0.211178 vt 0.993532 0.244968 vt 0.925508 0.115370 vt 0.907756 0.114851 vt 0.905120 0.090163 vt 0.921579 0.093217 vt 0.889636 0.117154 vt 0.888063 0.091231 vt 0.868282 0.121119 vt 0.869611 0.095200 vt 0.847806 0.123826 vt 0.851151 0.099149 vt 0.831528 0.124349 vt 0.834570 0.101274 vt 0.816135 0.121698 vt 0.817433 0.099807 vt 0.798948 0.115947 vt 0.798104 0.095031 vt 0.944411 0.116066 vt 0.940280 0.096421 vt 0.933813 0.292486 vt 0.906387 0.290379 vt 0.870957 0.290141 vt 0.834178 0.292036 vt 0.803617 0.293403 vt 0.935262 0.323507 vt 0.906940 0.322918 vt 0.869512 0.324606 vt 0.830447 0.326655 vt 0.800013 0.327442 vt 0.929311 0.355546 vt 0.900019 0.352289 vt 0.864751 0.353540 vt 0.829554 0.356726 vt 0.800257 0.359426 vt 0.778148 0.360946 vt 0.775899 0.328332 vt 0.773285 0.291031 vt 0.963215 0.294992 vt 0.958077 0.327807 vt 0.950719 0.360946 vt 0.942617 0.189325 vt 0.918955 0.193586 vt 0.895088 0.195796 vt 0.868846 0.197568 vt 0.843054 0.198171 vt 0.820167 0.196544 vt 0.797958 0.191103 vt 0.774634 0.181419 vt 0.967437 0.182487 vt 0.887064 0.375332 vt 0.940035 0.391815 vt 0.917995 0.455716 vt 0.870247 0.432190 vt 0.836073 0.367751 vt 0.824546 0.423475 vt 0.788655 0.371611 vt 0.778784 0.421763 vt 0.955353 0.484000 vt 0.986675 0.426237 vt 0.732314 0.422220 vt 0.741108 0.381004 vt 0.900859 0.500210 vt 0.852406 0.476768 vt 0.811575 0.466545 vt 0.770041 0.462344 vt 0.937386 0.525591 vt 0.725651 0.461548 vt 0.880775 0.551075 vt 0.836701 0.527336 vt 0.797792 0.514127 vt 0.759377 0.508439 vt 0.913857 0.575039 vt 0.725651 0.506149 vt 0.865684 0.587482 vt 0.821281 0.564934 vt 0.787202 0.552815 vt 0.755325 0.547510 vt 0.725651 0.546264 vt 0.898001 0.607830 vt 0.569988 0.700590 vt 0.585328 0.719905 vt 0.546321 0.754766 vt 0.535520 0.732184 vt 0.509563 0.768667 vt 0.504323 0.747817 vt 0.675711 0.994379 vt 0.668356 0.975055 vt 0.698759 0.964984 vt 0.705521 0.987438 vt 0.610127 0.696548 vt 0.595169 0.678228 vt 0.732795 0.987196 vt 0.732794 0.962797 vt 0.599583 0.738801 vt 0.555640 0.774850 vt 0.515662 0.787440 vt 0.657888 0.959183 vt 0.691394 0.943641 vt 0.624971 0.715188 vt 0.732794 0.941074 vt 0.603384 0.833329 vt 0.617210 0.851804 vt 0.597434 0.867055 vt 0.585874 0.851314 vt 0.584980 0.813744 vt 0.569211 0.833435 vt 0.556947 0.800928 vt 0.546762 0.823712 vt 0.521444 0.800093 vt 0.521927 0.816784 vt 0.645904 0.929888 vt 0.644990 0.953827 vt 0.628911 0.953150 vt 0.627877 0.930700 vt 0.639473 0.899216 vt 0.620190 0.907346 vt 0.631625 0.876874 vt 0.611086 0.888231 vt 0.637719 0.731981 vt 0.614475 0.763949 vt 0.593459 0.800158 vt 0.652546 0.931978 vt 0.688612 0.924506 vt 0.732795 0.920494 vt 0.575018 0.888069 vt 0.562870 0.873723 vt 0.548625 0.858496 vt 0.532725 0.848360 vt 0.514132 0.839286 vt 0.606113 0.955636 vt 0.602662 0.937567 vt 0.595448 0.919093 vt 0.585940 0.902616 vt 0.611576 0.807529 vt 0.653300 0.901743 vt 0.688758 0.898706 vt 0.732795 0.895839 vt 0.649974 0.751148 vt 0.629721 0.779176 vt 0.640006 0.834554 vt 0.653952 0.861751 vt 0.662656 0.764809 vt 0.644615 0.797575 vt 0.692152 0.870965 vt 0.732796 0.872146 vt 0.463719 0.617262 vt 0.444181 0.640306 vt 0.411954 0.624176 vt 0.435328 0.598532 vt 0.507715 0.593502 vt 0.483111 0.602546 vt 0.457466 0.583360 vt 0.482990 0.572211 vt 0.435205 0.663171 vt 0.430337 0.695020 vt 0.397018 0.695020 vt 0.400633 0.650102 vt 0.387055 0.598617 vt 0.420846 0.565611 vt 0.454881 0.547866 vt 0.489614 0.537081 vt 0.366470 0.695021 vt 0.372441 0.644080 vt 0.661317 0.826152 vt 0.675408 0.846937 vt 0.686811 0.815629 vt 0.698153 0.830461 vt 0.672320 0.776433 vt 0.662649 0.800206 vt 0.681635 0.784822 vt 0.683389 0.801078 vt 0.700561 0.852932 vt 0.732797 0.851748 vt 0.712587 0.835739 vt 0.732798 0.835786 vt 0.559205 0.902622 vt 0.549001 0.890834 vt 0.534429 0.876148 vt 0.519219 0.865725 vt 0.503900 0.856835 vt 0.587167 0.960003 vt 0.582948 0.944963 vt 0.575561 0.927326 vt 0.567531 0.913018 vt 0.540077 0.915350 vt 0.529826 0.906995 vt 0.517398 0.897218 vt 0.505832 0.887784 vt 0.493477 0.877807 vt 0.566086 0.967743 vt 0.562902 0.953735 vt 0.556264 0.938127 vt 0.548642 0.924913 vt 0.519152 0.931261 vt 0.510448 0.923146 vt 0.501167 0.914442 vt 0.492575 0.906612 vt 0.482102 0.897502 vt 0.545274 0.975605 vt 0.540655 0.962839 vt 0.534209 0.950746 vt 0.527138 0.940113 vt 0.503485 0.942839 vt 0.496518 0.934822 vt 0.489492 0.926408 vt 0.482232 0.918688 vt 0.471761 0.909890 vt 0.530689 0.981736 vt 0.524518 0.970370 vt 0.517646 0.960553 vt 0.510690 0.951233 vt 0.796059 0.669944 vt 0.797327 0.684804 vt 0.761766 0.691146 vt 0.759393 0.670512 vt 0.800064 0.699862 vt 0.768023 0.713991 vt 0.807137 0.712911 vt 0.779774 0.732015 vt 0.819904 0.727583 vt 0.797581 0.749343 vt 0.819904 0.608781 vt 0.808127 0.623008 vt 0.782016 0.605592 vt 0.798364 0.586888 vt 0.801463 0.638516 vt 0.769822 0.628971 vt 0.797261 0.654344 vt 0.761848 0.650543 vt 0.727152 0.698963 vt 0.723401 0.671819 vt 0.736196 0.728778 vt 0.753272 0.751032 vt 0.777947 0.772442 vt 0.756732 0.586802 vt 0.779379 0.563621 vt 0.738826 0.617150 vt 0.727058 0.645545 vt 0.564234 0.375332 vt 0.581052 0.432191 vt 0.533304 0.455717 vt 0.511263 0.391816 vt 0.615225 0.367750 vt 0.626753 0.423474 vt 0.662644 0.371609 vt 0.672517 0.421762 vt 0.495946 0.484003 vt 0.464623 0.426240 vt 0.710193 0.381002 vt 0.718988 0.422219 vt 0.598894 0.476769 vt 0.550441 0.500212 vt 0.639725 0.466546 vt 0.681259 0.462344 vt 0.513914 0.525594 vt 0.614600 0.527338 vt 0.570526 0.551077 vt 0.653509 0.514128 vt 0.691924 0.508440 vt 0.537444 0.575042 vt 0.630020 0.564936 vt 0.585617 0.587484 vt 0.664100 0.552816 vt 0.695977 0.547510 vt 0.553300 0.607833 vt 0.895598 0.700619 vt 0.930060 0.732210 vt 0.919260 0.754787 vt 0.880260 0.719930 vt 0.961252 0.747841 vt 0.956012 0.768688 vt 0.789877 0.994378 vt 0.760068 0.987438 vt 0.766829 0.964984 vt 0.797232 0.975055 vt 0.855465 0.696575 vt 0.870421 0.678258 vt 0.909942 0.774869 vt 0.866006 0.738823 vt 0.949914 0.787459 vt 0.774194 0.943641 vt 0.807700 0.959183 vt 0.840622 0.715212 vt 0.862202 0.833339 vt 0.879709 0.851322 vt 0.868150 0.867062 vt 0.848377 0.851812 vt 0.880604 0.813757 vt 0.896371 0.833446 vt 0.908634 0.800943 vt 0.918817 0.823724 vt 0.944132 0.800109 vt 0.943648 0.816798 vt 0.819683 0.929889 vt 0.837709 0.930701 vt 0.836675 0.953150 vt 0.820596 0.953827 vt 0.826114 0.899219 vt 0.845396 0.907349 vt 0.833962 0.876879 vt 0.854498 0.888236 vt 0.827875 0.732002 vt 0.851115 0.763967 vt 0.872127 0.800172 vt 0.813041 0.931979 vt 0.776976 0.924507 vt 0.902711 0.873730 vt 0.890564 0.888074 vt 0.916954 0.858504 vt 0.932852 0.848369 vt 0.951442 0.839296 vt 0.862922 0.937568 vt 0.859470 0.955636 vt 0.870136 0.919095 vt 0.879642 0.902620 vt 0.854012 0.807541 vt 0.812287 0.901746 vt 0.776831 0.898707 vt 0.815620 0.751167 vt 0.835870 0.779192 vt 0.811637 0.861756 vt 0.825584 0.834563 vt 0.802940 0.764824 vt 0.820977 0.797588 vt 0.773439 0.870968 vt 0.463721 0.772779 vt 0.435329 0.791508 vt 0.411956 0.765864 vt 0.444183 0.749734 vt 0.507716 0.796541 vt 0.482990 0.817830 vt 0.457466 0.806681 vt 0.483112 0.787496 vt 0.435207 0.726868 vt 0.400635 0.739939 vt 0.420846 0.824427 vt 0.387057 0.791421 vt 0.489612 0.852961 vt 0.454880 0.842174 vt 0.372443 0.745961 vt 0.790184 0.846942 vt 0.804275 0.826160 vt 0.767441 0.830465 vt 0.778785 0.815636 vt 0.793277 0.776446 vt 0.802945 0.800217 vt 0.782207 0.801087 vt 0.783963 0.784834 vt 0.765031 0.852934 vt 0.753008 0.835742 vt 0.916579 0.890839 vt 0.906375 0.902626 vt 0.931149 0.876153 vt 0.946357 0.865731 vt 0.961674 0.856842 vt 0.882635 0.944965 vt 0.878415 0.960004 vt 0.890021 0.927329 vt 0.898051 0.913022 vt 0.935753 0.906998 vt 0.925503 0.915352 vt 0.948179 0.897221 vt 0.959743 0.887787 vt 0.972096 0.877811 vt 0.902679 0.953737 vt 0.899496 0.967744 vt 0.909318 0.938128 vt 0.916938 0.924915 vt 0.955130 0.923146 vt 0.946427 0.931262 vt 0.964409 0.914442 vt 0.973000 0.906613 vt 0.983471 0.897503 vt 0.924927 0.962840 vt 0.920307 0.975606 vt 0.931372 0.950747 vt 0.938443 0.940114 vt 0.969060 0.934820 vt 0.962095 0.942838 vt 0.976085 0.926406 vt 0.983343 0.918686 vt 0.993812 0.909888 vt 0.941063 0.970371 vt 0.934893 0.981737 vt 0.947936 0.960553 vt 0.954891 0.951233 vt 0.703019 0.666118 vt 0.666353 0.665551 vt 0.668726 0.644916 vt 0.704287 0.651259 vt 0.674984 0.622071 vt 0.707024 0.636201 vt 0.686735 0.604048 vt 0.714097 0.623153 vt 0.704543 0.586720 vt 0.726865 0.608481 vt 0.726866 0.727280 vt 0.705327 0.749173 vt 0.688977 0.730470 vt 0.715088 0.713053 vt 0.676782 0.707091 vt 0.708424 0.697545 vt 0.668807 0.685520 vt 0.704220 0.681718 vt 0.630360 0.664243 vt 0.634112 0.637099 vt 0.643157 0.607284 vt 0.660233 0.585031 vt 0.684909 0.563621 vt 0.686342 0.772442 vt 0.663693 0.749262 vt 0.645786 0.718913 vt 0.634017 0.690518 vn 0.9808 0.0000 0.1951 vn 0.6935 0.6935 0.1951 vn 0.6578 0.6578 0.3668 vn 0.9303 0.0000 0.3668 vn 0.5159 0.5159 0.6838 vn 0.7296 0.0000 0.6838 vn 0.2988 0.2988 0.9063 vn 0.4226 0.0000 0.9063 vn 0.0000 0.7296 0.6838 vn 0.0000 0.4226 0.9063 vn 0.0000 0.9303 0.3668 vn 0.0000 0.9808 0.1951 vn -0.6935 0.6935 0.1951 vn -0.6578 0.6578 0.3668 vn -0.5159 0.5159 0.6838 vn -0.2988 0.2988 0.9063 vn -0.7296 0.0000 0.6838 vn -0.4226 0.0000 0.9063 vn -0.9303 0.0000 0.3668 vn -0.9808 0.0000 0.1951 vn -0.6935 -0.6935 0.1951 vn -0.6578 -0.6578 0.3668 vn -0.5159 -0.5159 0.6838 vn -0.2988 -0.2988 0.9063 vn 0.0000 -0.7296 0.6839 vn 0.0000 -0.4226 0.9063 vn 0.0000 -0.9303 0.3668 vn 0.0000 -0.9808 0.1951 vn 0.6935 -0.6935 0.1951 vn 0.6578 -0.6578 0.3668 vn 0.5159 -0.5159 0.6839 vn 0.2988 -0.2988 0.9063 vn 0.0000 0.7296 0.6839 vn 0.0000 0.0000 1.0000 vn 0.5643 0.8040 0.1874 vn 0.0349 0.9721 0.2318 vn 0.0947 0.8904 0.4452 vn 0.4281 0.8664 0.2569 vn -0.5387 0.8154 0.2117 vn -0.3772 0.8679 0.3231 vn 0.1450 0.7340 0.6634 vn 0.7758 0.5399 0.3265 vn 0.8703 0.2401 0.4299 vn 0.1390 0.3490 0.9267 vn -0.6682 0.5266 0.5255 vn -0.7534 0.1115 0.6481 vn 0.9774 0.0400 -0.2074 vn 0.9857 0.0764 -0.1498 vn 0.6959 -0.0005 -0.7181 vn 0.7255 0.0813 -0.6833 vn -0.1401 0.0042 -0.9901 vn -0.1604 0.1024 -0.9817 vn -0.8955 -0.0806 -0.4376 vn -0.9261 -0.0122 -0.3771 vn -0.9898 -0.0967 0.1042 vn -0.9834 -0.0490 0.1744 vn 0.7592 0.1627 0.6302 vn 0.9763 0.1522 0.1538 vn -0.9769 0.1285 0.1706 vn -0.5997 0.1582 0.7844 vn 0.1708 0.1190 0.9781 vn 0.9530 -0.1198 -0.2783 vn 0.7327 -0.1711 -0.6586 vn -0.1230 -0.2498 -0.9604 vn -0.9050 -0.2604 -0.3363 vn -0.9612 -0.2711 0.0510 vn 0.7664 -0.4016 0.5014 vn 0.9768 -0.2119 0.0309 vn -0.9783 -0.2069 0.0067 vn -0.6627 -0.4289 0.6138 vn 0.1814 -0.5018 0.8457 vn 0.0058 1.0000 0.0024 vn 0.0173 0.9977 0.0647 vn 0.0140 0.9991 0.0408 vn 0.0019 1.0000 0.0008 vn 0.0000 1.0000 0.0000 vn -0.0224 0.9996 0.0142 vn -0.0042 1.0000 0.0027 vn -0.0213 0.9966 0.0796 vn -0.0286 0.9987 0.0418 vn -0.0590 0.9951 0.0785 vn -0.0184 0.9995 0.0245 vn 0.1013 0.9910 0.0873 vn 0.0174 0.9997 0.0150 vn 0.7189 0.0279 -0.6945 vn 0.9626 0.0684 -0.2620 vn 0.9571 0.0645 -0.2824 vn 0.7201 0.0280 -0.6933 vn -0.1395 0.0000 -0.9902 vn -0.9302 -0.0125 -0.3669 vn -0.9322 -0.0126 -0.3615 vn -0.9971 -0.0141 0.0751 vn -0.9955 -0.0159 0.0926 vn 0.9860 -0.0695 0.1517 vn 0.8051 -0.0640 0.5896 vn 0.8059 -0.0642 0.5885 vn 0.9999 0.0103 0.0087 vn -0.6658 -0.0783 0.7420 vn -0.9853 -0.0678 0.1570 vn -0.9989 0.0388 -0.0261 vn -0.6672 -0.0785 0.7407 vn 0.1900 0.0000 0.9817 vn 0.7158 0.0000 -0.6983 vn 0.9327 0.0000 -0.3606 vn 0.7170 0.0000 -0.6970 vn -0.9310 0.0000 -0.3649 vn -0.9332 0.0000 -0.3594 vn -0.9881 0.0000 0.1535 vn 0.0033 -0.9999 -0.0127 vn 0.0023 -0.9999 -0.0089 vn 0.0027 -0.9999 -0.0103 vn 0.0000 -1.0000 0.0000 vn 0.0954 0.0000 0.9954 vn -0.0056 -0.9974 -0.0721 vn 0.0013 -0.9999 0.0089 vn 0.0012 -0.9999 0.0084 vn -0.0046 -0.9981 -0.0606 vn 0.6696 0.6973 0.2557 vn 0.1028 0.8632 0.4942 vn -0.6143 0.7079 0.3484 vn 0.9873 0.1398 -0.0755 vn -0.9995 0.0267 0.0124 vn 0.9779 -0.1336 -0.1604 vn -0.9660 -0.2525 -0.0552 vn -0.0163 0.9943 0.1053 vn -0.0438 0.9933 0.1068 vn 0.0311 0.9941 0.1034 vn 0.0784 0.9911 0.1075 vn 0.9903 0.0667 -0.1216 vn 0.9864 0.0718 -0.1479 vn -0.9952 0.0603 -0.0766 vn -0.9986 0.0348 -0.0406 vn -0.0098 -0.9895 -0.1438 vn -0.0098 -0.9895 -0.1439 vn -0.0081 -0.9884 -0.1514 vn 0.8285 -0.0534 0.5575 vn 0.1751 -0.1238 0.9767 vn -0.6633 -0.1615 0.7307 vn 0.9980 -0.0279 -0.0564 vn 0.7054 -0.0600 -0.7062 vn -0.1476 -0.1158 -0.9822 vn -0.8880 -0.1676 -0.4282 vn -0.9642 -0.1540 0.2156 vn 0.8321 -0.0872 0.5477 vn 0.1824 -0.1765 0.9672 vn -0.6357 -0.2123 0.7422 vn 0.9988 -0.0410 -0.0260 vn 0.7005 -0.0492 -0.7119 vn -0.0960 -0.1009 -0.9902 vn -0.8561 -0.1889 -0.4810 vn -0.9500 -0.2058 0.2347 vn 0.8354 -0.0230 0.5491 vn 0.2129 -0.1662 0.9628 vn -0.6209 -0.2031 0.7571 vn 0.9959 0.0898 0.0090 vn 0.6932 0.2000 -0.6924 vn -0.1296 0.2023 -0.9707 vn -0.8672 0.0488 -0.4956 vn -0.9589 -0.1257 0.2542 vn 0.5578 0.8200 -0.1283 vn -0.3067 0.9382 0.1602 vn -0.2646 0.9275 0.2640 vn 0.3538 0.8414 -0.4085 vn 0.4636 0.8056 0.3688 vn -0.3696 0.9282 -0.0433 vn 0.0360 0.7070 0.7062 vn -0.0649 0.9775 -0.2006 vn -0.4514 0.7279 0.5161 vn 0.2260 0.9671 -0.1168 vn -0.6267 0.7781 0.0426 vn 0.2509 0.9651 0.0752 vn -0.5250 0.8071 -0.2700 vn 0.1836 0.9705 0.1564 vn 0.0480 0.9255 0.3755 vn -0.1718 0.8630 -0.4750 vn -0.9321 0.2105 0.2948 vn -0.6008 0.1924 0.7758 vn -0.8687 0.3474 -0.3530 vn -0.1809 0.5031 -0.8451 vn 0.6545 0.4714 -0.5910 vn 0.9164 0.3983 0.0378 vn 0.7583 0.3540 0.5473 vn 0.1593 0.2625 0.9517 vn -0.9604 -0.0017 0.2784 vn -0.6481 -0.0459 0.7602 vn -0.9610 0.0396 0.2738 vn -0.6532 0.0267 0.7566 vn -0.9165 0.1694 -0.3624 vn -0.9272 0.1982 -0.3177 vn 0.9709 0.2383 -0.0220 vn 0.7237 0.3410 -0.6000 vn 0.9615 0.2732 -0.0290 vn 0.7596 0.3717 -0.5336 vn 0.8061 0.1566 0.5707 vn 0.8005 0.2079 0.5620 vn 0.1455 0.0126 0.9893 vn 0.1321 0.1030 0.9858 vn 0.4210 0.3782 -0.8244 vn 0.5070 0.4330 -0.7453 vn -0.6329 0.2678 -0.7264 vn -0.6678 0.3027 -0.6799 vn 0.8194 0.0042 0.5731 vn 0.2042 -0.1556 0.9665 vn -0.5818 -0.1815 0.7927 vn 0.9828 0.1584 0.0945 vn 0.7138 0.3758 -0.5909 vn -0.1448 0.4327 -0.8898 vn -0.9073 0.2036 -0.3680 vn -0.9447 -0.0626 0.3218 vn 0.5684 0.4212 -0.7067 vn 0.8292 0.3811 -0.4088 vn 0.6229 0.7278 -0.2867 vn 0.4379 0.7944 -0.4209 vn 0.6725 0.7303 -0.1198 vn 0.9261 0.3677 -0.0848 vn 0.6326 0.7729 0.0491 vn 0.9099 0.3658 0.1957 vn 0.6224 0.7252 0.2945 vn 0.8397 0.3773 0.3904 vn 0.4227 0.8023 0.4215 vn 0.7024 0.4112 0.5809 vn 0.2416 0.8125 0.5304 vn 0.4131 0.4433 0.7955 vn -0.0042 0.8036 0.5950 vn 0.0177 0.4490 0.8933 vn -0.1995 0.7997 0.5662 vn -0.3616 0.4600 0.8110 vn -0.4445 0.7648 0.4664 vn -0.6326 0.4611 0.6222 vn -0.5921 0.7521 0.2894 vn -0.8083 0.4348 0.3969 vn -0.6529 0.7562 0.0428 vn -0.9061 0.3968 0.1467 vn -0.6632 0.7307 -0.1617 vn -0.9214 0.3656 -0.1315 vn -0.6329 0.7220 -0.2795 vn -0.8258 0.3753 -0.4209 vn -0.4337 0.8022 -0.4103 vn -0.5758 0.4194 -0.7018 vn -0.1841 0.8154 -0.5488 vn -0.2334 0.4557 -0.8590 vn 0.0078 0.7958 -0.6054 vn 0.0132 0.4587 -0.8885 vn 0.1951 0.8080 -0.5560 vn 0.2450 0.4549 -0.8561 vn 0.2894 0.9051 -0.3114 vn 0.3992 0.8986 -0.1818 vn 0.4342 0.8998 -0.0419 vn 0.4264 0.9007 0.0834 vn 0.3884 0.9030 0.1838 vn 0.3197 0.9057 0.2783 vn 0.2018 0.9081 0.3669 vn 0.0685 0.9092 0.4106 vn -0.0517 0.9243 0.3781 vn -0.1844 0.9428 0.2777 vn -0.3140 0.9413 0.1234 vn -0.3878 0.9213 -0.0288 vn -0.4024 0.9057 -0.1333 vn -0.3835 0.8993 -0.2100 vn -0.2905 0.9044 -0.3124 vn -0.1374 0.9128 -0.3845 vn 0.0021 0.9184 -0.3956 vn 0.1396 0.9140 -0.3809 vn 0.0322 0.9994 -0.0120 vn 0.0033 0.5454 -0.8382 vn 0.0412 0.1898 -0.9809 vn -0.0090 0.3287 -0.9444 vn -0.4245 -0.9052 0.0175 vn -0.0551 0.3208 -0.9455 vn -0.0438 0.2972 -0.9538 vn 0.2574 -0.6565 -0.7090 vn 0.8623 0.0269 0.5057 vn 0.7951 0.3225 0.5136 vn 0.7586 0.5118 0.4032 vn 0.8786 0.1567 0.4510 vn 0.8159 0.2487 0.5219 vn -0.2534 -0.8652 0.4326 vn -0.8412 0.2313 0.4887 vn -0.8099 0.3698 0.4553 vn -0.7885 0.4400 0.4297 vn -0.7905 0.3883 0.4736 vn -0.8086 0.2466 0.5341 vn -0.0430 -0.1536 -0.9872 vn 0.1329 -0.6703 -0.7301 vn -0.1098 0.4881 -0.8658 vn 0.0657 0.5343 -0.8427 vn 0.0789 -0.1132 -0.9904 vn 0.0039 0.5423 -0.8402 vn 0.0079 -0.0543 -0.9985 vn -0.0684 0.5347 -0.8422 vn -0.1110 -0.0825 -0.9904 vn 0.0688 0.5040 -0.8610 vn -0.0165 -0.1200 -0.9926 vn -0.0827 -0.5025 -0.8606 vn 0.8057 -0.2191 0.5503 vn 0.6322 -0.6802 0.3709 vn 0.7025 0.4908 0.5154 vn 0.8809 -0.1507 0.4487 vn 0.7418 0.5519 0.3808 vn 0.8707 -0.0538 0.4888 vn 0.7161 0.5662 0.4081 vn 0.8087 -0.0862 0.5819 vn 0.7042 0.5523 0.4462 vn 0.8716 -0.1670 0.4609 vn 0.7978 0.4907 0.3503 vn 0.6875 -0.6447 0.3342 vn -0.8472 -0.1510 0.5094 vn -0.7342 -0.5202 0.4363 vn -0.7088 0.5038 0.4936 vn -0.7257 0.5610 0.3982 vn -0.8927 -0.0851 0.4425 vn -0.7082 0.5753 0.4092 vn -0.8581 -0.0227 0.5130 vn -0.7039 0.5624 0.4337 vn -0.8097 -0.0734 0.5822 vn -0.7851 0.5027 0.3617 vn -0.8606 -0.0916 0.5010 vn -0.6582 -0.5762 0.4845 vn 0.3752 0.7432 -0.5540 vn 0.2752 0.4698 -0.8388 vn 0.2696 0.4489 -0.8520 vn 0.3661 0.7237 -0.5849 vn 0.4506 0.8723 -0.1897 vn 0.4492 0.8709 -0.1991 vn 0.4726 0.8720 0.1269 vn 0.4727 0.8721 0.1265 vn 0.4491 0.7697 0.4536 vn 0.4515 0.7777 0.4374 vn 0.4055 0.6255 0.6665 vn 0.4045 0.6214 0.6710 vn 0.3404 0.4999 0.7964 vn 0.3471 0.5092 0.7875 vn 0.2132 0.3815 0.8994 vn 0.2123 0.3808 0.8999 vn 0.0750 0.2979 0.9516 vn 0.0760 0.2984 0.9514 vn -0.0333 0.2791 0.9597 vn -0.0280 0.2780 0.9601 vn -0.2097 0.2106 0.9548 vn -0.2110 0.2097 0.9547 vn -0.4812 0.0034 0.8766 vn -0.4667 0.0164 0.8842 vn -0.6958 -0.1962 0.6909 vn -0.6890 -0.1898 0.6994 vn -0.8328 -0.3222 0.4501 vn -0.8396 -0.3279 0.4331 vn -0.8966 -0.3772 0.2318 vn -0.8987 -0.3785 0.2212 vn -0.9227 -0.3854 -0.0028 vn -0.9228 -0.3852 -0.0077 vn -0.8657 -0.3755 -0.3309 vn -0.8532 -0.3742 -0.3632 vn -0.6545 -0.3737 -0.6572 vn -0.6833 -0.3721 -0.6281 vn -0.4339 -0.3700 -0.8215 vn -0.4162 -0.3687 -0.8311 vn -0.1782 -0.2616 -0.9486 vn -0.1764 -0.2602 -0.9493 vn -0.0415 -0.1176 -0.9922 vn -0.0414 -0.1171 -0.9922 vn -0.0217 -0.0444 -0.9988 vn -0.0217 -0.0443 -0.9988 vn 0.0586 0.0589 -0.9965 vn 0.0595 0.0598 -0.9964 vn 0.1792 0.2177 -0.9594 vn 0.1808 0.2205 -0.9585 vn -0.7294 0.6838 -0.0192 vn -0.7253 0.6884 0.0018 vn -0.7275 0.6861 -0.0025 vn -0.7275 0.6860 -0.0121 vn -0.7291 0.6843 0.0056 vn -0.7293 0.6842 0.0047 vn -0.7222 0.6903 0.0425 vn -0.7223 0.6904 0.0413 vn -0.7214 0.6920 0.0241 vn -0.7210 0.6926 0.0205 vn -0.7217 0.6919 0.0177 vn -0.7227 0.6908 0.0214 vn -0.7215 0.6920 0.0214 vn -0.7200 0.6937 0.0179 vn -0.7136 0.7005 0.0067 vn -0.7122 0.7019 0.0050 vn -0.7099 0.7043 0.0022 vn -0.7100 0.7041 0.0024 vn -0.7091 0.7050 0.0031 vn -0.7094 0.7048 0.0031 vn -0.7049 0.7092 0.0029 vn -0.7058 0.7084 0.0028 vn -0.7004 0.7137 0.0064 vn -0.7013 0.7128 0.0054 vn -0.7098 0.7043 -0.0104 vn -0.7074 0.7067 -0.0063 vn -0.7180 0.6955 -0.0250 vn -0.7210 0.6909 -0.0529 vn -0.7207 0.6914 -0.0502 vn -0.7248 0.6878 -0.0391 vn -0.7248 0.6878 -0.0393 vn -0.7253 0.6884 -0.0013 vn -0.7253 0.6884 -0.0014 vn -0.7234 0.6904 -0.0075 vn -0.7227 0.6911 -0.0096 vn -0.7214 0.6924 -0.0132 vn -0.7217 0.6921 -0.0124 vn -0.7266 0.6870 -0.0059 vn -0.7289 0.6846 -0.0034 vn -0.7299 0.6836 -0.0013 vn -0.7284 0.6850 -0.0023 vn -0.7234 0.6903 -0.0050 vn -0.7235 0.6902 -0.0050 vn -0.7275 0.6860 -0.0081 vn -0.7259 0.6877 -0.0072 vn -0.7315 0.6817 -0.0106 vn -0.7314 0.6818 -0.0104 vn 0.7236 -0.6902 -0.0032 vn 0.7229 -0.6909 0.0057 vn 0.7211 -0.6928 -0.0006 vn 0.7295 -0.6839 0.0085 vn 0.7258 -0.6878 0.0105 vn 0.7257 -0.6879 0.0103 vn 0.7237 -0.6897 -0.0227 vn 0.7236 -0.6897 -0.0275 vn 0.7226 -0.6895 -0.0487 vn 0.7226 -0.6895 -0.0482 vn 0.7207 -0.6921 -0.0394 vn 0.7194 -0.6937 -0.0340 vn 0.7120 -0.7020 -0.0128 vn 0.7087 -0.7054 -0.0054 vn 0.6996 -0.7144 0.0119 vn 0.6983 -0.7156 0.0134 vn 0.6951 -0.7187 0.0167 vn 0.6951 -0.7187 0.0166 vn 0.7041 -0.7099 0.0157 vn 0.7024 -0.7115 0.0157 vn 0.7296 -0.6834 0.0246 vn 0.7233 -0.6902 0.0214 vn 0.7175 -0.6965 0.0045 vn 0.7247 -0.6889 0.0124 vn 0.7061 -0.7080 -0.0058 vn 0.7053 -0.7089 -0.0071 vn 0.7143 -0.6996 0.0170 vn 0.7137 -0.7003 0.0146 vn 0.7179 -0.6955 0.0284 vn 0.7180 -0.6954 0.0288 vn 0.7180 -0.6959 0.0117 vn 0.7180 -0.6959 0.0121 vn 0.7168 -0.6972 0.0089 vn 0.7163 -0.6977 0.0121 vn 0.7144 -0.6993 0.0240 vn 0.7141 -0.6995 0.0248 vn 0.7131 -0.7005 0.0273 vn 0.7127 -0.7009 0.0281 vn 0.7186 -0.6951 0.0226 vn 0.7224 -0.6912 0.0186 vn 0.7251 -0.6885 0.0136 vn 0.7239 -0.6897 0.0142 vn 0.7211 -0.6926 0.0160 vn 0.7210 -0.6927 0.0160 vn 0.7315 -0.6815 0.0212 vn 0.7286 -0.6846 0.0195 vn 0.7373 -0.6751 0.0244 vn 0.7374 -0.6749 0.0246 vn -0.5643 0.8040 0.1874 vn -0.4281 0.8664 0.2569 vn -0.0947 0.8904 0.4452 vn -0.0349 0.9721 0.2318 vn 0.3772 0.8679 0.3231 vn 0.5387 0.8154 0.2117 vn -0.8703 0.2401 0.4299 vn -0.7758 0.5399 0.3265 vn -0.1450 0.7340 0.6634 vn -0.1390 0.3490 0.9267 vn 0.6682 0.5266 0.5255 vn 0.7534 0.1115 0.6481 vn -0.9857 0.0764 -0.1498 vn -0.9774 0.0400 -0.2074 vn -0.7255 0.0813 -0.6833 vn -0.6959 -0.0005 -0.7181 vn 0.1604 0.1024 -0.9817 vn 0.1401 0.0042 -0.9901 vn 0.9261 -0.0122 -0.3771 vn 0.8955 -0.0806 -0.4376 vn 0.9834 -0.0490 0.1744 vn 0.9898 -0.0967 0.1042 vn -0.9763 0.1522 0.1538 vn -0.7592 0.1627 0.6302 vn 0.5997 0.1582 0.7844 vn 0.9769 0.1285 0.1706 vn -0.1708 0.1190 0.9781 vn -0.7327 -0.1711 -0.6586 vn -0.9530 -0.1198 -0.2783 vn 0.1230 -0.2498 -0.9604 vn 0.9050 -0.2604 -0.3363 vn 0.9612 -0.2711 0.0510 vn -0.9768 -0.2119 0.0309 vn -0.7664 -0.4016 0.5014 vn 0.6627 -0.4289 0.6138 vn 0.9783 -0.2069 0.0067 vn -0.1814 -0.5018 0.8457 vn -0.0058 1.0000 0.0024 vn -0.0019 1.0000 0.0008 vn -0.0140 0.9991 0.0408 vn -0.0173 0.9977 0.0647 vn 0.0224 0.9996 0.0142 vn 0.0042 1.0000 0.0027 vn 0.0213 0.9966 0.0796 vn 0.0286 0.9987 0.0418 vn 0.0590 0.9951 0.0785 vn 0.0184 0.9995 0.0245 vn -0.0174 0.9997 0.0150 vn -0.1013 0.9910 0.0873 vn -0.7189 0.0279 -0.6945 vn -0.7201 0.0280 -0.6933 vn -0.9571 0.0645 -0.2824 vn -0.9626 0.0684 -0.2620 vn 0.1395 0.0000 -0.9902 vn 0.9302 -0.0125 -0.3669 vn 0.9322 -0.0126 -0.3615 vn 0.9971 -0.0141 0.0751 vn 0.9955 -0.0159 0.0926 vn -0.9860 -0.0695 0.1517 vn -0.9999 0.0103 0.0087 vn -0.8059 -0.0642 0.5885 vn -0.8051 -0.0640 0.5896 vn 0.6658 -0.0783 0.7420 vn 0.6672 -0.0785 0.7407 vn 0.9989 0.0388 -0.0261 vn 0.9853 -0.0678 0.1570 vn -0.1900 0.0000 0.9817 vn -0.7158 0.0000 -0.6983 vn -0.7170 0.0000 -0.6970 vn -0.9327 0.0000 -0.3606 vn 0.9310 0.0000 -0.3649 vn 0.9332 0.0000 -0.3594 vn 0.9881 0.0000 0.1535 vn -0.0033 -0.9999 -0.0127 vn -0.0027 -0.9999 -0.0103 vn -0.0023 -0.9999 -0.0089 vn -0.0954 0.0000 0.9954 vn 0.0056 -0.9974 -0.0721 vn 0.0046 -0.9981 -0.0606 vn -0.0012 -0.9999 0.0084 vn -0.0013 -0.9999 0.0089 vn -0.6696 0.6973 0.2557 vn -0.1028 0.8632 0.4942 vn 0.6143 0.7079 0.3484 vn -0.9873 0.1398 -0.0755 vn 0.9995 0.0267 0.0124 vn -0.9779 -0.1336 -0.1604 vn 0.9660 -0.2525 -0.0552 vn 0.0438 0.9933 0.1068 vn 0.0163 0.9943 0.1053 vn -0.0784 0.9911 0.1075 vn -0.0311 0.9941 0.1034 vn -0.9864 0.0718 -0.1479 vn -0.9903 0.0667 -0.1216 vn 0.9986 0.0348 -0.0406 vn 0.9952 0.0603 -0.0766 vn 0.0098 -0.9895 -0.1439 vn 0.0098 -0.9895 -0.1438 vn 0.0081 -0.9884 -0.1514 vn -0.1751 -0.1238 0.9767 vn -0.8285 -0.0534 0.5575 vn 0.6633 -0.1615 0.7307 vn -0.9980 -0.0279 -0.0564 vn -0.7054 -0.0600 -0.7062 vn 0.1476 -0.1158 -0.9822 vn 0.8880 -0.1676 -0.4282 vn 0.9642 -0.1540 0.2156 vn -0.1824 -0.1765 0.9672 vn -0.8321 -0.0872 0.5477 vn 0.6357 -0.2123 0.7422 vn -0.9988 -0.0410 -0.0260 vn -0.7005 -0.0492 -0.7119 vn 0.0960 -0.1009 -0.9902 vn 0.8561 -0.1889 -0.4810 vn 0.9500 -0.2058 0.2347 vn -0.2129 -0.1662 0.9628 vn -0.8354 -0.0230 0.5491 vn 0.6209 -0.2031 0.7571 vn -0.9959 0.0898 0.0090 vn -0.6932 0.2000 -0.6924 vn 0.1296 0.2023 -0.9707 vn 0.8672 0.0488 -0.4956 vn 0.9589 -0.1257 0.2542 vn -0.5578 0.8200 -0.1283 vn -0.3538 0.8414 -0.4085 vn 0.2646 0.9275 0.2640 vn 0.3067 0.9382 0.1602 vn 0.3696 0.9282 -0.0433 vn -0.4636 0.8056 0.3688 vn 0.0649 0.9775 -0.2006 vn -0.0360 0.7070 0.7062 vn -0.2260 0.9671 -0.1168 vn 0.4514 0.7279 0.5161 vn -0.2509 0.9651 0.0752 vn 0.6267 0.7781 0.0426 vn -0.1836 0.9705 0.1564 vn 0.5250 0.8071 -0.2700 vn 0.1718 0.8630 -0.4750 vn -0.0480 0.9255 0.3755 vn 0.6008 0.1924 0.7758 vn 0.9321 0.2105 0.2948 vn 0.8687 0.3474 -0.3530 vn 0.1809 0.5031 -0.8451 vn -0.6545 0.4714 -0.5910 vn -0.9164 0.3983 0.0378 vn -0.7583 0.3540 0.5473 vn -0.1593 0.2625 0.9517 vn 0.6481 -0.0459 0.7602 vn 0.9604 -0.0017 0.2784 vn 0.6532 0.0267 0.7566 vn 0.9610 0.0396 0.2738 vn 0.9165 0.1694 -0.3624 vn 0.9272 0.1982 -0.3177 vn -0.7237 0.3410 -0.6000 vn -0.9709 0.2383 -0.0220 vn -0.7596 0.3717 -0.5336 vn -0.9615 0.2732 -0.0290 vn -0.8061 0.1566 0.5707 vn -0.8005 0.2079 0.5620 vn -0.1455 0.0126 0.9893 vn -0.1321 0.1030 0.9858 vn -0.4210 0.3782 -0.8244 vn -0.5070 0.4330 -0.7453 vn 0.6329 0.2678 -0.7264 vn 0.6678 0.3027 -0.6799 vn -0.2042 -0.1556 0.9665 vn -0.8194 0.0042 0.5731 vn 0.5818 -0.1815 0.7927 vn -0.9828 0.1584 0.0945 vn -0.7138 0.3758 -0.5909 vn 0.1448 0.4327 -0.8898 vn 0.9073 0.2036 -0.3680 vn 0.9447 -0.0626 0.3218 vn -0.6501 -0.0552 0.7578 vn -0.6162 -0.0574 0.7855 vn -0.9896 -0.0777 0.1207 vn -0.9904 -0.0789 0.1137 vn -0.8206 -0.2037 -0.5339 vn -0.8253 -0.2023 -0.5272 vn -0.2995 -0.2358 -0.9244 vn -0.2933 -0.2353 -0.9266 vn -0.1039 -0.0634 0.9926 vn -0.1046 -0.0635 0.9925 vn 0.0000 -0.2035 -0.9791 vn 0.6501 -0.0552 0.7578 vn 0.9904 -0.0789 0.1137 vn 0.9896 -0.0777 0.1207 vn 0.6162 -0.0574 0.7855 vn 0.8253 -0.2023 -0.5272 vn 0.8206 -0.2037 -0.5339 vn 0.2933 -0.2353 -0.9266 vn 0.2995 -0.2358 -0.9244 vn 0.1039 -0.0634 0.9926 vn 0.1046 -0.0635 0.9925 vn 0.5867 0.4925 -0.6428 vn 0.6344 0.5441 -0.5489 vn 0.3189 0.0218 -0.9475 vn 0.2481 -0.0747 -0.9658 vn -0.5290 -0.8139 -0.2402 vn -0.5352 -0.8351 -0.1272 vn -0.2644 -0.6963 -0.6672 vn -0.2163 -0.6168 -0.7568 vn -0.4068 0.0869 0.9093 vn -0.5131 -0.0359 0.8575 vn 0.1113 0.6099 0.7845 vn 0.2069 0.6956 0.6880 vn -0.8217 -0.4729 0.3181 vn -0.8436 -0.5214 0.1282 vn -0.7674 -0.0267 0.6405 vn -0.6554 -0.0763 0.7514 vn 0.6539 0.7285 0.2041 vn 0.6378 0.7485 0.1815 vn 0.0350 0.5455 0.8374 vn 0.0019 0.6154 0.7882 vn 0.6195 0.7823 0.0655 vn 0.4573 0.1525 -0.8761 vn 0.5605 0.2968 -0.7731 vn -0.0270 -0.1694 -0.9852 vn -0.1560 -0.4219 -0.8931 vn -0.6250 -0.5332 -0.5701 vn -0.6080 -0.6690 -0.4275 vn -0.7595 -0.6503 0.0143 vn -0.7697 -0.6314 0.0938 vn -0.7246 -0.6873 -0.0500 vn -0.7741 -0.6307 -0.0551 vn -0.6974 -0.6462 0.3099 vn -0.6403 -0.7607 0.1061 vn -0.7103 -0.4686 0.5253 vn -0.6522 -0.6209 0.4348 vn -0.6191 -0.7507 0.2304 vn 0.7121 0.5679 -0.4128 vn 0.7577 0.5658 -0.3251 vn -0.6536 -0.4130 0.6343 vn 0.6214 0.7812 -0.0602 vn -0.5867 0.4925 -0.6428 vn -0.2481 -0.0747 -0.9658 vn -0.3189 0.0218 -0.9475 vn -0.6344 0.5441 -0.5489 vn 0.5291 -0.8140 -0.2398 vn 0.2163 -0.6169 -0.7567 vn 0.2644 -0.6964 -0.6671 vn 0.5351 -0.8352 -0.1268 vn 0.4069 0.0867 0.9093 vn -0.2069 0.6956 0.6880 vn -0.1113 0.6099 0.7846 vn 0.5132 -0.0362 0.8574 vn 0.8217 -0.4729 0.3179 vn 0.6556 -0.0766 0.7512 vn 0.7675 -0.0268 0.6404 vn 0.8437 -0.5213 0.1281 vn -0.6539 0.7285 0.2041 vn -0.0018 0.6154 0.7882 vn -0.0350 0.5455 0.8374 vn -0.6378 0.7485 0.1815 vn -0.6195 0.7823 0.0655 vn -0.4573 0.1525 -0.8761 vn 0.1560 -0.4221 -0.8930 vn 0.0272 -0.1697 -0.9851 vn -0.5604 0.2969 -0.7731 vn 0.6079 -0.6692 -0.4272 vn 0.6247 -0.5335 -0.5702 vn 0.7595 -0.6503 0.0144 vn 0.7739 -0.6308 -0.0553 vn 0.7247 -0.6873 -0.0499 vn 0.7699 -0.6312 0.0938 vn 0.6403 -0.7607 0.1062 vn 0.6973 -0.6463 0.3099 vn 0.7102 -0.4689 0.5251 vn 0.6188 -0.7509 0.2306 vn 0.6519 -0.6212 0.4348 vn -0.7121 0.5679 -0.4128 vn -0.7577 0.5658 -0.3251 vn 0.6534 -0.4133 0.6342 vn -0.6214 0.7812 -0.0602 vn 0.0000 0.5914 0.8064 vn 0.0316 0.5911 0.8060 vn 0.0308 0.4554 0.8897 vn 0.0000 0.4364 0.8997 vn -0.0308 0.4554 0.8897 vn -0.0316 0.5911 0.8060 vn 0.0053 0.2001 0.9797 vn 0.0000 0.1821 0.9832 vn -0.0053 0.2001 0.9797 vn -0.0293 0.0845 0.9960 vn 0.0000 0.0840 0.9965 vn 0.0293 0.0845 0.9960 vn -0.0337 0.0768 0.9965 vn 0.0000 0.0768 0.9970 vn 0.0337 0.0768 0.9965 vn -0.2639 -0.9229 0.2804 vn -0.5502 -0.7853 0.2838 vn -0.5543 -0.8025 0.2206 vn -0.2597 -0.9390 0.2256 vn -0.6224 -0.7773 0.0913 vn -0.8156 -0.2854 0.5033 vn -0.4764 -0.8671 0.1454 vn -0.2137 -0.9079 0.3605 vn -0.2650 -0.9136 0.3082 vn -0.5307 -0.8193 0.2169 vn -0.3614 -0.4355 0.8245 vn 0.6115 0.6158 0.4967 vn 0.9253 0.3726 0.0707 vn -0.3077 -0.8602 -0.4066 vn -0.5429 -0.2364 0.8058 vn -0.4774 -0.7141 -0.5120 vn 0.5385 0.6378 0.5506 vn -0.5263 -0.2101 0.8239 vn 0.5362 0.6596 0.5267 vn -0.5197 -0.3168 0.7934 vn -0.3943 -0.4563 0.7976 vn -0.4687 -0.7263 -0.5027 vn 0.6151 0.1681 -0.7703 vn 0.6056 0.1588 -0.7797 vn 0.6847 0.2040 -0.6996 vn 0.9241 0.3363 -0.1812 vn -0.2064 -0.9190 -0.3359 vn -0.3505 -0.8126 -0.4655 vn -0.6384 -0.1511 0.7546 vn -0.6010 -0.2153 0.7696 vn -0.3800 -0.8005 -0.4635 vn -0.6182 -0.1377 0.7738 vn 0.8422 0.0462 -0.5371 vn 0.7189 0.0910 -0.6891 vn 0.6671 0.1120 -0.7365 vn 0.6074 0.4649 0.6440 vn 0.9569 0.1108 0.2682 vn 0.1561 -0.9810 -0.1152 vn 0.4081 0.5417 0.7348 vn 0.7953 0.6057 0.0260 vn 0.7091 0.6996 0.0873 vn -0.1219 0.7312 0.6711 vn 0.0861 0.7418 0.6650 vn -0.8705 -0.2709 0.4108 vn -0.1397 0.7839 0.6049 vn 0.9502 0.2931 -0.1055 vn -0.8876 -0.3498 0.2996 vn 0.6767 0.7203 0.1526 vn 0.2139 -0.8716 -0.4410 vn 0.8944 0.3965 -0.2070 vn 0.2764 -0.8589 -0.4311 vn 0.8778 0.4236 -0.2235 vn 0.3988 -0.8937 -0.2055 vn 0.8715 0.3520 0.3414 vn 0.5563 -0.7154 0.4228 vn 0.4584 0.6602 0.5950 vn 0.4427 0.7237 0.5294 vn 0.6466 0.1100 -0.7548 vn -0.3852 -0.8051 -0.4510 vn 0.4280 0.7653 0.4806 vn 0.4386 0.7680 0.4667 vn -0.3182 -0.8583 -0.4025 vn 0.7022 0.0591 -0.7095 vn -0.5268 -0.1932 0.8277 vn -0.5936 -0.1400 0.7924 vn 0.6956 0.4361 -0.5709 vn 0.7708 0.4104 -0.4872 vn 0.3933 0.0489 -0.9181 vn 0.3017 0.0102 -0.9533 vn 0.3482 0.2615 -0.9002 vn 0.7546 0.4969 0.4285 vn 0.7400 0.4853 0.4657 vn 0.3758 0.2801 -0.8833 vn -0.3462 -0.2911 0.8918 vn -0.3428 -0.3327 0.8785 vn 0.4376 0.1417 -0.8879 vn -0.4476 -0.7287 -0.5182 vn -0.3621 -0.7150 -0.5980 vn 0.3710 0.2472 -0.8951 vn -0.6496 -0.6094 -0.4545 vn -0.7330 -0.5146 -0.4448 vn -0.6915 -0.5415 -0.4781 vn -0.5542 -0.6909 -0.4642 vn 0.4561 0.2024 -0.8666 vn 0.4927 0.2234 -0.8410 vn -0.5438 -0.6721 -0.5025 vn 0.4856 0.2306 -0.8432 vn -0.3897 -0.8233 -0.4127 vn 0.5302 0.2436 -0.8121 vn -0.5614 -0.6305 -0.5359 vn 0.6357 0.6197 0.4602 vn 0.6257 0.5990 0.4997 vn -0.3981 -0.2807 0.8733 vn -0.4138 -0.2854 0.8644 vn -0.2250 -0.4896 0.8424 vn 0.7202 0.5854 0.3723 vn 0.6359 0.5551 0.5361 vn 0.6458 0.5111 0.5672 vn 0.5900 0.1849 -0.7859 vn -0.4275 -0.2898 0.8563 vn -0.4019 -0.3358 0.8519 vn 0.8694 0.2119 -0.4463 vn -0.5067 -0.6875 -0.5202 vn 0.0260 -0.6685 0.7432 vn -0.0347 -0.9581 -0.2843 vn 0.8344 -0.0024 0.5511 vn 0.9019 0.3363 0.2710 vn -0.2236 -0.5208 0.8238 vn -0.2998 0.8627 0.4071 vn -0.2232 0.8304 0.5104 vn -0.8625 -0.4081 0.2992 vn -0.7765 -0.4543 0.4365 vn -0.0961 0.6693 0.7367 vn -0.3810 -0.3719 0.8465 vn 0.7649 0.4133 0.4940 vn 0.9079 -0.2552 0.3326 vn 0.6127 -0.2835 -0.7377 vn 0.4411 0.2565 -0.8600 vn 0.0121 -0.7999 0.6000 vn -0.2419 -0.8693 -0.4310 vn -0.3346 -0.3858 0.8597 vn -0.3572 -0.3238 0.8761 vn -0.6850 -0.5326 -0.4971 vn -0.6569 -0.5612 -0.5034 vn 0.7223 0.4910 0.4869 vn 0.8177 -0.2963 -0.4935 vn 0.7985 0.0944 0.5945 vn 0.5864 0.6405 0.4959 vn -0.5138 -0.1709 0.8407 vn -0.0509 -0.5529 0.8317 vn -0.5367 -0.1576 0.8289 vn 0.5527 0.6792 0.4830 vn -0.0105 -0.9829 -0.1836 vn 0.8447 -0.3419 -0.4117 vn -0.4639 -0.7522 -0.4679 vn 0.6364 0.0752 -0.7677 vn -0.4995 -0.7101 -0.4963 vn 0.5879 0.1422 -0.7963 vn 0.1442 -0.9864 -0.0787 vn 0.9043 -0.3073 -0.2962 vn -0.0049 -0.5033 0.8641 vn 0.7508 0.2203 0.6226 vn 0.4887 0.7214 0.4907 vn 0.1530 -0.0386 -0.9875 vn -0.5243 -0.5828 -0.6207 vn -0.6027 -0.7866 0.1343 vn -0.6928 -0.7179 0.0685 vn -0.6119 -0.7715 0.1744 vn -0.5510 -0.7897 0.2697 vn 0.8391 0.4838 -0.2485 vn 0.7673 0.5777 -0.2782 vn 0.6330 0.3985 -0.6636 vn 0.7683 0.5821 -0.2662 vn -0.4094 -0.6659 -0.6236 vn 0.4010 0.2994 -0.8657 vn 0.4268 0.7456 0.5117 vn 0.2639 -0.9228 0.2804 vn 0.2598 -0.9389 0.2255 vn 0.5544 -0.8024 0.2207 vn 0.5506 -0.7848 0.2842 vn 0.8152 -0.2862 0.5034 vn 0.6218 -0.7778 0.0916 vn 0.2129 -0.9077 0.3616 vn 0.4767 -0.8668 0.1462 vn 0.2655 -0.9135 0.3081 vn 0.5317 -0.8186 0.2171 vn -0.9253 0.3724 0.0711 vn -0.6123 0.6149 0.4969 vn 0.3612 -0.4361 0.8242 vn 0.3077 -0.8603 -0.4064 vn 0.5428 -0.2366 0.8058 vn 0.4769 -0.7141 -0.5123 vn -0.5385 0.6377 0.5507 vn -0.5362 0.6595 0.5267 vn 0.5264 -0.2105 0.8237 vn 0.3954 -0.4557 0.7974 vn 0.5199 -0.3167 0.7933 vn 0.4686 -0.7263 -0.5028 vn -0.6057 0.1588 -0.7797 vn -0.6152 0.1679 -0.7703 vn -0.6851 0.2033 -0.6995 vn -0.9241 0.3362 -0.1818 vn 0.2062 -0.9192 -0.3355 vn 0.6011 -0.2154 0.7696 vn 0.6386 -0.1517 0.7544 vn 0.3498 -0.8126 -0.4661 vn 0.6185 -0.1382 0.7735 vn 0.3794 -0.8003 -0.4642 vn -0.7190 0.0909 -0.6890 vn -0.8430 0.0452 -0.5359 vn -0.6671 0.1121 -0.7365 vn -0.1583 -0.9808 -0.1137 vn -0.9562 0.1124 0.2702 vn -0.6069 0.4659 0.6439 vn -0.4079 0.5414 0.7351 vn -0.7093 0.6995 0.0865 vn -0.7952 0.6059 0.0254 vn 0.1229 0.7307 0.6715 vn -0.0854 0.7421 0.6648 vn 0.8695 -0.2719 0.4122 vn -0.9511 0.2907 -0.1040 vn 0.1302 0.7873 0.6027 vn 0.8883 -0.3482 0.2993 vn -0.6765 0.7206 0.1521 vn -0.2160 -0.8713 -0.4405 vn -0.2756 -0.8593 -0.4307 vn -0.8955 0.3946 -0.2054 vn -0.3988 -0.8936 -0.2057 vn -0.8779 0.4227 -0.2249 vn -0.5557 -0.7164 0.4218 vn -0.8726 0.3504 0.3404 vn -0.4432 0.7231 0.5298 vn -0.4579 0.6601 0.5954 vn 0.3848 -0.8050 -0.4515 vn -0.6464 0.1101 -0.7550 vn -0.4385 0.7681 0.4666 vn -0.4281 0.7654 0.4805 vn 0.3176 -0.8581 -0.4034 vn -0.7007 0.0593 -0.7109 vn 0.5939 -0.1405 0.7922 vn 0.5270 -0.1937 0.8274 vn -0.6956 0.4361 -0.5708 vn -0.3016 0.0102 -0.9533 vn -0.3933 0.0490 -0.9181 vn -0.7708 0.4105 -0.4872 vn -0.3479 0.2611 -0.9004 vn -0.3755 0.2799 -0.8835 vn -0.7402 0.4855 0.4652 vn -0.7548 0.4970 0.4281 vn 0.3421 -0.3322 0.8789 vn 0.3449 -0.2912 0.8923 vn 0.3622 -0.7150 -0.5979 vn 0.4474 -0.7287 -0.5184 vn -0.4377 0.1419 -0.8878 vn 0.6492 -0.6101 -0.4541 vn -0.3709 0.2469 -0.8952 vn 0.7325 -0.5158 -0.4442 vn 0.6916 -0.5416 -0.4778 vn 0.5539 -0.6911 -0.4642 vn 0.5438 -0.6719 -0.5027 vn -0.4926 0.2233 -0.8411 vn -0.4561 0.2022 -0.8666 vn -0.4856 0.2307 -0.8432 vn 0.3897 -0.8233 -0.4126 vn 0.5614 -0.6305 -0.5360 vn -0.5299 0.2437 -0.8123 vn -0.6257 0.5990 0.4997 vn -0.6358 0.6196 0.4602 vn 0.4139 -0.2854 0.8644 vn 0.3979 -0.2809 0.8733 vn -0.7202 0.5854 0.3722 vn 0.2251 -0.4899 0.8422 vn -0.6357 0.5553 0.5361 vn -0.5889 0.1855 -0.7866 vn -0.6452 0.5120 0.5670 vn 0.4025 -0.3360 0.8515 vn 0.4278 -0.2901 0.8560 vn -0.8694 0.2122 -0.4462 vn 0.5063 -0.6872 -0.5209 vn -0.0243 -0.6686 0.7432 vn 0.0365 -0.9579 -0.2848 vn -0.8351 -0.0030 0.5500 vn 0.2230 -0.5209 0.8239 vn -0.9019 0.3365 0.2708 vn 0.2965 0.8639 0.4070 vn 0.7761 -0.4523 0.4394 vn 0.8639 -0.4058 0.2982 vn 0.2248 0.8299 0.5106 vn 0.0866 0.6696 0.7376 vn 0.3829 -0.3690 0.8468 vn -0.7647 0.4137 0.4939 vn -0.4406 0.2567 -0.8602 vn -0.6118 -0.2834 -0.7385 vn -0.9082 -0.2551 0.3317 vn 0.2425 -0.8691 -0.4310 vn -0.0120 -0.7999 0.6000 vn 0.3356 -0.3854 0.8596 vn 0.6579 -0.5605 -0.5029 vn 0.6861 -0.5315 -0.4967 vn 0.3569 -0.3221 0.8768 vn -0.7226 0.4910 0.4865 vn -0.8172 -0.2966 -0.4942 vn -0.7996 0.0939 0.5932 vn 0.0532 -0.5525 0.8318 vn 0.5142 -0.1712 0.8404 vn -0.5850 0.6419 0.4957 vn 0.5370 -0.1578 0.8287 vn -0.5525 0.6792 0.4831 vn 0.0115 -0.9828 -0.1844 vn -0.8449 -0.3413 -0.4118 vn 0.4641 -0.7520 -0.4680 vn -0.6347 0.0760 -0.7690 vn -0.5879 0.1423 -0.7963 vn 0.4989 -0.7098 -0.4972 vn -0.1428 -0.9865 -0.0794 vn -0.9038 -0.3078 -0.2973 vn 0.0068 -0.5033 0.8640 vn -0.7525 0.2197 0.6209 vn -0.4883 0.7223 0.4897 vn 0.5241 -0.5827 -0.6211 vn -0.1529 -0.0387 -0.9875 vn 0.6023 -0.7867 0.1349 vn 0.6928 -0.7179 0.0681 vn 0.5510 -0.7895 0.2705 vn 0.6120 -0.7714 0.1743 vn -0.8389 0.4840 -0.2489 vn -0.6330 0.3985 -0.6636 vn -0.7673 0.5777 -0.2782 vn -0.7683 0.5821 -0.2662 vn 0.4094 -0.6656 -0.6239 vn -0.4008 0.2993 -0.8659 vn -0.4268 0.7455 0.5118 vn 0.7582 0.2197 0.6139 vn 0.7683 0.2270 0.5985 vn 0.7289 0.3504 0.5882 vn 0.6271 0.4322 0.6480 vn 0.2808 -0.7982 0.5330 vn 0.2296 -0.3707 0.8999 vn 0.3005 -0.3598 0.8833 vn 0.5432 -0.6083 0.5787 vn 0.2564 -0.7570 0.6009 vn 0.4443 -0.2793 0.8512 vn 0.3707 -0.6150 0.6959 vn 0.6432 -0.2588 0.7206 vn 0.4973 -0.4428 0.7460 vn 0.7764 -0.2221 0.5898 vn 0.6035 -0.1940 0.7734 vn 0.8323 -0.1104 0.5431 vn 0.4249 0.2239 0.8771 vn 0.6032 0.1015 0.7911 vn 0.7829 0.1171 0.6110 vn 0.5765 0.2389 0.7814 vn 0.3123 0.2911 0.9043 vn 0.4449 0.1368 0.8850 vn 0.4106 0.4375 0.8000 vn 0.5586 0.3086 0.7699 vn 0.5984 0.3789 0.7059 vn 0.6377 0.4165 0.6480 vn -0.0012 0.4888 0.8724 vn -0.0018 0.5252 0.8509 vn 0.6285 0.4140 0.6585 vn 0.7646 0.3588 0.5354 vn -0.0019 0.5069 0.8620 vn 0.5952 0.4053 0.6939 vn 0.7822 0.3161 0.5369 vn -0.0000 0.4783 0.8782 vn 0.5764 -0.0305 0.8166 vn 0.6335 -0.0656 0.7709 vn 0.6856 -0.1623 0.7096 vn 0.5863 -0.1444 0.7971 vn 0.7228 0.2496 0.6443 vn 0.5743 0.0604 0.8164 vn 0.8457 0.1133 0.5214 vn 0.6987 -0.1054 0.7076 vn 0.5543 -0.0240 0.8320 vn 0.5129 -0.0626 0.8561 vn 0.2505 0.2151 0.9439 vn 0.1112 0.0934 0.9894 vn 0.5249 0.2817 0.8032 vn 0.7906 0.2004 0.5786 vn 0.9182 0.0382 0.3941 vn 0.1079 0.0482 0.9930 vn 0.1982 -0.1280 0.9717 vn -0.0016 0.0508 0.9987 vn -0.0029 -0.0709 0.9975 vn 0.6447 0.0479 0.7629 vn 0.5792 0.0915 0.8100 vn 0.6718 0.0253 0.7403 vn 0.7821 -0.0792 0.6181 vn 0.4368 -0.0073 0.8995 vn 0.0018 -0.0942 0.9955 vn -0.0003 0.3992 0.9168 vn 0.4355 0.3392 0.8338 vn 0.6934 -0.1085 0.7123 vn 0.6929 -0.0406 0.7198 vn 0.8389 -0.1663 0.5182 vn 0.8260 -0.1741 0.5361 vn 0.7719 -0.1557 0.6163 vn 0.6703 -0.1058 0.7344 vn 0.3185 0.2238 0.9211 vn 0.4400 -0.0919 0.8932 vn 0.4340 -0.5919 0.6791 vn 0.7140 -0.4843 0.5055 vn 0.8408 -0.4059 0.3582 vn 0.4530 -0.8890 0.0665 vn 0.5708 -0.8210 0.0130 vn 0.5992 -0.8000 0.0306 vn 0.4465 -0.8902 0.0908 vn 0.2340 -0.9614 0.1449 vn 0.2551 -0.9553 0.1496 vn 0.8333 0.0587 0.5496 vn 0.6356 0.1060 0.7647 vn 0.6740 -0.1415 0.7250 vn 0.7595 -0.1903 0.6220 vn 0.4297 0.0634 0.9008 vn -0.0018 0.1940 0.9810 vn 0.0011 0.1267 0.9919 vn 0.4440 0.1291 0.8867 vn 0.2810 0.8336 0.4756 vn 0.0007 0.8745 0.4850 vn 0.2239 -0.1651 0.9605 vn 0.4341 0.5425 0.7192 vn 0.5389 0.0443 0.8412 vn 0.4574 -0.1100 0.8824 vn 0.5737 -0.1514 0.8049 vn 0.6222 -0.0359 0.7820 vn 0.4025 -0.3061 0.8627 vn 0.0014 -0.3820 0.9242 vn 0.6379 0.1158 0.7613 vn 0.5120 0.6898 0.5118 vn 0.1267 -0.7389 0.6618 vn 0.4449 -0.2964 0.8451 vn 0.5599 -0.3430 0.7542 vn 0.2820 -0.7669 0.5764 vn 0.1836 -0.8027 0.5674 vn 0.3493 -0.4081 0.8435 vn 0.0005 -0.4352 0.9003 vn 0.0002 -0.8286 0.5598 vn 0.2763 -0.7842 0.5555 vn 0.5560 -0.3713 0.7436 vn 0.5989 -0.1033 0.7941 vn 0.6156 -0.0436 0.7869 vn 0.6388 -0.0931 0.7637 vn 0.6008 -0.1194 0.7904 vn 0.6345 0.1051 0.7657 vn 0.5628 0.5589 0.6089 vn 0.5777 0.0794 0.8123 vn 0.3768 0.0939 0.9215 vn 0.4074 0.2368 0.8820 vn 0.6372 0.0876 0.7656 vn 0.0008 0.0973 0.9952 vn 0.4223 0.0628 0.9042 vn -0.0011 0.2424 0.9702 vn 0.6432 0.0412 0.7646 vn -0.0016 0.0902 0.9959 vn 0.8080 -0.2907 0.5124 vn 0.6423 -0.4734 0.6028 vn 0.8296 -0.0756 0.5532 vn 0.4301 -0.2327 0.8723 vn 0.5863 -0.1966 0.7859 vn 0.5485 0.0652 0.8336 vn 0.4421 -0.2856 0.8502 vn 0.7363 -0.3884 0.5541 vn -0.0065 -0.6837 0.7297 vn -0.0013 0.0307 0.9995 vn 0.6607 -0.0385 0.7496 vn 0.5651 -0.0099 0.8250 vn 0.3843 -0.1113 0.9164 vn 0.0025 -0.2507 0.9680 vn 0.0001 0.2142 0.9768 vn 0.0003 -0.1929 0.9812 vn 0.0005 -0.6541 0.7564 vn 0.0002 -0.9863 0.1646 vn -0.0004 -0.9821 0.1881 vn 0.9288 -0.3647 0.0655 vn 0.9420 -0.3108 0.1264 vn 0.9825 -0.0652 0.1742 vn 0.9540 -0.2391 0.1807 vn 0.6856 -0.7273 0.0310 vn 0.6975 -0.7165 -0.0093 vn 0.7934 -0.6047 0.0697 vn 0.7778 -0.6280 -0.0254 vn 0.8989 -0.4382 -0.0031 vn 0.8698 -0.4851 -0.0900 vn 0.8912 -0.3417 0.2982 vn 0.5824 -0.0899 0.8079 vn 0.8924 -0.2492 0.3761 vn 0.8615 -0.2203 0.4574 vn 0.8052 -0.2293 0.5468 vn 0.7174 -0.2016 0.6669 vn 0.9355 -0.0226 0.3527 vn 0.8004 -0.0605 0.5963 vn 0.9296 -0.0194 0.3680 vn 0.9355 0.0184 0.3527 vn 0.8829 -0.1617 0.4407 vn 0.9070 -0.2055 0.3676 vn 0.9589 -0.1118 0.2609 vn 0.9661 -0.1306 0.2226 vn 0.9134 -0.2235 0.3400 vn 0.9673 -0.1345 0.2147 vn -0.0077 -0.9073 0.4205 vn 0.5738 -0.7885 0.2210 vn 0.6131 -0.5511 0.5659 vn -0.0053 -0.5700 0.8216 vn 0.6828 -0.7227 -0.1070 vn 0.6583 -0.7222 0.2121 vn 0.3005 -0.9102 0.2849 vn -0.0864 -0.8240 0.5599 vn 0.0508 -0.1701 0.9841 vn 0.4794 -0.8242 -0.3014 vn 0.3572 -0.9297 -0.0893 vn -0.1660 -0.9697 -0.1791 vn 0.1188 -0.9566 -0.2658 vn 0.9348 0.0031 0.3550 vn 0.9108 -0.4120 -0.0250 vn 0.6838 -0.7113 0.1628 vn 0.6259 0.2243 0.7469 vn 0.8694 -0.4674 0.1600 vn 0.8431 -0.5220 -0.1293 vn 0.1321 -0.7737 0.6196 vn 0.7475 -0.3038 0.5907 vn -0.4339 -0.8863 0.1620 vn -0.0095 -0.9611 0.2760 vn 0.4239 0.5181 0.7428 vn 0.6842 0.2856 0.6710 vn 0.4343 -0.5007 0.7488 vn 0.1718 -0.6532 0.7374 vn 0.1506 -0.6232 0.7674 vn 0.3354 -0.4684 0.8173 vn 0.4507 -0.3902 0.8028 vn 0.4709 -0.2305 0.8515 vn 0.4901 0.2327 0.8400 vn 0.3445 0.2478 0.9055 vn 0.2073 0.3290 0.9213 vn 0.2049 0.4727 0.8570 vn 0.5107 0.0732 0.8566 vn 0.6915 -0.1222 0.7119 vn 0.0887 0.8676 0.4893 vn 0.1953 0.8434 0.5004 vn 0.2855 0.7825 0.5534 vn 0.6216 0.4313 0.6539 vn 0.6761 -0.2232 0.7021 vn 0.4150 -0.6373 0.6492 vn 0.2639 -0.7886 0.5554 vn 0.1015 -0.8883 0.4479 vn 0.0315 -0.8718 0.4888 vn -0.0269 -0.7701 0.6373 vn -0.0586 -0.4636 0.8841 vn -0.0179 0.1928 0.9810 vn -0.0081 0.7203 0.6935 vn -0.0016 0.8168 0.5769 vn 0.5870 -0.7756 0.2319 vn 0.4189 -0.9076 -0.0290 vn 0.0971 -0.9887 -0.1137 vn -0.2621 -0.9584 -0.1130 vn -0.5486 -0.8301 0.0992 vn -0.7003 -0.4384 0.5633 vn -0.5495 0.8342 0.0453 vn -0.3517 0.9142 -0.2010 vn -0.0664 0.9420 -0.3288 vn 0.2329 0.9147 -0.3301 vn 0.4197 0.9076 0.0074 vn -0.7428 0.3618 0.5633 vn 0.6023 -0.6748 0.4264 vn 0.4478 -0.8006 0.3981 vn 0.1610 -0.8795 0.4477 vn -0.1231 -0.8754 0.4674 vn -0.3728 -0.7714 0.5156 vn -0.4050 -0.3814 0.8310 vn -0.4878 0.7882 0.3751 vn -0.2984 0.8819 0.3650 vn -0.0045 0.9598 0.2805 vn 0.3976 0.9027 0.1640 vn 0.6170 0.7787 0.1132 vn -0.4851 0.2970 0.8224 vn 0.5795 -0.3055 0.7555 vn 0.4514 -0.3508 0.8205 vn -0.0685 0.2510 0.9655 vn 0.1788 -0.3938 0.9016 vn -0.0384 -0.3904 0.9198 vn 0.1355 0.2574 0.9567 vn 0.4353 0.2475 0.8656 vn 0.6648 0.3251 0.6726 vn 0.4090 0.3750 0.8319 vn 0.2337 0.3243 0.9166 vn 0.6208 0.3724 0.6898 vn 0.8311 0.2465 0.4984 vn 0.9360 0.0803 0.3425 vn 0.1045 0.2871 0.9522 vn -0.0005 0.2781 0.9605 vn 0.9784 -0.0448 0.2019 vn 0.9702 -0.0006 0.2425 vn 0.9619 0.0069 0.2731 vn 0.9825 -0.0949 0.1599 vn 0.9854 -0.1102 0.1298 vn 0.9915 -0.0888 0.0943 vn 0.9880 -0.1543 -0.0030 vn 0.9150 -0.1765 0.3627 vn 0.9299 -0.0402 0.3656 vn 0.9738 0.0487 0.2221 vn 0.8012 -0.2927 0.5219 vn 0.9819 -0.0320 0.1866 vn 0.8314 -0.0290 0.5549 vn 0.6979 0.0679 0.7129 vn 0.9319 0.0355 0.3608 vn -0.2330 0.7887 0.5688 vn -0.1733 0.8108 0.5591 vn -0.0406 0.7854 0.6177 vn -0.1187 0.7190 0.6848 vn 0.2815 0.5347 0.7967 vn 0.1209 0.4666 0.8761 vn 0.6155 -0.0837 0.7837 vn 0.4122 -0.1155 0.9037 vn 0.2281 0.0598 0.9718 vn 0.5482 -0.1726 0.8183 vn 0.6457 -0.3360 0.6856 vn 0.7675 -0.5440 0.3392 vn 0.7195 -0.3168 0.6179 vn 0.6938 -0.6651 0.2762 vn 0.7239 -0.4323 0.5376 vn 0.3470 -0.8618 0.3699 vn -0.0003 -0.9059 0.4235 vn 0.3786 -0.6455 0.6633 vn -0.0023 -0.7054 0.7088 vn 0.5796 -0.7589 0.2967 vn 0.6210 -0.5266 0.5805 vn 0.3775 0.0646 0.9237 vn 0.1235 0.2131 0.9692 vn 0.3949 0.2199 0.8920 vn 0.5367 0.1600 0.8285 vn -0.1141 0.4584 0.8814 vn -0.1984 0.6807 0.7052 vn -0.2395 0.7730 0.5874 vn 0.4125 -0.2410 0.8785 vn 0.5680 -0.4289 0.7024 vn -0.0469 0.8608 0.5068 vn 0.0623 0.8558 0.5135 vn 0.3302 0.6493 0.6851 vn 0.9360 0.3086 0.1692 vn 0.8394 0.2729 0.4700 vn 0.9763 0.0412 -0.2122 vn 0.9982 -0.0604 -0.0012 vn 0.9929 -0.0176 -0.1176 vn 0.9183 -0.0115 -0.3956 vn 0.2800 -0.3257 0.9030 vn 0.3546 -0.4979 0.7914 vn -0.0019 -0.5312 0.8472 vn -0.0009 -0.3603 0.9328 vn 0.8511 0.0940 0.5165 vn 0.7298 0.1426 0.6686 vn 0.7349 0.2952 0.6105 vn 0.7986 0.2608 0.5423 vn -0.1494 0.1932 0.9697 vn -0.0002 0.3187 0.9478 vn -0.0002 0.5611 0.8277 vn -0.2356 0.4376 0.8677 vn -0.2796 0.4599 0.8427 vn -0.1245 0.2579 0.9581 vn -0.0468 0.1417 0.9888 vn -0.0344 0.2994 0.9535 vn 0.2831 0.2507 0.9257 vn 0.5556 0.2141 0.8034 vn 0.5726 0.3702 0.7314 vn 0.9876 -0.0668 0.1421 vn 0.9851 -0.0138 0.1712 vn 0.9778 0.0101 0.2091 vn 0.9519 0.0619 0.2999 vn 0.9716 -0.2317 0.0472 vn 0.9615 -0.2031 -0.1852 vn 0.9527 -0.2558 -0.1638 vn 0.9453 -0.1385 -0.2951 vn 0.9861 -0.0878 0.1412 vn 0.8603 0.2256 0.4572 vn 0.1733 0.4817 0.8590 vn -0.0003 0.4824 0.8759 vn 0.6938 0.3904 0.6052 vn 0.5276 0.4577 0.7156 vn 0.3629 0.4737 0.8024 vn 0.9733 0.0722 0.2177 vn 0.8965 0.2665 0.3538 vn 0.7335 0.4720 0.4891 vn 0.5754 0.5840 0.5726 vn 0.4160 0.6532 0.6326 vn 0.2203 0.6996 0.6797 vn -0.0003 0.7155 0.6986 vn 0.9843 0.0758 0.1589 vn 0.8727 0.2265 0.4324 vn 0.7198 0.6345 0.2814 vn 0.9867 0.1558 0.0455 vn 0.9953 -0.0320 0.0910 vn 0.8187 -0.5516 0.1594 vn 0.9470 -0.2358 0.2180 vn 0.9819 -0.0986 0.1617 vn 0.8855 -0.2559 0.3879 vn 0.8912 -0.0589 0.4497 vn 0.9817 -0.0146 0.1897 vn 0.1102 0.9903 -0.0840 vn 0.9721 0.2278 -0.0561 vn 0.9825 0.0748 0.1702 vn 0.9916 0.0168 0.1278 vn 0.9982 0.0088 0.0586 vn 0.9908 0.0696 0.1161 vn 0.9223 0.2758 0.2707 vn 0.9426 0.2722 0.1932 vn 0.7584 0.5297 0.3799 vn 0.7790 0.5678 0.2660 vn 0.5808 0.6882 0.4348 vn 0.5733 0.7639 0.2962 vn 0.4173 0.7829 0.4613 vn 0.3999 0.8648 0.3036 vn 0.2281 0.8453 0.4831 vn 0.2131 0.9275 0.3070 vn -0.0004 0.8702 0.4926 vn -0.0004 0.9512 0.3085 vn 0.9862 0.0124 0.1651 vn 0.9927 -0.0243 0.1179 vn 0.9630 -0.1671 0.2115 vn 0.9147 -0.2635 0.3062 vn 0.9081 -0.3343 0.2522 vn 0.8651 -0.4870 0.1198 vn 0.8138 -0.5675 0.1252 vn 0.7233 -0.6749 0.1458 vn 0.5530 -0.8056 0.2127 vn 0.3056 -0.9041 0.2986 vn 0.0005 -0.9414 0.3372 vn 0.7523 -0.5602 -0.3468 vn 0.7585 -0.6347 -0.1476 vn 0.1063 0.8930 0.4373 vn 0.0349 0.9186 0.3937 vn 0.0775 0.9284 0.3633 vn 0.1617 0.8659 0.4733 vn 0.2545 0.7494 0.6111 vn 0.3673 0.6561 0.6592 vn 0.2130 0.7909 0.5737 vn 0.1453 0.8597 0.4896 vn 0.1990 0.7491 0.6318 vn 0.2266 0.6973 0.6800 vn 0.4455 0.5990 0.6653 vn 0.4367 0.5471 0.7141 vn -0.1425 -0.4490 -0.8821 vn 0.2102 -0.5287 -0.8223 vn 0.9877 -0.1237 -0.0952 vn 0.8433 -0.3157 -0.4349 vn -0.5966 0.5023 -0.6258 vn -0.4835 0.2249 -0.8459 vn 0.1132 0.2753 -0.9546 vn 0.4564 0.5646 -0.6877 vn -0.4101 -0.3906 -0.8242 vn 0.5776 -0.4773 -0.6622 vn -0.4126 -0.1463 -0.8991 vn -0.4539 -0.3581 -0.8159 vn 0.2999 -0.4301 -0.8515 vn 0.0530 -0.1151 -0.9919 vn -0.4859 0.7632 -0.4259 vn 0.9001 0.3909 -0.1921 vn 0.7945 -0.3614 -0.4880 vn 0.9912 0.0295 0.1287 vn 0.9997 -0.0249 -0.0060 vn 0.9976 0.0560 0.0413 vn 0.9604 0.2673 0.0779 vn 0.8026 0.5873 0.1041 vn 0.5779 0.8076 0.1174 vn 0.3890 0.9136 0.1177 vn 0.2006 0.9730 0.1140 vn -0.0004 0.9937 0.1122 vn 0.1099 -0.1335 -0.9849 vn -0.0013 -0.1688 -0.9856 vn -0.0021 -0.2815 -0.9595 vn 0.1777 -0.2741 -0.9451 vn 0.2228 0.2255 -0.9484 vn -0.0019 0.2469 -0.9690 vn -0.0015 0.4761 -0.8794 vn 0.2279 0.4626 -0.8567 vn 0.9266 -0.1050 -0.3609 vn 0.8503 -0.0323 -0.5253 vn 0.8820 0.1174 -0.4563 vn 0.9517 -0.0046 -0.3069 vn 0.9702 0.1510 -0.1891 vn 0.8631 0.4317 -0.2619 vn 0.8317 0.5497 -0.0783 vn 0.9714 0.2313 -0.0539 vn 0.9973 -0.0433 -0.0592 vn 0.9921 -0.0302 -0.1218 vn 0.9905 -0.0083 -0.1372 vn 0.9984 0.0272 -0.0489 vn 0.8845 -0.0619 -0.4624 vn 0.8536 -0.2029 -0.4797 vn 0.9547 -0.1487 -0.2577 vn 0.9726 -0.0816 -0.2176 vn 0.9643 -0.0119 -0.2644 vn 0.9814 -0.0383 -0.1882 vn 0.8812 0.2624 -0.3932 vn 0.9607 0.0628 -0.2703 vn -0.0005 0.9091 -0.4166 vn -0.0005 0.9922 -0.1247 vn 0.2080 0.9708 -0.1196 vn 0.2274 0.8871 -0.4016 vn 0.6017 0.7930 -0.0956 vn 0.6460 0.6899 -0.3267 vn 0.4249 0.8261 -0.3701 vn 0.3981 0.9109 -0.1085 vn 0.6695 0.0681 -0.7397 vn 0.4472 0.1622 -0.8795 vn 0.4550 0.4081 -0.7915 vn 0.6993 0.2840 -0.6559 vn 0.4480 0.6412 -0.6230 vn 0.6867 0.4964 -0.5310 vn -0.0008 0.7150 -0.6991 vn 0.2353 0.6986 -0.6756 vn 0.8272 0.0580 -0.5589 vn 0.7219 0.0827 -0.6870 vn 0.7139 -0.1442 -0.6852 vn 0.8134 -0.0699 -0.5775 vn 0.8559 -0.2378 -0.4592 vn 0.7453 -0.2657 -0.6114 vn 0.7913 -0.1794 -0.5846 vn 0.8858 -0.2047 -0.4165 vn 0.9355 -0.2147 -0.2804 vn 0.9838 -0.0923 -0.1534 vn 0.9246 -0.1564 -0.3473 vn 0.9341 -0.1876 -0.3038 vn 0.9921 0.0409 -0.1186 vn 0.8987 -0.0174 -0.4381 vn 0.9939 0.0239 -0.1077 vn 0.9127 -0.0683 -0.4027 vn 0.7162 -0.2773 -0.6404 vn 0.8311 -0.1960 -0.5204 vn -0.0023 -0.1747 -0.9846 vn -0.0024 0.0132 -0.9999 vn 0.2215 -0.0114 -0.9751 vn 0.2142 -0.1877 -0.9586 vn 0.6084 -0.1230 -0.7840 vn 0.5613 -0.2423 -0.7913 vn 0.3898 -0.2101 -0.8966 vn 0.4230 -0.0648 -0.9038 vn 0.5422 0.0598 -0.8381 vn 0.2660 -0.0318 -0.9634 vn 0.3533 -0.2477 -0.9021 vn 0.5500 -0.2034 -0.8100 vn 0.3727 -0.2710 -0.8875 vn 0.5468 -0.2907 -0.7851 vn -0.0020 -0.2572 -0.9663 vn 0.2035 -0.2591 -0.9442 vn -0.2736 0.6673 0.6927 vn -0.2621 0.7474 0.6105 vn -0.2578 0.6690 0.6971 vn -0.2536 0.7208 0.6451 vn -0.0009 0.6898 0.7240 vn -0.0017 0.7283 0.6852 vn 0.4770 0.5701 0.6688 vn 0.6020 0.6883 0.4046 vn 0.4076 0.6126 -0.6771 vn 0.5396 0.7326 -0.4147 vn 0.5414 0.7185 -0.4365 vn 0.4115 0.5860 -0.6980 vn 0.3188 0.7439 0.5872 vn 0.3443 0.7071 0.6176 vn 0.5740 0.8174 -0.0478 vn 0.5846 0.8101 -0.0441 vn 0.5126 0.7874 0.3422 vn 0.4878 0.8095 0.3265 vn 0.3878 0.6632 -0.6401 vn 0.5404 0.7517 -0.3780 vn 0.6359 0.7716 -0.0160 vn 0.5855 0.6347 -0.5043 vn 0.3090 0.6850 -0.6597 vn 0.8221 0.5492 -0.1501 vn 0.8868 0.2250 -0.4037 vn 0.7142 0.3279 -0.6184 vn -0.0360 0.1549 -0.9872 vn 0.0003 0.1149 -0.9933 vn 0.4610 0.3333 -0.8224 vn 0.0914 0.2518 -0.9634 vn -0.2069 0.3382 -0.9181 vn 0.0006 0.3193 -0.9476 vn 0.2899 0.5476 -0.7849 vn -0.1165 0.4249 -0.8977 vn -0.3315 0.3966 -0.8560 vn 0.0005 0.4023 -0.9155 vn 0.0374 0.6140 -0.7884 vn -0.3061 0.4682 -0.8289 vn -0.3375 0.4068 -0.8489 vn -0.3328 0.3706 -0.8671 vn 0.0027 0.3914 -0.9202 vn 0.0033 0.4318 -0.9019 vn -0.3330 0.4060 -0.8510 vn -0.3170 0.3846 -0.8669 vn -0.1143 0.4408 -0.8903 vn -0.0972 0.4361 -0.8946 vn -0.3764 0.4190 -0.8263 vn 0.0024 0.4577 -0.8891 vn -0.4048 0.4227 -0.8108 vn -0.3542 0.4439 -0.8231 vn -0.3871 0.4146 -0.8236 vn 0.0028 0.4855 -0.8742 vn -0.1590 0.5109 -0.8448 vn -0.1673 0.4494 -0.8775 vn 0.1023 0.5842 -0.8051 vn 0.1390 0.5389 -0.8308 vn 0.1746 0.5078 -0.8436 vn 0.1820 0.4980 -0.8478 vn 0.0002 -0.9609 0.2768 vn 0.0384 -0.9514 0.3056 vn 0.0774 -0.9317 0.3548 vn 0.0820 -0.9211 0.3806 vn 0.0826 0.9966 0.0069 vn -0.0004 0.9938 -0.1114 vn 0.3482 0.9025 0.2535 vn 0.3241 0.8325 0.4493 vn -0.0356 0.0069 0.9993 vn -0.0001 -0.2038 0.9790 vn 0.1049 -0.1682 0.9801 vn 0.0651 -0.0324 0.9973 vn -0.0440 0.0190 0.9988 vn -0.0001 0.0779 0.9969 vn 0.8638 -0.4530 0.2206 vn 0.8901 -0.3815 0.2491 vn 0.8909 -0.2893 0.3501 vn 0.9586 -0.2387 0.1552 vn 0.9630 0.0624 0.2621 vn 0.8904 -0.4506 -0.0646 vn 0.9232 -0.3580 0.1397 vn 0.9494 0.3134 0.0179 vn 0.9470 -0.2390 0.2148 vn 0.9239 -0.2732 -0.2678 vn 0.8668 -0.4187 0.2706 vn 0.9751 -0.2151 -0.0533 vn 0.9903 -0.1325 0.0421 vn 0.7928 0.4661 -0.3926 vn 0.9936 -0.1132 -0.0003 vn 0.7425 0.2701 -0.6129 vn 0.9850 -0.1650 -0.0515 vn 0.6906 0.0520 -0.7214 vn 0.2201 0.8912 0.3966 vn -0.3141 0.2509 -0.9156 vn -0.3352 0.5180 -0.7870 vn -0.2610 0.8384 -0.4785 vn -0.0594 0.9981 0.0172 vn 0.9791 -0.1948 -0.0591 vn 0.5885 -0.1664 -0.7911 vn 0.9810 -0.1935 0.0129 vn 0.5948 -0.3266 -0.7345 vn 0.9660 -0.1584 0.2042 vn 0.6543 -0.4228 -0.6270 vn 0.9621 0.0010 0.2727 vn 0.7318 -0.4447 -0.5164 vn -0.4756 -0.3942 -0.7864 vn -0.4958 -0.3291 -0.8036 vn -0.4092 -0.2751 -0.8700 vn -0.3319 -0.0656 -0.9410 vn 0.9833 0.0629 0.1704 vn 0.6975 -0.5161 -0.4971 vn 0.5649 -0.7472 -0.3500 vn 0.9426 -0.2803 0.1816 vn -0.3965 -0.5141 -0.7606 vn -0.0161 -0.6634 -0.7480 vn 0.9752 0.2024 -0.0894 vn 0.9228 0.3839 0.0322 vn 0.9515 0.2336 0.2002 vn 0.8798 0.3264 0.3454 vn 0.7342 -0.3280 0.5944 vn 0.6364 0.5125 0.5764 vn 0.8796 -0.1671 0.4454 vn 0.8158 -0.1409 0.5609 vn 0.8726 0.0760 0.4825 vn 0.9072 -0.1517 0.3924 vn 0.9424 -0.2628 0.2069 vn 0.9701 -0.2418 -0.0198 vn 0.8577 -0.1611 0.4883 vn 0.7598 -0.2012 0.6183 vn 0.8122 -0.3142 0.4915 vn 0.9293 -0.1915 0.3159 vn 0.8831 -0.2182 0.4154 vn 0.9926 -0.0184 0.1202 vn 0.9953 -0.0960 0.0104 vn 0.4295 0.8123 0.3944 vn -0.0378 -0.9143 0.4032 vn -0.7576 0.2227 0.6135 vn -0.6259 0.4340 0.6479 vn -0.7276 0.3501 0.5899 vn -0.7667 0.2268 0.6006 vn -0.2808 -0.7982 0.5328 vn -0.5447 -0.6069 0.5788 vn -0.3009 -0.3595 0.8833 vn -0.2294 -0.3709 0.8999 vn -0.2562 -0.7573 0.6007 vn -0.4440 -0.2799 0.8512 vn -0.3703 -0.6155 0.6957 vn -0.6433 -0.2595 0.7202 vn -0.4968 -0.4434 0.7460 vn -0.7762 -0.2223 0.5900 vn -0.6029 -0.1942 0.7738 vn -0.8313 -0.1086 0.5451 vn -0.4238 0.2262 0.8770 vn -0.5754 0.2423 0.7811 vn -0.7813 0.1209 0.6123 vn -0.6026 0.1031 0.7914 vn -0.3123 0.2925 0.9038 vn -0.4446 0.1367 0.8852 vn -0.4102 0.4376 0.8001 vn -0.5571 0.3085 0.7710 vn -0.6391 0.4162 0.6467 vn -0.5991 0.3782 0.7056 vn -0.7627 0.3644 0.5343 vn -0.6281 0.4156 0.6578 vn -0.7784 0.3213 0.5393 vn -0.5921 0.4064 0.6959 vn -0.5761 -0.0316 0.8167 vn -0.5876 -0.1444 0.7961 vn -0.6862 -0.1626 0.7089 vn -0.6333 -0.0679 0.7709 vn -0.7230 0.2553 0.6419 vn -0.5759 0.0615 0.8152 vn -0.8471 0.1152 0.5187 vn -0.6992 -0.1080 0.7067 vn -0.5145 -0.0621 0.8552 vn -0.5533 -0.0246 0.8326 vn -0.1113 0.0935 0.9893 vn -0.2495 0.2148 0.9442 vn -0.5246 0.2798 0.8040 vn -0.7914 0.1979 0.5784 vn -0.9181 0.0380 0.3945 vn -0.2009 -0.1287 0.9711 vn -0.1096 0.0485 0.9928 vn -0.6452 0.0475 0.7625 vn -0.7821 -0.0792 0.6181 vn -0.6719 0.0252 0.7402 vn -0.5797 0.0903 0.8098 vn -0.4357 -0.0081 0.9000 vn -0.4352 0.3398 0.8337 vn -0.6922 -0.1092 0.7133 vn -0.8258 -0.1750 0.5360 vn -0.8386 -0.1669 0.5184 vn -0.6926 -0.0408 0.7202 vn -0.7721 -0.1559 0.6161 vn -0.6697 -0.1072 0.7349 vn -0.4397 -0.0924 0.8933 vn -0.3182 0.2236 0.9212 vn -0.7140 -0.4844 0.5054 vn -0.4335 -0.5922 0.6792 vn -0.8407 -0.4060 0.3581 vn -0.4533 -0.8888 0.0670 vn -0.4465 -0.8902 0.0908 vn -0.5992 -0.8000 0.0306 vn -0.5711 -0.8207 0.0134 vn -0.2340 -0.9613 0.1451 vn -0.2555 -0.9551 0.1501 vn -0.6349 0.1084 0.7649 vn -0.8309 0.0635 0.5527 vn -0.7607 -0.1873 0.6215 vn -0.6735 -0.1411 0.7256 vn -0.4311 0.0622 0.9001 vn -0.2803 0.8336 0.4758 vn -0.4420 0.1303 0.8875 vn -0.2239 -0.1650 0.9605 vn -0.4573 -0.1100 0.8824 vn -0.5394 0.0449 0.8408 vn -0.4342 0.5429 0.7189 vn -0.5744 -0.1513 0.8045 vn -0.6238 -0.0356 0.7808 vn -0.4010 -0.3059 0.8635 vn -0.5114 0.6908 0.5111 vn -0.6380 0.1183 0.7609 vn -0.1267 -0.7389 0.6618 vn -0.2820 -0.7669 0.5764 vn -0.5599 -0.3430 0.7542 vn -0.4447 -0.2965 0.8451 vn -0.1833 -0.8026 0.5676 vn -0.3489 -0.4081 0.8436 vn -0.2763 -0.7842 0.5555 vn -0.5560 -0.3713 0.7436 vn -0.5987 -0.1036 0.7942 vn -0.6160 -0.0434 0.7865 vn -0.5998 -0.1215 0.7908 vn -0.6375 -0.0953 0.7645 vn -0.5628 0.5601 0.6079 vn -0.6357 0.1074 0.7644 vn -0.5751 0.0773 0.8144 vn -0.6353 0.0852 0.7675 vn -0.4077 0.2362 0.8820 vn -0.3772 0.0945 0.9213 vn -0.4221 0.0623 0.9044 vn -0.6418 0.0403 0.7658 vn -0.6374 -0.4831 0.6002 vn -0.8033 -0.3002 0.5144 vn -0.8241 -0.0822 0.5604 vn -0.5837 -0.2004 0.7869 vn -0.4278 -0.2356 0.8726 vn -0.4436 -0.2859 0.8494 vn -0.5491 0.0667 0.8331 vn -0.7407 -0.3831 0.5518 vn -0.6600 -0.0395 0.7502 vn -0.5623 -0.0121 0.8268 vn -0.3849 -0.1120 0.9161 vn -0.9272 -0.3687 0.0660 vn -0.9529 -0.2410 0.1841 vn -0.9825 -0.0625 0.1751 vn -0.9412 -0.3139 0.1247 vn -0.6973 -0.7166 -0.0093 vn -0.6857 -0.7272 0.0312 vn -0.7763 -0.6298 -0.0259 vn -0.7927 -0.6057 0.0688 vn -0.8666 -0.4901 -0.0936 vn -0.8968 -0.4424 -0.0073 vn -0.8911 -0.3421 0.2980 vn -0.5821 -0.0915 0.8079 vn -0.8926 -0.2496 0.3754 vn -0.8054 -0.2300 0.5463 vn -0.8619 -0.2203 0.4567 vn -0.7175 -0.2029 0.6664 vn -0.9344 -0.0171 0.3559 vn -0.8003 -0.0625 0.5964 vn -0.9286 -0.0215 0.3704 vn -0.9335 0.0249 0.3577 vn -0.8825 -0.1645 0.4405 vn -0.9063 -0.2065 0.3686 vn -0.9579 -0.1181 0.2618 vn -0.9657 -0.1322 0.2236 vn -0.9125 -0.2247 0.3418 vn -0.9670 -0.1332 0.2170 vn -0.6157 -0.5504 0.5638 vn -0.5809 -0.7841 0.2182 vn -0.6524 -0.7273 0.2130 vn -0.6828 -0.7227 -0.1070 vn -0.2906 -0.9139 0.2834 vn 0.0897 -0.8253 0.5575 vn -0.0505 -0.1701 0.9841 vn -0.4794 -0.8242 -0.3013 vn -0.1189 -0.9566 -0.2659 vn 0.1654 -0.9698 -0.1793 vn -0.3571 -0.9297 -0.0894 vn -0.6914 -0.7044 0.1605 vn -0.9109 -0.4119 -0.0250 vn -0.9349 0.0031 0.3550 vn -0.6256 0.2243 0.7472 vn -0.8431 -0.5220 -0.1293 vn -0.8694 -0.4675 0.1600 vn -0.7473 -0.3038 0.5910 vn -0.1331 -0.7736 0.6195 vn 0.4332 -0.8865 0.1625 vn -0.6856 0.2907 0.6674 vn -0.4248 0.5212 0.7402 vn -0.1718 -0.6532 0.7374 vn -0.4356 -0.4997 0.7486 vn -0.1506 -0.6232 0.7674 vn -0.3353 -0.4684 0.8173 vn -0.4508 -0.3901 0.8029 vn -0.4712 -0.2302 0.8514 vn -0.3449 0.2480 0.9053 vn -0.4907 0.2334 0.8395 vn -0.2075 0.3292 0.9212 vn -0.2059 0.4741 0.8560 vn -0.5112 0.0741 0.8562 vn -0.6939 -0.1185 0.7102 vn -0.1951 0.8438 0.4999 vn -0.0885 0.8677 0.4892 vn -0.2856 0.7831 0.5524 vn -0.6218 0.4335 0.6522 vn -0.4152 -0.6372 0.6493 vn -0.6769 -0.2214 0.7019 vn -0.2639 -0.7885 0.5555 vn -0.1015 -0.8883 0.4479 vn -0.0316 -0.8718 0.4888 vn 0.0266 -0.7700 0.6374 vn 0.0582 -0.4640 0.8839 vn 0.0081 0.7206 0.6932 vn 0.0166 0.1924 0.9812 vn 0.0016 0.8167 0.5770 vn -0.4190 -0.9075 -0.0291 vn -0.5872 -0.7755 0.2318 vn -0.0971 -0.9887 -0.1138 vn 0.2620 -0.9584 -0.1130 vn 0.5482 -0.8304 0.0992 vn 0.6994 -0.4400 0.5632 vn 0.3520 0.9144 -0.1998 vn 0.5500 0.8339 0.0464 vn 0.0668 0.9422 -0.3284 vn -0.2325 0.9147 -0.3306 vn -0.4192 0.9078 0.0064 vn 0.7430 0.3601 0.5641 vn -0.4480 -0.8005 0.3980 vn -0.6027 -0.6745 0.4264 vn -0.1610 -0.8795 0.4477 vn 0.1231 -0.8754 0.4674 vn 0.3726 -0.7715 0.5156 vn 0.4044 -0.3821 0.8309 vn 0.2980 0.8817 0.3658 vn 0.4878 0.7878 0.3760 vn 0.0038 0.9598 0.2806 vn -0.3977 0.9028 0.1635 vn -0.6171 0.7788 0.1122 vn 0.4848 0.2959 0.8230 vn -0.4516 -0.3506 0.8204 vn -0.5798 -0.3049 0.7555 vn 0.0682 0.2508 0.9656 vn 0.0383 -0.3904 0.9198 vn -0.1788 -0.3938 0.9016 vn -0.4357 0.2477 0.8653 vn -0.1355 0.2574 0.9567 vn -0.6651 0.3258 0.6718 vn -0.2333 0.3243 0.9167 vn -0.4069 0.3753 0.8328 vn -0.6210 0.3710 0.6904 vn -0.8324 0.2431 0.4979 vn -0.9360 0.0790 0.3430 vn -0.1051 0.2871 0.9521 vn -0.9695 0.0083 0.2450 vn -0.9781 -0.0479 0.2024 vn -0.9613 0.0141 0.2751 vn -0.9819 -0.1034 0.1583 vn -0.9851 -0.1146 0.1282 vn -0.9919 -0.0842 0.0950 vn -0.9874 -0.1581 -0.0065 vn -0.9742 0.0472 0.2204 vn -0.9300 -0.0407 0.3653 vn -0.9152 -0.1782 0.3615 vn -0.8013 -0.2928 0.5217 vn -0.9819 -0.0320 0.1866 vn -0.9319 0.0355 0.3608 vn -0.6979 0.0680 0.7129 vn -0.8314 -0.0290 0.5549 vn 0.0406 0.7854 0.6176 vn 0.1733 0.8108 0.5591 vn 0.2330 0.7887 0.5688 vn 0.1187 0.7190 0.6848 vn -0.2815 0.5347 0.7967 vn -0.1209 0.4666 0.8761 vn -0.6158 -0.0837 0.7834 vn -0.4135 -0.1163 0.9030 vn -0.6478 -0.3359 0.6836 vn -0.5490 -0.1726 0.8178 vn -0.2286 0.0595 0.9717 vn -0.7679 -0.5433 0.3392 vn -0.7202 -0.3164 0.6174 vn -0.6950 -0.6635 0.2768 vn -0.7258 -0.4304 0.5366 vn -0.3485 -0.8603 0.3721 vn -0.3828 -0.6423 0.6640 vn -0.5817 -0.7566 0.2985 vn -0.6244 -0.5230 0.5802 vn -0.3778 0.0647 0.9236 vn -0.5367 0.1600 0.8284 vn -0.3951 0.2198 0.8919 vn -0.1236 0.2130 0.9692 vn 0.1984 0.6807 0.7052 vn 0.1141 0.4584 0.8814 vn 0.2395 0.7730 0.5874 vn -0.4141 -0.2423 0.8774 vn -0.5713 -0.4290 0.6997 vn -0.0623 0.8558 0.5135 vn 0.0469 0.8608 0.5067 vn -0.3302 0.6492 0.6851 vn -0.9360 0.3086 0.1692 vn -0.8394 0.2729 0.4700 vn -0.9981 -0.0605 -0.0013 vn -0.9763 0.0412 -0.2122 vn -0.9928 -0.0180 -0.1181 vn -0.9183 -0.0115 -0.3957 vn -0.2816 -0.3268 0.9022 vn -0.3581 -0.4982 0.7896 vn -0.8511 0.0940 0.5164 vn -0.7298 0.1426 0.6686 vn -0.7349 0.2952 0.6105 vn -0.7986 0.2608 0.5423 vn 0.1492 0.1933 0.9697 vn 0.2354 0.4377 0.8677 vn 0.0467 0.1416 0.9888 vn 0.1244 0.2579 0.9581 vn 0.2796 0.4599 0.8427 vn 0.0344 0.2994 0.9535 vn -0.2831 0.2506 0.9257 vn -0.5556 0.2141 0.8034 vn -0.5726 0.3702 0.7314 vn -0.9857 -0.0034 0.1686 vn -0.9883 -0.0652 0.1375 vn -0.9523 0.0604 0.2990 vn -0.9779 0.0164 0.2083 vn -0.9706 -0.2356 0.0478 vn -0.9482 -0.1438 -0.2831 vn -0.9536 -0.2590 -0.1536 vn -0.9630 -0.2023 -0.1777 vn -0.9860 -0.0949 0.1371 vn -0.8612 0.2244 0.4560 vn -0.1736 0.4817 0.8589 vn -0.6938 0.3915 0.6045 vn -0.5256 0.4593 0.7161 vn -0.3620 0.4740 0.8026 vn -0.8971 0.2673 0.3518 vn -0.9738 0.0715 0.2159 vn -0.7332 0.4739 0.4876 vn -0.5746 0.5854 0.5720 vn -0.4152 0.6537 0.6326 vn -0.2206 0.6995 0.6796 vn -0.9844 0.0777 0.1578 vn -0.9867 0.1562 0.0455 vn -0.7184 0.6361 0.2815 vn -0.8713 0.2311 0.4328 vn -0.9951 -0.0367 0.0910 vn -0.9434 -0.2402 0.2287 vn -0.8160 -0.5533 0.1673 vn -0.9835 -0.0923 0.1558 vn -0.9827 -0.0066 0.1852 vn -0.8921 -0.0506 0.4490 vn -0.8859 -0.2499 0.3907 vn -0.9720 0.2281 -0.0559 vn -0.1099 0.9904 -0.0840 vn -0.9828 0.0746 0.1689 vn -0.9908 0.0696 0.1156 vn -0.9982 0.0089 0.0585 vn -0.9917 0.0175 0.1272 vn -0.9225 0.2767 0.2691 vn -0.9427 0.2725 0.1926 vn -0.7580 0.5308 0.3789 vn -0.7789 0.5680 0.2658 vn -0.5806 0.6887 0.4343 vn -0.5733 0.7639 0.2962 vn -0.4171 0.7831 0.4612 vn -0.3999 0.8648 0.3036 vn -0.2285 0.8452 0.4831 vn -0.2135 0.9274 0.3070 vn -0.9864 0.0155 0.1635 vn -0.9926 -0.0165 0.1201 vn -0.9622 -0.1663 0.2156 vn -0.9070 -0.3356 0.2542 vn -0.9137 -0.2647 0.3083 vn -0.8639 -0.4888 0.1209 vn -0.8137 -0.5675 0.1254 vn -0.7233 -0.6749 0.1458 vn -0.5529 -0.8056 0.2127 vn -0.3053 -0.9042 0.2987 vn -0.7539 -0.6388 -0.1535 vn -0.7487 -0.5625 -0.3507 vn -0.1063 0.8930 0.4373 vn -0.1617 0.8660 0.4733 vn -0.0775 0.9284 0.3633 vn -0.0349 0.9185 0.3937 vn -0.3673 0.6561 0.6592 vn -0.2545 0.7494 0.6111 vn -0.2130 0.7909 0.5737 vn -0.2266 0.6973 0.6800 vn -0.1990 0.7491 0.6318 vn -0.1453 0.8597 0.4896 vn -0.4367 0.5471 0.7141 vn -0.4455 0.5990 0.6653 vn 0.1419 -0.4497 -0.8818 vn -0.8428 -0.3155 -0.4361 vn -0.9874 -0.1232 -0.0991 vn -0.2070 -0.5288 -0.8231 vn 0.5967 0.5022 -0.6258 vn -0.4561 0.5647 -0.6877 vn -0.1131 0.2753 -0.9546 vn 0.4833 0.2247 -0.8461 vn 0.4110 -0.3905 -0.8238 vn -0.5776 -0.4773 -0.6622 vn 0.4122 -0.1464 -0.8992 vn -0.0529 -0.1151 -0.9919 vn -0.3002 -0.4301 -0.8514 vn 0.4535 -0.3587 -0.8159 vn 0.4860 0.7631 -0.4259 vn -0.9001 0.3911 -0.1919 vn -0.9918 0.0285 0.1247 vn -0.7909 -0.3644 -0.4917 vn -0.9976 0.0561 0.0413 vn -0.9997 -0.0248 -0.0059 vn -0.9604 0.2673 0.0779 vn -0.8026 0.5873 0.1041 vn -0.5779 0.8076 0.1174 vn -0.3890 0.9136 0.1177 vn -0.2010 0.9729 0.1140 vn -0.1113 -0.1333 -0.9848 vn -0.1800 -0.2740 -0.9447 vn -0.2247 0.2254 -0.9480 vn -0.2294 0.4626 -0.8564 vn -0.9266 -0.1050 -0.3610 vn -0.9517 -0.0045 -0.3069 vn -0.8820 0.1174 -0.4563 vn -0.8503 -0.0323 -0.5253 vn -0.9702 0.1511 -0.1891 vn -0.9714 0.2313 -0.0539 vn -0.8317 0.5497 -0.0783 vn -0.8631 0.4317 -0.2619 vn -0.9973 -0.0431 -0.0591 vn -0.9984 0.0273 -0.0489 vn -0.9905 -0.0082 -0.1372 vn -0.9921 -0.0300 -0.1218 vn -0.8844 -0.0618 -0.4625 vn -0.9726 -0.0815 -0.2177 vn -0.9546 -0.1487 -0.2579 vn -0.8536 -0.2029 -0.4798 vn -0.9814 -0.0382 -0.1883 vn -0.9643 -0.0117 -0.2645 vn -0.8812 0.2624 -0.3932 vn -0.9607 0.0629 -0.2703 vn -0.2280 0.8871 -0.4014 vn -0.2085 0.9707 -0.1195 vn -0.6017 0.7930 -0.0956 vn -0.3981 0.9109 -0.1085 vn -0.4249 0.8261 -0.3701 vn -0.6460 0.6899 -0.3267 vn -0.6695 0.0681 -0.7397 vn -0.6993 0.2840 -0.6559 vn -0.4550 0.4081 -0.7915 vn -0.4472 0.1622 -0.8795 vn -0.4480 0.6412 -0.6230 vn -0.6867 0.4964 -0.5310 vn -0.2363 0.6986 -0.6753 vn -0.8272 0.0580 -0.5589 vn -0.8133 -0.0700 -0.5775 vn -0.7139 -0.1443 -0.6852 vn -0.7219 0.0827 -0.6870 vn -0.8558 -0.2378 -0.4593 vn -0.8857 -0.2047 -0.4166 vn -0.7912 -0.1794 -0.5846 vn -0.7453 -0.2657 -0.6115 vn -0.9355 -0.2145 -0.2806 vn -0.9340 -0.1875 -0.3040 vn -0.9245 -0.1565 -0.3476 vn -0.9838 -0.0924 -0.1538 vn -0.9920 0.0407 -0.1194 vn -0.8986 -0.0175 -0.4383 vn -0.9126 -0.0684 -0.4030 vn -0.9938 0.0238 -0.1083 vn -0.7162 -0.2773 -0.6404 vn -0.8310 -0.1960 -0.5205 vn -0.2164 -0.1880 -0.9580 vn -0.2238 -0.0116 -0.9745 vn -0.6084 -0.1230 -0.7840 vn -0.4230 -0.0648 -0.9038 vn -0.3898 -0.2101 -0.8966 vn -0.5613 -0.2423 -0.7913 vn -0.5422 0.0598 -0.8381 vn -0.5500 -0.2034 -0.8100 vn -0.3536 -0.2476 -0.9020 vn -0.2663 -0.0316 -0.9633 vn -0.3728 -0.2709 -0.8874 vn -0.5468 -0.2907 -0.7851 vn -0.2057 -0.2591 -0.9437 vn 0.2621 0.7474 0.6105 vn 0.2736 0.6673 0.6927 vn 0.2569 0.6692 0.6972 vn 0.2520 0.7212 0.6453 vn -0.6020 0.6883 0.4046 vn -0.4770 0.5701 0.6688 vn -0.4076 0.6126 -0.6771 vn -0.4115 0.5860 -0.6980 vn -0.5414 0.7186 -0.4365 vn -0.5396 0.7326 -0.4147 vn -0.3188 0.7439 0.5872 vn -0.3443 0.7071 0.6176 vn -0.5740 0.8174 -0.0478 vn -0.4878 0.8095 0.3265 vn -0.5126 0.7874 0.3422 vn -0.5846 0.8101 -0.0441 vn -0.3878 0.6632 -0.6401 vn -0.5404 0.7517 -0.3780 vn -0.6359 0.7716 -0.0160 vn -0.5855 0.6347 -0.5043 vn -0.3090 0.6850 -0.6597 vn -0.8221 0.5492 -0.1501 vn -0.8868 0.2250 -0.4037 vn -0.7142 0.3279 -0.6184 vn 0.0362 0.1551 -0.9872 vn -0.0914 0.2519 -0.9634 vn -0.4610 0.3333 -0.8224 vn 0.2075 0.3383 -0.9178 vn 0.1165 0.4249 -0.8977 vn -0.2899 0.5476 -0.7849 vn 0.3323 0.3963 -0.8558 vn 0.3061 0.4682 -0.8289 vn -0.0374 0.6140 -0.7884 vn 0.3404 0.4063 -0.8480 vn 0.3359 0.3703 -0.8660 vn 0.3330 0.4060 -0.8510 vn 0.3170 0.3846 -0.8669 vn 0.1143 0.4408 -0.8903 vn 0.0972 0.4361 -0.8946 vn 0.3783 0.4183 -0.8257 vn 0.4048 0.4227 -0.8108 vn 0.3870 0.4146 -0.8236 vn 0.3569 0.4433 -0.8223 vn 0.1590 0.5109 -0.8448 vn 0.1673 0.4494 -0.8775 vn -0.1023 0.5842 -0.8051 vn -0.1746 0.5078 -0.8436 vn -0.1390 0.5389 -0.8308 vn -0.1820 0.4980 -0.8478 vn -0.0383 -0.9514 0.3056 vn -0.0774 -0.9317 0.3549 vn -0.0820 -0.9210 0.3807 vn -0.0828 0.9965 0.0068 vn -0.3481 0.9026 0.2534 vn -0.3241 0.8326 0.4491 vn 0.0356 0.0068 0.9993 vn -0.1052 -0.1686 0.9800 vn -0.0653 -0.0327 0.9973 vn 0.0440 0.0191 0.9988 vn -0.8591 -0.4635 0.2170 vn -0.9543 -0.2606 0.1462 vn -0.8896 -0.2932 0.3502 vn -0.8952 -0.3780 0.2360 vn -0.9590 0.0819 0.2710 vn -0.9445 0.3278 0.0207 vn -0.9386 -0.3172 0.1358 vn -0.9080 -0.4163 -0.0473 vn -0.9430 -0.2246 0.2454 vn -0.9404 -0.2538 -0.2262 vn -0.8572 -0.4245 0.2914 vn -0.9717 -0.2338 -0.0339 vn -0.7827 0.4797 -0.3965 vn -0.9940 -0.1072 0.0193 vn -0.7390 0.2726 -0.6161 vn -0.9946 -0.1015 -0.0223 vn -0.6915 0.0512 -0.7205 vn -0.9845 -0.1605 -0.0706 vn -0.2169 0.8922 0.3960 vn 0.3349 0.5178 -0.7872 vn 0.3130 0.2507 -0.9160 vn 0.2616 0.8382 -0.4785 vn 0.0611 0.9980 0.0168 vn -0.5894 -0.1664 -0.7905 vn -0.9776 -0.1956 -0.0776 vn -0.5957 -0.3257 -0.7342 vn -0.9797 -0.2000 -0.0104 vn -0.6506 -0.4241 -0.6300 vn -0.9680 -0.1726 0.1818 vn -0.7290 -0.4457 -0.5195 vn -0.9651 -0.0133 0.2614 vn 0.4953 -0.3296 -0.8038 vn 0.4760 -0.3949 -0.7857 vn 0.4075 -0.2755 -0.8706 vn 0.3300 -0.0660 -0.9416 vn -0.6968 -0.5148 -0.4994 vn -0.9844 0.0598 0.1653 vn -0.5655 -0.7459 -0.3518 vn -0.9438 -0.2784 0.1778 vn 0.3953 -0.5165 -0.7596 vn 0.0176 -0.6652 -0.7465 vn -0.9749 0.2061 -0.0837 vn -0.9253 0.3772 0.0386 vn -0.8803 0.3233 0.3471 vn -0.9504 0.2384 0.1997 vn -0.7347 -0.3269 0.5944 vn -0.6359 0.5136 0.5759 vn -0.8123 -0.1348 0.5673 vn -0.8770 -0.1678 0.4501 vn -0.8743 0.0653 0.4809 vn -0.9090 -0.1593 0.3851 vn -0.9442 -0.2705 0.1879 vn -0.9682 -0.2455 -0.0472 vn -0.8538 -0.1513 0.4981 vn -0.8040 -0.3254 0.4976 vn -0.7456 -0.2026 0.6348 vn -0.9333 -0.1788 0.3115 vn -0.8843 -0.2327 0.4047 vn -0.9953 0.0024 0.0963 vn -0.9958 -0.0881 -0.0215 vn -0.4295 0.8125 0.3942 vn 0.0378 -0.9143 0.4032 vn 0.7009 -0.2227 -0.6775 vn 0.7507 -0.1354 -0.6466 vn 0.0158 0.0911 -0.9957 vn -0.0139 -0.1133 -0.9934 vn -0.7542 0.1018 -0.6487 vn -0.7330 -0.0163 -0.6800 vn -0.9999 -0.0004 0.0144 vn -1.0000 -0.0049 0.0031 vn -0.7617 -0.1222 0.6362 vn -0.7435 -0.0915 0.6624 vn -0.0387 -0.2729 0.9612 vn -0.0289 -0.2206 0.9749 vn 0.6930 -0.3455 0.6327 vn 0.6909 -0.2953 0.6599 vn 0.9523 -0.3048 0.0131 vn 0.9585 -0.2850 0.0026 vn -0.0241 -0.1111 -0.9935 vn -0.1103 -0.2661 -0.9576 vn 0.0000 -0.4858 -0.8740 vn 0.7055 -0.2544 -0.6614 vn -0.7425 -0.0121 -0.6697 vn -0.7738 -0.0472 -0.6316 vn -1.0000 0.0053 0.0044 vn -0.9987 0.0507 0.0060 vn -0.7434 -0.0461 0.6672 vn -0.7721 -0.0116 0.6354 vn -0.0388 -0.1682 0.9850 vn -0.1471 -0.2321 0.9615 vn 0.6964 -0.2921 0.6555 vn 0.0000 -0.4816 0.8764 vn 0.9490 -0.3152 0.0058 vn 0.0000 -0.9998 0.0206 vn 0.7694 0.1193 -0.6275 vn 0.0522 0.4025 -0.9139 vn 0.7053 0.2607 -0.6592 vn -0.0031 0.5076 -0.8616 vn -0.6920 0.4010 -0.6003 vn -0.6443 0.5223 -0.5585 vn -0.9798 0.1981 0.0274 vn -0.9314 0.3637 -0.0124 vn -0.7489 -0.0432 0.6612 vn -0.7943 0.0840 0.6016 vn -0.0193 -0.2656 0.9639 vn -0.0644 -0.2408 0.9684 vn 0.7075 -0.3245 0.6278 vn 0.7505 -0.2868 0.5954 vn 0.9817 -0.1902 0.0023 vn 0.9959 -0.0618 -0.0661 vn -0.7490 0.0587 -0.6599 vn -0.1558 -0.1548 -0.9756 vn -0.9841 0.1770 -0.0132 vn -0.7669 0.0595 0.6390 vn -0.2355 -0.1804 0.9550 vn -0.7317 0.2497 -0.6342 vn -0.1797 0.2153 -0.9598 vn -0.9853 0.1702 -0.0129 vn -0.7740 0.0499 0.6312 vn -0.2623 -0.0431 0.9640 vn -0.7886 0.2376 -0.5671 vn -0.2038 0.3414 -0.9175 vn -0.9967 0.0685 0.0432 vn -0.7622 -0.0278 0.6467 vn -0.2533 -0.0563 0.9657 vn 0.0000 -0.0675 0.9977 vn 0.0000 -0.0549 0.9985 vn 0.0000 -0.3066 0.9518 vn 0.0000 -0.2510 -0.9680 vn 0.0000 0.1825 -0.9832 vn 0.0000 0.3395 -0.9406 vn -0.0137 -0.1017 -0.9947 vn 0.7148 -0.2085 -0.6674 vn -0.7429 0.0031 -0.6694 vn -0.9997 0.0232 -0.0000 vn -0.7467 -0.0530 0.6630 vn -0.0230 -0.1830 0.9828 vn 0.7042 -0.2631 0.6594 vn 0.9651 -0.2619 -0.0003 vn -0.0158 0.0911 -0.9957 vn -0.7507 -0.1354 -0.6466 vn -0.7009 -0.2227 -0.6775 vn 0.0139 -0.1133 -0.9934 vn 0.7542 0.1018 -0.6487 vn 0.7330 -0.0163 -0.6800 vn 0.9999 -0.0004 0.0144 vn 1.0000 -0.0049 0.0031 vn 0.7617 -0.1222 0.6362 vn 0.7435 -0.0915 0.6624 vn 0.0387 -0.2729 0.9612 vn 0.0289 -0.2206 0.9749 vn -0.6930 -0.3455 0.6327 vn -0.6909 -0.2953 0.6599 vn -0.9523 -0.3048 0.0131 vn -0.9585 -0.2850 0.0026 vn 0.0241 -0.1111 -0.9935 vn -0.7055 -0.2544 -0.6614 vn 0.1103 -0.2661 -0.9576 vn 0.7425 -0.0121 -0.6697 vn 0.7738 -0.0472 -0.6316 vn 1.0000 0.0053 0.0044 vn 0.9987 0.0507 0.0060 vn 0.7434 -0.0461 0.6672 vn 0.7721 -0.0116 0.6354 vn 0.0388 -0.1682 0.9850 vn 0.1471 -0.2321 0.9615 vn -0.6964 -0.2921 0.6555 vn -0.9490 -0.3152 0.0058 vn -0.7694 0.1193 -0.6275 vn -0.0522 0.4025 -0.9139 vn 0.0031 0.5076 -0.8616 vn -0.7053 0.2607 -0.6592 vn 0.6920 0.4010 -0.6003 vn 0.6443 0.5223 -0.5585 vn 0.9798 0.1981 0.0274 vn 0.9314 0.3637 -0.0124 vn 0.7489 -0.0432 0.6612 vn 0.7943 0.0840 0.6016 vn 0.0193 -0.2656 0.9639 vn 0.0644 -0.2408 0.9684 vn -0.7075 -0.3245 0.6278 vn -0.7505 -0.2868 0.5954 vn -0.9817 -0.1902 0.0023 vn -0.9959 -0.0618 -0.0661 vn 0.1558 -0.1548 -0.9756 vn 0.7490 0.0587 -0.6599 vn 0.9841 0.1770 -0.0132 vn 0.7669 0.0595 0.6390 vn 0.2355 -0.1804 0.9550 vn 0.1797 0.2153 -0.9598 vn 0.7317 0.2497 -0.6342 vn 0.9853 0.1702 -0.0129 vn 0.7740 0.0499 0.6312 vn 0.2623 -0.0431 0.9640 vn 0.2038 0.3414 -0.9175 vn 0.7886 0.2376 -0.5671 vn 0.9967 0.0685 0.0432 vn 0.7622 -0.0278 0.6467 vn 0.2533 -0.0563 0.9657 vn -0.7148 -0.2085 -0.6674 vn 0.0137 -0.1017 -0.9947 vn 0.7429 0.0031 -0.6694 vn 0.9997 0.0232 -0.0000 vn 0.7467 -0.0530 0.6630 vn 0.0230 -0.1830 0.9828 vn -0.7042 -0.2631 0.6594 vn -0.9651 -0.2619 -0.0003 vn -0.9305 0.3442 0.1249 vn -0.7269 0.2687 0.6320 vn -0.6836 0.2431 0.6882 vn -0.9388 0.3246 0.1150 vn -0.8229 0.4189 -0.3837 vn -0.8218 0.3643 -0.4380 vn -0.4623 0.3872 -0.7977 vn -0.4250 0.3146 -0.8487 vn -0.3871 0.1594 0.9081 vn -0.4411 0.2060 0.8734 vn -0.1795 0.2589 -0.9490 vn -0.1585 0.2797 -0.9469 vn -0.6629 0.1725 0.7285 vn -0.9602 0.2648 0.0883 vn -0.8278 0.2795 -0.4864 vn -0.4296 0.2507 -0.8675 vn -0.3178 0.0812 0.9447 vn 0.0000 0.2257 -0.9742 vn -0.6545 0.1083 0.7482 vn -0.9778 0.1840 0.1006 vn -0.8214 0.2268 -0.5233 vn -0.3881 0.2433 -0.8889 vn -0.2732 0.0443 0.9609 vn 0.0000 0.2517 -0.9678 vn -0.6199 0.0893 0.7796 vn -0.9856 0.1431 0.0903 vn -0.8148 0.1839 -0.5498 vn -0.3740 0.2535 -0.8921 vn 0.0000 0.3010 -0.9536 vn -0.2541 0.0410 0.9663 vn -0.6076 0.0420 0.7931 vn -0.5980 -0.0347 0.8007 vn -0.9375 -0.2623 0.2285 vn -0.9684 -0.1241 0.2161 vn -0.7920 -0.4096 -0.4527 vn -0.8297 -0.3137 -0.4616 vn -0.2858 -0.3760 -0.8814 vn -0.2590 -0.2981 -0.9187 vn -0.2287 0.0641 0.9714 vn -0.2261 0.0876 0.9701 vn 0.0000 -0.2554 -0.9668 vn 0.0000 -0.3327 -0.9430 vn -0.5301 -0.0882 0.8433 vn -0.8493 -0.4104 0.3321 vn -0.7116 -0.6099 -0.3486 vn -0.2332 -0.4742 -0.8489 vn -0.2270 0.0645 0.9717 vn 0.0000 -0.3504 -0.9366 vn -0.5909 0.2845 0.7549 vn -0.7041 0.6731 0.2262 vn -0.6750 0.7067 0.2120 vn -0.5221 0.3539 0.7760 vn -0.4721 -0.3063 0.8266 vn -0.1414 -0.2192 0.9654 vn -0.4331 -0.7302 0.5285 vn 0.2356 -0.7827 0.5761 vn -0.3519 -0.9301 -0.1050 vn 0.3481 -0.9354 -0.0623 vn -0.2890 -0.5904 -0.7536 vn 0.1628 -0.5295 -0.8325 vn -0.4027 0.2159 -0.8895 vn -0.2563 0.2367 -0.9371 vn -0.6033 0.6231 -0.4978 vn -0.5584 0.6612 -0.5009 vn -0.2101 0.1086 0.9716 vn -0.4954 -0.0439 0.8675 vn -0.6827 -0.2557 0.6844 vn -0.5014 -0.5441 -0.6727 vn -0.1398 -0.2956 -0.9450 vn 0.0000 -0.1463 -0.9892 vn -0.5453 0.8124 0.2065 vn -0.4171 0.4386 0.7960 vn -0.1028 -0.1814 0.9780 vn 0.3036 -0.7578 0.5774 vn 0.4383 -0.8970 -0.0575 vn 0.1599 -0.5565 -0.8153 vn -0.2808 0.2088 -0.9368 vn -0.4873 0.6958 -0.5276 vn -0.6286 0.1609 0.7609 vn -0.3999 0.0846 -0.9127 vn -0.1346 0.1483 -0.9797 vn 0.0000 0.1400 -0.9901 vn -0.1455 0.3738 0.9160 vn -0.3840 0.3333 0.8610 vn -0.5499 0.7781 0.3034 vn -0.4931 0.7170 -0.4927 vn -0.0883 0.6438 0.7600 vn -0.3134 0.6461 0.6959 vn -0.1995 0.4476 -0.8717 vn 0.0000 0.3229 -0.9464 vn 0.8813 0.4724 -0.0088 vn 0.7941 0.4290 0.4305 vn -0.0864 0.9856 0.1451 vn 0.0280 0.9633 0.2670 vn 0.8173 0.5099 -0.2683 vn 0.8346 0.5002 -0.2308 vn 0.1703 0.9413 0.2913 vn 0.3046 0.8919 0.3341 vn 0.4281 0.3246 0.8434 vn 0.0000 0.2474 0.9689 vn 0.0000 0.9969 0.0780 vn -0.0873 0.9961 0.0095 vn -0.7519 0.6446 -0.1379 vn -0.7108 0.6391 0.2936 vn -0.4981 0.6461 0.5783 vn -0.3810 0.6395 0.6677 vn 0.0000 0.5987 -0.8009 vn -0.4424 0.6083 -0.6590 vn -0.3468 0.9035 0.2518 vn -0.3959 0.8547 -0.3358 vn -0.3311 0.9086 0.2546 vn -0.4184 0.8819 -0.2170 vn -0.1143 0.6544 0.7474 vn -0.2011 0.7588 0.6194 vn -0.0993 0.7308 0.6753 vn -0.1946 0.8158 0.5446 vn -0.2835 0.6131 -0.7374 vn 0.0000 0.4652 -0.8852 vn -0.2913 0.6623 -0.6902 vn 0.0000 0.5503 -0.8350 vn -0.5202 0.8349 0.1797 vn -0.2936 0.5365 0.7912 vn 0.0479 -0.0916 0.9946 vn 0.4032 -0.7122 0.5746 vn 0.4545 -0.8882 -0.0677 vn 0.1287 -0.5681 -0.8128 vn -0.3250 0.1772 -0.9290 vn -0.5206 0.6599 -0.5417 vn -0.5974 0.7709 0.2210 vn -0.2542 0.5503 0.7953 vn 0.1685 -0.0062 0.9857 vn 0.4398 -0.6721 0.5956 vn 0.3967 -0.9172 -0.0357 vn 0.0496 -0.6219 -0.7815 vn -0.4032 0.1236 -0.9067 vn -0.6385 0.5749 -0.5116 vn -0.5685 0.7945 0.2133 vn -0.2400 0.5868 0.7733 vn 0.2135 -0.0171 0.9768 vn 0.4756 -0.6876 0.5486 vn 0.4152 -0.9088 -0.0402 vn 0.0716 -0.6156 -0.7848 vn -0.3941 0.1769 -0.9019 vn -0.6174 0.6261 -0.4762 vn -0.4967 0.8361 0.2327 vn -0.1930 0.6250 0.7564 vn 0.2468 0.0142 0.9689 vn 0.5123 -0.6868 0.5157 vn 0.4545 -0.8877 -0.0728 vn 0.1032 -0.5467 -0.8309 vn -0.3741 0.2477 -0.8937 vn -0.5754 0.6948 -0.4315 vn -0.6893 0.6571 0.3049 vn -0.3672 0.4369 0.8211 vn -0.3961 0.3886 0.8319 vn -0.7006 0.6366 0.3222 vn 0.0527 -0.1498 0.9873 vn -0.0166 -0.2036 0.9789 vn 0.3432 -0.7831 0.5185 vn 0.2679 -0.8050 0.5293 vn 0.3268 -0.9419 -0.0771 vn 0.2604 -0.9629 -0.0707 vn -0.0390 -0.6161 -0.7867 vn -0.1134 -0.6437 -0.7568 vn -0.5172 0.1429 -0.8438 vn -0.5643 0.1326 -0.8148 vn -0.7468 0.5476 -0.3773 vn -0.7632 0.5405 -0.3542 vn -0.4188 0.3402 0.8419 vn -0.7050 0.6152 0.3528 vn -0.0831 -0.2227 0.9713 vn 0.1923 -0.7967 0.5729 vn 0.2002 -0.9780 -0.0580 vn -0.1858 -0.6495 -0.7373 vn -0.6026 0.1249 -0.7882 vn -0.7770 0.5413 -0.3211 vn 0.9305 0.3442 0.1249 vn 0.9388 0.3246 0.1150 vn 0.6836 0.2431 0.6882 vn 0.7269 0.2687 0.6320 vn 0.8229 0.4189 -0.3837 vn 0.8218 0.3643 -0.4380 vn 0.4623 0.3872 -0.7977 vn 0.4250 0.3146 -0.8487 vn 0.3871 0.1594 0.9081 vn 0.4411 0.2060 0.8734 vn 0.1585 0.2797 -0.9469 vn 0.1795 0.2589 -0.9490 vn 0.9602 0.2648 0.0883 vn 0.6629 0.1725 0.7285 vn 0.8278 0.2795 -0.4864 vn 0.4296 0.2507 -0.8675 vn 0.3178 0.0812 0.9447 vn 0.9778 0.1840 0.1006 vn 0.6545 0.1083 0.7482 vn 0.8214 0.2268 -0.5233 vn 0.3881 0.2433 -0.8889 vn 0.2732 0.0443 0.9609 vn 0.9856 0.1431 0.0903 vn 0.6199 0.0893 0.7796 vn 0.8148 0.1839 -0.5498 vn 0.3740 0.2535 -0.8921 vn 0.2541 0.0410 0.9663 vn 0.6076 0.0420 0.7931 vn 0.9684 -0.1241 0.2161 vn 0.9375 -0.2623 0.2285 vn 0.5980 -0.0347 0.8007 vn 0.8297 -0.3137 -0.4616 vn 0.7920 -0.4096 -0.4527 vn 0.2590 -0.2981 -0.9187 vn 0.2858 -0.3760 -0.8814 vn 0.2287 0.0641 0.9714 vn 0.2261 0.0876 0.9701 vn 0.8493 -0.4104 0.3321 vn 0.5301 -0.0882 0.8433 vn 0.7116 -0.6099 -0.3486 vn 0.2332 -0.4742 -0.8489 vn 0.2270 0.0645 0.9717 vn 0.5909 0.2845 0.7549 vn 0.5221 0.3539 0.7760 vn 0.6750 0.7067 0.2120 vn 0.7041 0.6731 0.2262 vn 0.4721 -0.3063 0.8266 vn 0.1414 -0.2192 0.9654 vn 0.4331 -0.7302 0.5285 vn -0.2356 -0.7827 0.5761 vn 0.3519 -0.9301 -0.1050 vn -0.3481 -0.9354 -0.0623 vn 0.2890 -0.5904 -0.7536 vn -0.1628 -0.5295 -0.8325 vn 0.4027 0.2159 -0.8895 vn 0.2563 0.2367 -0.9371 vn 0.6033 0.6231 -0.4978 vn 0.5584 0.6612 -0.5009 vn 0.2101 0.1086 0.9716 vn 0.4954 -0.0439 0.8675 vn 0.6827 -0.2557 0.6844 vn 0.5014 -0.5441 -0.6727 vn 0.1398 -0.2956 -0.9450 vn 0.4171 0.4386 0.7960 vn 0.5453 0.8124 0.2065 vn 0.1028 -0.1814 0.9780 vn -0.3036 -0.7578 0.5774 vn -0.4383 -0.8970 -0.0575 vn -0.1599 -0.5565 -0.8153 vn 0.2808 0.2088 -0.9368 vn 0.4873 0.6958 -0.5276 vn 0.6286 0.1609 0.7609 vn 0.3999 0.0846 -0.9127 vn 0.1346 0.1483 -0.9797 vn 0.1455 0.3738 0.9160 vn 0.3840 0.3333 0.8610 vn 0.4931 0.7170 -0.4927 vn 0.5499 0.7781 0.3034 vn 0.0883 0.6438 0.7600 vn 0.3134 0.6461 0.6959 vn 0.1995 0.4476 -0.8717 vn -0.8813 0.4724 -0.0088 vn -0.0280 0.9633 0.2670 vn 0.0864 0.9856 0.1451 vn -0.7941 0.4290 0.4305 vn -0.8173 0.5099 -0.2683 vn -0.3046 0.8919 0.3341 vn -0.1703 0.9413 0.2913 vn -0.8346 0.5002 -0.2308 vn -0.4281 0.3246 0.8434 vn 0.0873 0.9961 0.0095 vn 0.7108 0.6391 0.2936 vn 0.7519 0.6446 -0.1379 vn 0.3810 0.6395 0.6677 vn 0.4981 0.6461 0.5783 vn 0.4424 0.6083 -0.6590 vn 0.3959 0.8547 -0.3358 vn 0.3468 0.9035 0.2518 vn 0.4184 0.8819 -0.2170 vn 0.3311 0.9086 0.2546 vn 0.1143 0.6544 0.7474 vn 0.2011 0.7588 0.6194 vn 0.1946 0.8158 0.5446 vn 0.0993 0.7308 0.6753 vn 0.2835 0.6131 -0.7374 vn 0.2913 0.6623 -0.6902 vn 0.2936 0.5365 0.7912 vn 0.5202 0.8349 0.1797 vn -0.0479 -0.0916 0.9946 vn -0.4032 -0.7122 0.5746 vn -0.4545 -0.8882 -0.0677 vn -0.1287 -0.5681 -0.8128 vn 0.3250 0.1772 -0.9290 vn 0.5206 0.6599 -0.5417 vn 0.2542 0.5503 0.7953 vn 0.5974 0.7709 0.2210 vn -0.1685 -0.0062 0.9857 vn -0.4398 -0.6721 0.5956 vn -0.3967 -0.9172 -0.0357 vn -0.0496 -0.6219 -0.7815 vn 0.4032 0.1236 -0.9067 vn 0.6385 0.5749 -0.5116 vn 0.2400 0.5868 0.7733 vn 0.5685 0.7945 0.2133 vn -0.2135 -0.0171 0.9768 vn -0.4756 -0.6876 0.5486 vn -0.4152 -0.9088 -0.0402 vn -0.0716 -0.6156 -0.7848 vn 0.3941 0.1769 -0.9019 vn 0.6174 0.6261 -0.4762 vn 0.1930 0.6250 0.7564 vn 0.4967 0.8361 0.2327 vn -0.2468 0.0142 0.9689 vn -0.5123 -0.6868 0.5157 vn -0.4545 -0.8877 -0.0728 vn -0.1032 -0.5467 -0.8309 vn 0.3741 0.2477 -0.8937 vn 0.5754 0.6948 -0.4315 vn 0.6893 0.6571 0.3049 vn 0.7006 0.6366 0.3222 vn 0.3961 0.3886 0.8319 vn 0.3672 0.4369 0.8211 vn 0.0166 -0.2036 0.9789 vn -0.0527 -0.1498 0.9873 vn -0.2679 -0.8050 0.5293 vn -0.3432 -0.7831 0.5185 vn -0.2604 -0.9629 -0.0707 vn -0.3268 -0.9419 -0.0771 vn 0.1134 -0.6437 -0.7568 vn 0.0390 -0.6161 -0.7867 vn 0.5643 0.1326 -0.8148 vn 0.5172 0.1429 -0.8438 vn 0.7632 0.5405 -0.3542 vn 0.7468 0.5476 -0.3773 vn 0.7050 0.6152 0.3528 vn 0.4188 0.3402 0.8419 vn 0.0831 -0.2227 0.9713 vn -0.1923 -0.7967 0.5729 vn -0.2002 -0.9780 -0.0580 vn 0.1858 -0.6495 -0.7373 vn 0.6026 0.1249 -0.7882 vn 0.7770 0.5413 -0.3211 usemtl Cornea s 1 f 268/1/1 269/2/2 270/3/3 267/4/4 f 267/4/4 270/3/3 271/5/5 266/6/6 f 266/6/6 271/5/5 272/7/7 265/8/8 f 271/5/5 274/9/9 273/10/10 272/7/7 f 270/3/3 275/11/11 274/9/9 271/5/5 f 269/2/2 276/12/12 275/11/11 270/3/3 f 276/12/12 277/13/13 278/14/14 275/11/11 f 275/11/11 278/14/14 279/15/15 274/9/9 f 274/9/9 279/15/15 280/16/16 273/10/10 f 279/15/15 282/17/17 281/18/18 280/16/16 f 278/14/14 283/19/19 282/17/17 279/15/15 f 277/13/13 284/20/20 283/19/19 278/14/14 f 284/20/20 285/21/21 286/22/22 283/19/19 f 283/19/19 286/22/22 287/23/23 282/17/17 f 282/17/17 287/23/23 288/24/24 281/18/18 f 287/23/23 290/25/25 289/26/26 288/24/24 f 286/22/22 291/27/27 290/25/25 287/23/23 f 285/21/21 292/28/28 291/27/27 286/22/22 f 292/28/28 293/29/29 294/30/30 291/27/27 f 291/27/27 294/30/30 295/31/31 290/25/25 f 290/25/25 295/31/31 296/32/32 289/26/26 f 295/31/31 266/6/6 265/8/8 296/32/32 f 294/30/30 267/4/4 266/6/6 295/31/31 f 293/29/29 268/1/1 267/4/4 294/30/30 f 300/33/7 326/34/8 327/35/6 299/36/5 f 299/36/5 327/35/6 328/37/4 298/38/3 f 298/38/3 328/37/4 329/39/1 297/40/2 f 303/41/11 298/38/3 297/40/2 304/42/12 f 302/43/33 299/36/5 298/38/3 303/41/11 f 301/44/10 300/33/7 299/36/5 302/43/33 f 308/45/16 301/44/10 302/43/33 307/46/15 f 307/46/15 302/43/33 303/41/11 306/47/14 f 306/47/14 303/41/11 304/42/12 305/48/13 f 312/49/19 306/47/14 305/48/13 313/50/20 f 311/51/17 307/46/15 306/47/14 312/49/19 f 310/52/18 308/45/16 307/46/15 311/51/17 f 317/53/24 310/52/18 311/51/17 316/54/23 f 316/54/23 311/51/17 312/49/19 315/55/22 f 315/55/22 312/49/19 313/50/20 314/56/21 f 320/57/27 315/55/22 314/56/21 321/58/28 f 319/59/25 316/54/23 315/55/22 320/57/27 f 318/60/26 317/53/24 316/54/23 319/59/25 f 325/61/32 318/60/26 319/59/25 324/62/31 f 324/62/31 319/59/25 320/57/27 323/63/30 f 323/63/30 320/57/27 321/58/28 322/64/29 f 328/37/4 323/63/30 322/64/29 329/39/1 f 327/35/6 324/62/31 323/63/30 328/37/4 f 326/34/8 325/61/32 324/62/31 327/35/6 usemtl Iris f 272/7/7 264/65/34 265/8/8 f 273/10/10 264/65/34 272/7/7 f 280/16/16 264/65/34 273/10/10 f 281/18/18 264/65/34 280/16/16 f 288/24/24 264/65/34 281/18/18 f 289/26/26 264/65/34 288/24/24 f 296/32/32 264/65/34 289/26/26 f 265/8/8 264/65/34 296/32/32 f 309/66/34 326/34/8 300/33/7 f 309/66/34 300/33/7 301/44/10 f 309/66/34 301/44/10 308/45/16 f 309/66/34 308/45/16 310/52/18 f 309/66/34 310/52/18 317/53/24 f 309/66/34 317/53/24 318/60/26 f 309/66/34 318/60/26 325/61/32 f 309/66/34 325/61/32 326/34/8 usemtl Leather f 17/67/35 22/68/36 24/69/37 20/70/38 f 22/68/36 18/71/39 21/72/40 24/69/37 f 23/73/41 16/74/42 25/75/43 26/76/44 f 19/77/45 23/73/41 26/76/44 27/78/46 f 16/74/42 28/79/47 30/80/48 25/75/43 f 28/79/47 32/81/49 31/82/50 30/80/48 f 31/82/50 32/81/49 34/83/51 33/84/52 f 33/85/52 34/86/51 36/87/53 35/88/54 f 35/88/54 36/87/53 38/89/55 37/90/56 f 19/77/45 27/78/46 37/90/56 38/89/55 f 17/67/35 20/70/38 40/91/57 29/92/58 f 18/71/39 39/93/59 41/94/60 21/72/40 f 21/72/40 41/94/60 42/95/61 24/69/37 f 20/70/38 24/69/37 42/95/61 40/91/57 f 32/81/49 28/79/47 2661/96/62 2662/97/63 f 34/83/51 32/81/49 2662/97/63 2663/98/64 f 36/87/53 34/86/51 2663/99/64 2664/100/65 f 38/89/55 36/87/53 2664/100/65 2665/101/66 f 29/92/58 40/91/57 2667/102/67 2666/103/68 f 41/94/60 39/93/59 2668/104/69 2669/105/70 f 42/95/61 41/94/60 2669/105/70 2670/106/71 f 40/91/57 42/95/61 2670/106/71 2667/102/67 f 45/107/72 43/108/73 2671/109/74 2672/110/75 f 46/111/76 45/107/72 2672/110/75 2673/112/76 f 47/113/77 46/114/76 2673/115/76 2674/116/78 f 48/117/79 47/113/77 2674/116/78 2675/118/80 f 44/119/81 50/120/76 2677/121/76 2676/122/82 f 51/123/76 49/124/83 2678/125/84 2679/126/76 f 52/127/76 51/123/76 2679/126/76 2680/128/76 f 50/120/76 52/129/76 2680/130/76 2677/121/76 f 55/131/85 53/132/86 2681/133/87 2684/134/88 f 56/135/89 55/131/85 2684/134/88 2685/136/89 f 57/137/90 56/138/89 2685/139/89 2686/140/91 f 58/141/92 57/137/90 2686/140/91 2687/142/93 f 54/143/94 60/144/95 2691/145/96 2690/146/97 f 61/147/98 59/148/99 2692/149/100 2693/150/101 f 62/151/102 61/147/98 2693/150/101 2694/152/102 f 60/144/95 62/153/102 2694/152/102 2691/145/96 f 65/154/103 2682/155/104 2695/156/104 2697/157/105 f 66/158/89 65/154/103 2697/157/105 2698/159/89 f 67/160/106 66/161/89 2698/159/89 2699/162/107 f 2688/163/108 67/160/106 2699/162/107 2700/164/108 f 2696/165/109 2701/166/109 76/167/110 74/168/111 f 74/168/111 76/167/110 75/169/112 f 2683/170/113 2689/171/113 77/172/113 73/173/113 f 63/174/114 70/175/115 71/176/116 69/177/117 f 70/175/115 72/178/112 71/176/116 f 78/179/118 16/74/42 23/73/41 80/180/119 f 80/180/119 22/68/36 17/67/35 78/179/118 f 19/77/45 79/181/120 80/180/119 23/73/41 f 79/181/120 18/71/39 22/68/36 80/180/119 f 16/74/42 78/179/118 81/182/121 28/79/47 f 78/179/118 17/67/35 29/92/58 81/182/121 f 18/71/39 79/181/120 82/183/122 39/93/59 f 79/181/120 19/77/45 38/89/55 82/183/122 f 28/79/47 81/182/121 2702/184/123 2661/96/62 f 81/182/121 29/92/58 2666/103/68 2702/184/123 f 39/93/59 82/183/122 2703/185/124 2668/104/69 f 82/183/122 38/89/55 2665/101/66 2703/185/124 f 43/108/73 83/186/125 2704/187/126 2671/109/74 f 83/186/125 44/119/81 2676/122/82 2704/187/126 f 49/124/83 84/188/127 2705/189/128 2678/125/84 f 84/188/127 48/117/79 2675/118/80 2705/189/128 f 53/132/86 85/190/129 2706/191/130 2681/133/87 f 85/190/129 54/143/94 2690/146/97 2706/191/130 f 59/148/99 86/192/131 2707/193/132 2692/149/100 f 86/192/131 58/141/92 2687/142/93 2707/193/132 f 69/177/117 88/194/133 87/195/134 63/174/114 f 88/194/133 68/196/135 64/197/135 87/195/134 f 26/76/44 25/75/43 89/198/136 90/199/137 f 27/78/46 26/76/44 90/199/137 91/200/138 f 25/75/43 30/80/48 92/201/139 89/198/136 f 30/80/48 31/82/50 93/202/140 92/201/139 f 31/82/50 33/84/52 94/203/141 93/202/140 f 33/85/52 35/88/54 95/204/142 94/205/141 f 35/88/54 37/90/56 96/206/143 95/204/142 f 37/90/56 27/78/46 91/200/138 96/206/143 f 90/199/137 89/198/136 98/207/144 97/208/145 f 91/200/138 90/199/137 97/208/145 99/209/146 f 89/198/136 92/201/139 100/210/147 98/207/144 f 92/201/139 93/202/140 101/211/148 100/210/147 f 93/202/140 94/203/141 102/212/149 101/211/148 f 94/205/141 95/204/142 103/213/150 102/214/149 f 95/204/142 96/206/143 104/215/151 103/213/150 f 96/206/143 91/200/138 99/209/146 104/215/151 f 97/208/145 98/207/144 106/216/152 105/217/153 f 99/209/146 97/208/145 105/217/153 107/218/154 f 98/207/144 100/210/147 108/219/155 106/216/152 f 100/210/147 101/211/148 109/220/156 108/219/155 f 101/211/148 102/212/149 110/221/157 109/220/156 f 102/214/149 103/213/150 111/222/158 110/223/157 f 103/213/150 104/215/151 112/224/159 111/222/158 f 104/215/151 99/209/146 107/218/154 112/224/159 f 154/225/160 116/226/161 117/227/162 151/228/163 f 154/225/160 153/229/164 118/230/165 116/226/161 f 153/229/164 2708/231/166 119/232/167 118/230/165 f 2708/233/166 2709/234/168 120/235/169 119/236/167 f 2709/234/168 152/237/170 121/238/171 120/235/169 f 152/237/170 150/239/172 122/240/173 121/238/171 f 151/228/163 117/227/162 123/241/174 2715/242/175 f 150/239/172 2715/242/175 123/241/174 122/240/173 f 116/226/161 124/243/176 125/244/177 117/227/162 f 116/226/161 118/230/165 126/245/178 124/243/176 f 118/230/165 119/232/167 127/246/179 126/245/178 f 119/236/167 120/235/169 128/247/180 127/248/179 f 120/235/169 121/238/171 129/249/181 128/247/180 f 121/238/171 122/240/173 130/250/182 129/249/181 f 117/227/162 125/244/177 131/251/183 123/241/174 f 122/240/173 123/241/174 131/251/183 130/250/182 f 124/243/176 142/252/184 141/253/185 125/244/177 f 142/252/184 132/254/186 133/255/187 141/253/185 f 126/245/178 143/256/188 142/252/184 124/243/176 f 143/256/188 134/257/189 132/254/186 142/252/184 f 129/249/181 145/258/190 144/259/191 128/247/180 f 145/258/190 136/260/192 135/261/193 144/259/191 f 130/250/182 146/262/194 145/258/190 129/249/181 f 146/262/194 137/263/195 136/260/192 145/258/190 f 125/244/177 141/253/185 147/264/196 131/251/183 f 141/253/185 133/255/187 138/265/197 147/264/196 f 131/251/183 147/264/196 146/262/194 130/250/182 f 147/264/196 138/265/197 137/263/195 146/262/194 f 128/247/180 144/259/191 148/266/198 127/248/179 f 144/259/191 135/261/193 139/267/199 148/266/198 f 127/246/179 149/268/200 143/256/188 126/245/178 f 149/268/200 140/269/201 134/257/189 143/256/188 f 105/217/153 106/216/152 2710/270/202 115/271/203 f 107/218/154 105/217/153 115/271/203 2714/272/204 f 106/216/152 108/219/155 2711/273/205 2710/270/202 f 108/219/155 109/220/156 114/274/206 2711/273/205 f 109/220/156 110/221/157 113/275/207 114/274/206 f 110/223/157 111/222/158 2712/276/208 113/277/207 f 111/222/158 112/224/159 2713/278/209 2712/276/208 f 112/224/159 107/218/154 2714/272/204 2713/278/209 f 173/279/210 174/280/211 156/281/212 155/282/213 f 157/283/214 156/281/212 174/280/211 175/284/215 f 158/285/216 157/283/214 175/284/215 176/286/217 f 159/287/218 158/285/216 176/286/217 177/288/219 f 160/289/220 159/287/218 177/288/219 178/290/221 f 161/291/222 160/289/220 178/290/221 179/292/223 f 162/293/224 161/291/222 179/292/223 180/294/225 f 163/295/226 162/293/224 180/294/225 181/296/227 f 164/297/228 163/295/226 181/296/227 182/298/229 f 165/299/230 164/297/228 182/298/229 183/300/231 f 166/301/232 165/299/230 183/300/231 184/302/233 f 167/303/234 166/301/232 184/302/233 185/304/235 f 168/305/236 167/303/234 185/304/235 186/306/237 f 169/307/238 168/305/236 186/306/237 187/308/239 f 170/309/240 169/307/238 187/308/239 188/310/241 f 171/311/242 170/309/240 188/310/241 189/312/243 f 172/313/244 171/311/242 189/312/243 190/314/245 f 172/313/244 190/314/245 173/279/210 155/282/213 f 174/280/211 173/279/210 192/315/246 191/316/247 f 175/284/215 174/280/211 191/316/247 193/317/248 f 176/286/217 175/284/215 193/317/248 194/318/249 f 177/288/219 176/286/217 194/318/249 195/319/250 f 178/290/221 177/288/219 195/319/250 196/320/251 f 179/292/223 178/290/221 196/320/251 197/321/252 f 180/294/225 179/292/223 197/321/252 198/322/253 f 181/296/227 180/294/225 198/322/253 199/323/254 f 182/298/229 181/296/227 199/323/254 200/324/255 f 183/300/231 182/298/229 200/324/255 201/325/256 f 184/302/233 183/300/231 201/325/256 202/326/257 f 185/304/235 184/302/233 202/326/257 203/327/258 f 186/306/237 185/304/235 203/327/258 204/328/259 f 187/308/239 186/306/237 204/328/259 205/329/260 f 188/310/241 187/308/239 205/329/260 206/330/261 f 189/312/243 188/310/241 206/330/261 207/331/262 f 190/314/245 189/312/243 207/331/262 208/332/263 f 190/314/245 208/332/263 192/315/246 173/279/210 f 191/316/247 192/315/246 209/333/264 f 193/317/248 191/316/247 209/333/264 f 194/318/249 193/317/248 209/333/264 f 195/319/250 194/318/249 209/333/264 f 196/320/251 195/319/250 209/333/264 f 197/321/252 196/320/251 209/333/264 f 198/322/253 197/321/252 209/333/264 f 199/323/254 198/322/253 209/333/264 f 200/324/255 199/323/254 209/333/264 f 201/325/256 200/324/255 209/333/264 f 202/326/257 201/325/256 209/333/264 f 203/327/258 202/326/257 209/333/264 f 204/328/259 203/327/258 209/333/264 f 205/329/260 204/328/259 209/333/264 f 206/330/261 205/329/260 209/333/264 f 207/331/262 206/330/261 209/333/264 f 208/332/263 207/331/262 209/333/264 f 208/332/263 209/333/264 192/315/246 f 170/309/240 171/311/242 220/334/265 221/335/266 f 169/307/238 170/309/240 221/335/266 222/336/267 f 168/305/236 169/307/238 222/336/267 213/337/268 f 171/311/242 172/313/244 219/338/269 220/334/265 f 155/282/213 218/339/270 219/338/269 172/313/244 f 155/282/213 156/281/212 211/340/271 218/339/270 f 156/281/212 157/283/214 223/341/272 211/340/271 f 157/283/214 158/285/216 224/342/273 223/341/272 f 158/285/216 159/287/218 225/343/274 224/342/273 f 159/287/218 160/289/220 226/344/275 225/343/274 f 160/289/220 161/291/222 227/345/276 226/344/275 f 161/291/222 162/293/224 212/346/277 227/345/276 f 162/293/224 163/295/226 228/347/278 212/346/277 f 163/295/226 164/297/228 229/348/279 228/347/278 f 164/297/228 165/299/230 230/349/280 229/348/279 f 165/299/230 166/301/232 231/350/281 230/349/280 f 166/301/232 167/303/234 232/351/282 231/350/281 f 167/303/234 168/305/236 213/337/268 232/351/282 f 235/352/283 234/353/284 214/354/285 f 222/336/267 213/337/268 234/353/284 235/352/283 f 214/354/285 215/355/286 236/356/287 235/352/283 f 235/352/283 236/356/287 221/335/266 222/336/267 f 215/355/286 216/357/288 237/358/289 236/356/287 f 236/356/287 237/358/289 220/334/265 221/335/266 f 216/357/288 217/359/290 238/360/291 237/358/289 f 237/358/289 238/360/291 219/338/269 220/334/265 f 217/359/290 210/361/292 239/362/293 238/360/291 f 238/360/291 239/362/293 218/339/270 219/338/269 f 233/363/294 239/362/293 210/361/292 f 211/340/271 218/339/270 239/362/293 233/363/294 f 212/346/277 227/345/276 240/364/295 246/365/296 f 246/365/296 240/364/295 251/366/297 f 241/367/298 240/364/295 227/345/276 226/344/275 f 247/368/299 251/366/297 240/364/295 241/367/298 f 242/369/300 241/367/298 226/344/275 225/343/274 f 248/370/301 247/368/299 241/367/298 242/369/300 f 243/371/302 242/369/300 225/343/274 224/342/273 f 249/372/303 248/370/301 242/369/300 243/371/302 f 244/373/304 243/371/302 224/342/273 223/341/272 f 250/374/305 249/372/303 243/371/302 244/373/304 f 223/341/272 211/340/271 245/375/306 244/373/304 f 244/373/304 245/375/306 250/374/305 f 259/376/307 258/377/308 253/378/309 f 228/347/278 212/346/277 258/377/308 259/376/307 f 253/378/309 254/379/310 260/380/311 259/376/307 f 259/376/307 260/380/311 229/348/279 228/347/278 f 254/379/310 255/381/312 261/382/313 260/380/311 f 260/380/311 261/382/313 230/349/280 229/348/279 f 255/381/312 256/383/314 262/384/315 261/382/313 f 261/382/313 262/384/315 231/350/281 230/349/280 f 256/383/314 252/385/316 263/386/317 262/384/315 f 262/384/315 263/386/317 232/351/282 231/350/281 f 257/387/318 263/386/317 252/385/316 f 213/337/268 232/351/282 263/386/317 257/387/318 f 2717/388/319 2716/389/320 2740/390/321 2741/391/322 f 2718/392/323 2717/388/319 2741/391/322 2742/393/324 f 2718/392/323 2742/393/324 2743/394/325 2719/395/326 f 2719/395/326 2743/394/325 2744/396/327 2720/397/328 f 2721/398/329 2720/397/328 2744/396/327 2745/399/330 f 2721/398/329 2745/399/330 2746/400/331 2722/401/332 f 2722/401/332 2746/400/331 2747/402/333 2723/403/334 f 2723/403/334 2747/402/333 2748/404/335 2724/405/336 f 2724/405/336 2748/404/335 2749/406/337 2725/407/338 f 2726/408/339 2725/407/338 2749/406/337 2750/409/340 f 2727/410/341 2726/411/339 2750/412/340 2751/413/342 f 2728/414/343 2727/410/341 2751/413/342 2752/415/344 f 2728/414/343 2752/415/344 2753/416/345 2729/417/346 f 2729/417/346 2753/416/345 2754/418/347 2730/419/348 f 2730/419/348 2754/418/347 2755/420/349 2731/421/350 f 2732/422/351 2731/421/350 2755/420/349 2756/423/352 f 2732/422/351 2756/423/352 2757/424/353 2733/425/354 f 2734/426/355 2733/425/354 2757/424/353 2758/427/356 f 2735/428/357 2734/426/355 2758/427/356 2759/429/358 f 2736/430/359 2735/428/357 2759/429/358 2760/431/360 f 2736/430/359 2760/431/360 2761/432/361 2737/433/362 f 2738/434/363 2737/433/362 2761/432/361 2762/435/364 f 2739/436/365 2738/434/363 2762/435/364 2763/437/366 f 2740/390/321 2716/389/320 2739/436/365 2763/437/366 f 500/438/367 499/439/368 523/440/369 524/441/370 f 501/442/371 500/438/367 524/441/370 525/443/372 f 502/444/373 501/442/371 525/443/372 526/445/374 f 503/446/375 502/444/373 526/445/374 527/447/376 f 504/448/377 503/446/375 527/447/376 528/449/378 f 505/450/379 504/448/377 528/449/378 529/451/380 f 506/452/381 505/450/379 529/451/380 530/453/382 f 507/454/383 506/452/381 530/453/382 531/455/384 f 508/456/385 507/454/383 531/455/384 532/457/386 f 509/458/387 508/456/385 532/457/386 533/459/388 f 510/460/389 509/461/387 533/462/388 534/463/390 f 511/464/391 510/460/389 534/463/390 535/465/392 f 512/466/393 511/464/391 535/465/392 536/467/393 f 513/468/394 512/466/393 536/467/393 537/469/395 f 514/470/396 513/468/394 537/469/395 538/471/397 f 515/472/398 514/470/396 538/471/397 539/473/399 f 516/474/400 515/472/398 539/473/399 540/475/401 f 517/476/402 516/474/400 540/475/401 541/477/403 f 518/478/404 517/476/402 541/477/403 542/479/405 f 519/480/406 518/478/404 542/479/405 543/481/407 f 520/482/408 519/480/406 543/481/407 544/483/409 f 521/484/410 520/482/408 544/483/409 545/485/411 f 522/486/412 521/484/410 545/485/411 546/487/413 f 499/439/368 522/486/412 546/487/413 523/440/369 f 475/488/414 476/489/415 548/490/416 547/491/417 f 476/489/415 477/492/418 549/493/419 548/490/416 f 477/492/418 478/494/420 550/495/421 549/493/419 f 478/494/420 479/496/422 551/497/423 550/495/421 f 479/496/422 480/498/424 552/499/425 551/497/423 f 480/498/424 481/500/426 553/501/427 552/499/425 f 481/500/426 482/502/428 554/503/429 553/501/427 f 482/502/428 483/504/430 555/505/431 554/503/429 f 483/504/430 484/506/432 556/507/433 555/505/431 f 484/506/432 485/508/434 557/509/435 556/507/433 f 485/510/434 486/511/436 558/512/437 557/513/435 f 486/511/436 487/514/438 559/515/439 558/512/437 f 487/514/438 488/516/440 560/517/441 559/515/439 f 488/516/440 489/518/442 561/519/443 560/517/441 f 489/518/442 490/520/444 562/521/445 561/519/443 f 490/520/444 491/522/446 563/523/447 562/521/445 f 491/522/446 492/524/448 564/525/449 563/523/447 f 492/524/448 493/526/450 565/527/451 564/525/449 f 493/526/450 494/528/452 566/529/453 565/527/451 f 494/528/452 495/530/454 567/531/455 566/529/453 f 495/530/454 496/532/456 568/533/457 567/531/455 f 496/532/456 497/534/458 569/535/459 568/533/457 f 497/534/458 498/536/460 570/537/461 569/535/459 f 498/536/460 475/488/414 547/491/417 570/537/461 f 572/538/462 575/539/463 579/540/464 577/541/465 f 577/541/465 579/540/464 576/542/466 573/543/467 f 580/544/468 571/545/469 578/546/470 581/547/471 f 574/548/472 582/549/473 581/547/471 578/546/470 f 571/545/469 580/544/468 585/550/474 583/551/475 f 583/551/475 585/550/474 586/552/476 587/553/477 f 586/552/476 588/554/478 589/555/479 587/553/477 f 588/556/478 590/557/480 591/558/481 589/559/479 f 590/557/480 592/560/482 593/561/483 591/558/481 f 574/548/472 593/561/483 592/560/482 582/549/473 f 572/538/462 584/562/484 595/563/485 575/539/463 f 573/543/467 576/542/466 596/564/486 594/565/487 f 576/542/466 579/540/464 597/566/488 596/564/486 f 575/539/463 595/563/485 597/566/488 579/540/464 f 587/553/477 2765/567/489 2764/568/490 583/551/475 f 589/555/479 2766/569/491 2765/567/489 587/553/477 f 591/558/481 2767/570/492 2766/571/491 589/559/479 f 593/561/483 2768/572/493 2767/570/492 591/558/481 f 584/562/484 2769/573/494 2770/574/495 595/563/485 f 596/564/486 2772/575/496 2771/576/497 594/565/487 f 597/566/488 2773/577/498 2772/575/496 596/564/486 f 595/563/485 2770/574/495 2773/577/498 597/566/488 f 600/578/499 2775/579/500 2774/580/501 598/581/502 f 601/582/76 2776/583/76 2775/579/500 600/578/499 f 602/584/503 2777/585/504 2776/586/76 601/587/76 f 603/588/505 2778/589/506 2777/585/504 602/584/503 f 599/590/507 2779/591/508 2780/592/76 605/593/76 f 606/594/76 2782/595/76 2781/596/509 604/597/510 f 607/598/76 2783/599/76 2782/595/76 606/594/76 f 605/593/76 2780/592/76 2783/600/76 607/601/76 f 610/602/511 2787/603/512 2784/604/513 608/605/514 f 611/606/515 2788/607/515 2787/603/512 610/602/511 f 612/608/516 2789/609/517 2788/610/515 611/611/515 f 613/612/518 2790/613/519 2789/609/517 612/608/516 f 609/614/520 2793/615/521 2794/616/522 615/617/523 f 616/618/524 2796/619/525 2795/620/526 614/621/527 f 617/622/528 2797/623/528 2796/619/525 616/618/524 f 615/617/523 2794/616/522 2797/623/528 617/624/528 f 620/625/529 2800/626/530 2798/627/531 2785/628/531 f 621/629/515 2801/630/515 2800/626/530 620/625/529 f 622/631/532 2802/632/533 2801/630/515 621/633/515 f 2791/634/534 2803/635/534 2802/632/533 622/631/532 f 2799/636/535 629/637/536 631/638/537 2804/639/535 f 629/637/536 630/640/112 631/638/537 f 2786/641/538 628/642/538 632/643/538 2792/644/538 f 618/645/539 624/646/540 626/647/541 625/648/542 f 625/648/542 626/647/541 627/649/112 f 578/546/470 571/545/469 633/650/543 635/651/544 f 635/651/544 633/650/543 572/538/462 577/541/465 f 574/548/472 578/546/470 635/651/544 634/652/545 f 634/652/545 635/651/544 577/541/465 573/543/467 f 571/545/469 583/551/475 636/653/546 633/650/543 f 633/650/543 636/653/546 584/562/484 572/538/462 f 573/543/467 594/565/487 637/654/547 634/652/545 f 634/652/545 637/654/547 593/561/483 574/548/472 f 583/551/475 2764/568/490 2805/655/548 636/653/546 f 636/653/546 2805/655/548 2769/573/494 584/562/484 f 594/565/487 2771/576/497 2806/656/549 637/654/547 f 637/654/547 2806/656/549 2768/572/493 593/561/483 f 598/581/502 2774/580/501 2807/657/550 638/658/551 f 638/658/551 2807/657/550 2779/591/508 599/590/507 f 604/597/510 2781/596/509 2808/659/552 639/660/553 f 639/660/553 2808/659/552 2778/589/506 603/588/505 f 608/605/514 2784/604/513 2809/661/554 640/662/555 f 640/662/555 2809/661/554 2793/615/521 609/614/520 f 614/621/527 2795/620/526 2810/663/556 641/664/557 f 641/664/557 2810/663/556 2790/613/519 613/612/518 f 624/646/540 618/645/539 642/665/558 643/666/559 f 643/666/559 642/665/558 619/667/560 623/668/560 f 581/547/471 645/669/561 644/670/562 580/544/468 f 582/549/473 646/671/563 645/669/561 581/547/471 f 580/544/468 644/670/562 647/672/564 585/550/474 f 585/550/474 647/672/564 648/673/565 586/552/476 f 586/552/476 648/673/565 649/674/566 588/554/478 f 588/556/478 649/675/566 650/676/567 590/557/480 f 590/557/480 650/676/567 651/677/568 592/560/482 f 592/560/482 651/677/568 646/671/563 582/549/473 f 645/669/561 652/678/569 653/679/570 644/670/562 f 646/671/563 654/680/571 652/678/569 645/669/561 f 644/670/562 653/679/570 655/681/572 647/672/564 f 647/672/564 655/681/572 656/682/573 648/673/565 f 648/673/565 656/682/573 657/683/574 649/674/566 f 649/675/566 657/684/574 658/685/575 650/676/567 f 650/676/567 658/685/575 659/686/576 651/677/568 f 651/677/568 659/686/576 654/680/571 646/671/563 f 652/678/569 660/687/577 661/688/578 653/679/570 f 654/680/571 662/689/579 660/687/577 652/678/569 f 653/679/570 661/688/578 663/690/580 655/681/572 f 655/681/572 663/690/580 664/691/581 656/682/573 f 656/682/573 664/691/581 665/692/582 657/683/574 f 657/684/574 665/693/582 666/694/583 658/685/575 f 658/685/575 666/694/583 667/695/584 659/686/576 f 659/686/576 667/695/584 662/689/579 654/680/571 f 709/696/585 706/697/586 672/698/587 671/699/588 f 709/696/585 671/699/588 673/700/589 708/701/590 f 708/701/590 673/700/589 674/702/591 2811/703/592 f 2811/704/592 674/705/591 675/706/593 2812/707/594 f 2812/707/594 675/706/593 676/708/595 707/709/596 f 707/709/596 676/708/595 677/710/597 705/711/598 f 706/697/586 2818/712/599 678/713/600 672/698/587 f 705/711/598 677/710/597 678/713/600 2818/712/599 f 671/699/588 672/698/587 680/714/601 679/715/602 f 671/699/588 679/715/602 681/716/603 673/700/589 f 673/700/589 681/716/603 682/717/604 674/702/591 f 674/705/591 682/718/604 683/719/605 675/706/593 f 675/706/593 683/719/605 684/720/606 676/708/595 f 676/708/595 684/720/606 685/721/607 677/710/597 f 672/698/587 678/713/600 686/722/608 680/714/601 f 677/710/597 685/721/607 686/722/608 678/713/600 f 679/715/602 680/714/601 696/723/609 697/724/610 f 697/724/610 696/723/609 688/725/611 687/726/612 f 681/716/603 679/715/602 697/724/610 698/727/613 f 698/727/613 697/724/610 687/726/612 689/728/614 f 684/720/606 683/719/605 699/729/615 700/730/616 f 700/730/616 699/729/615 690/731/617 691/732/618 f 685/721/607 684/720/606 700/730/616 701/733/619 f 701/733/619 700/730/616 691/732/618 692/734/620 f 680/714/601 686/722/608 702/735/621 696/723/609 f 696/723/609 702/735/621 693/736/622 688/725/611 f 686/722/608 685/721/607 701/733/619 702/735/621 f 702/735/621 701/733/619 692/734/620 693/736/622 f 683/719/605 682/718/604 703/737/623 699/729/615 f 699/729/615 703/737/623 694/738/624 690/731/617 f 682/717/604 681/716/603 698/727/613 704/739/625 f 704/739/625 698/727/613 689/728/614 695/740/626 f 660/687/577 670/741/627 2813/742/628 661/688/578 f 662/689/579 2817/743/629 670/741/627 660/687/577 f 661/688/578 2813/742/628 2814/744/630 663/690/580 f 663/690/580 2814/744/630 669/745/631 664/691/581 f 664/691/581 669/745/631 668/746/632 665/692/582 f 665/693/582 668/747/632 2815/748/633 666/694/583 f 666/694/583 2815/748/633 2816/749/634 667/695/584 f 667/695/584 2816/749/634 2817/743/629 662/689/579 f 902/750/112 728/751/112 2869/752/112 2868/753/112 f 730/754/76 906/755/76 2870/756/76 2871/757/76 f 903/758/76 873/759/76 2873/760/76 2872/761/76 f 906/755/76 905/762/76 2874/763/76 2870/756/76 f 905/762/76 904/764/76 2875/765/76 2874/763/76 f 904/764/76 903/758/76 2872/761/76 2875/765/76 f 874/766/112 899/767/112 2876/768/112 2877/769/112 f 901/770/112 902/750/112 2868/753/112 2878/771/112 f 900/772/112 901/770/112 2878/771/112 2879/773/112 f 899/767/112 900/772/112 2879/773/112 2876/768/112 f 753/774/76 730/754/76 2871/757/76 2880/775/76 f 715/776/76 750/777/76 2881/778/76 2882/779/76 f 752/780/76 753/774/76 2880/775/76 2883/781/76 f 751/782/76 752/780/76 2883/781/76 2884/783/76 f 750/777/76 751/782/76 2884/783/76 2881/778/76 f 746/784/112 716/785/112 2886/786/112 2885/787/112 f 728/751/112 749/788/112 2887/789/112 2869/752/112 f 749/788/112 748/790/112 2888/791/112 2887/789/112 f 748/790/112 747/792/112 2889/793/112 2888/791/112 f 747/792/112 746/784/112 2885/787/112 2889/793/112 f 1038/794/635 1034/795/636 1033/796/637 1037/797/638 f 1037/797/638 1033/796/637 1032/798/639 1036/799/640 f 1036/799/640 1032/798/639 1031/800/641 1035/801/642 f 1042/802/643 1034/795/636 1038/794/635 1041/803/644 f 1040/804/645 1035/801/642 1031/800/641 1039/805/645 f 1028/806/646 1027/807/647 1023/808/648 1024/809/649 f 1027/807/647 1026/810/650 1022/811/651 1023/808/648 f 1026/810/650 1025/812/652 1021/813/653 1022/811/651 f 1030/814/654 1029/815/655 1028/806/646 1024/809/649 f 1040/804/645 1039/805/645 1021/813/653 1025/812/652 f 715/776/76 2882/779/76 2873/816/76 873/817/76 f 716/785/112 874/818/112 2877/819/112 2886/786/112 f 1029/820/655 1030/821/654 1042/802/643 1041/803/644 usemtl WhiteCloth f 1208/822/656 1207/823/657 1085/824/658 1145/825/659 f 1165/826/660 1164/827/661 1189/828/662 1188/829/663 f 1234/830/664 1233/831/665 1210/832/666 1209/833/667 f 1144/834/668 1059/835/669 1231/836/670 1232/837/671 f 1096/838/672 1095/839/673 1211/840/674 1212/841/675 f 1095/839/673 1136/842/676 1210/832/666 1211/840/674 f 1084/843/677 1163/844/678 1186/845/679 1187/846/680 f 1085/824/658 1084/843/677 1187/846/680 1188/829/663 f 1145/825/659 1085/824/658 1188/829/663 1189/828/662 f 1187/846/680 1186/845/679 1167/847/681 1166/848/682 f 1192/849/683 1191/850/684 1146/851/685 1147/852/686 f 1191/850/684 1190/853/687 1140/854/688 1146/851/685 f 1144/834/668 1142/855/689 1190/853/687 1191/850/684 f 1059/835/669 1144/834/668 1191/850/684 1192/849/683 f 1190/853/687 1141/856/690 1086/857/691 1140/854/688 f 1206/858/692 1205/859/693 1163/844/678 1084/843/677 f 1207/823/657 1206/858/692 1084/843/677 1085/824/658 f 1147/852/686 1146/851/685 1166/860/682 1167/861/681 f 1146/851/685 1140/854/688 1165/862/660 1166/860/682 f 1142/855/689 1087/863/694 1141/856/690 1190/853/687 f 1095/839/673 1096/838/672 1205/859/693 1206/858/692 f 1137/864/695 1136/842/676 1207/823/657 1208/822/656 f 1136/842/676 1095/839/673 1206/858/692 1207/823/657 f 1136/842/676 1137/864/695 1209/833/667 1210/832/666 f 1142/855/689 1144/834/668 1232/837/671 1233/831/665 f 1087/863/694 1142/855/689 1233/831/665 1234/830/664 f 1140/854/688 1086/857/691 1164/865/661 1165/862/660 f 1212/841/675 1211/840/674 1232/837/671 1231/836/670 f 1211/840/674 1210/832/666 1233/831/665 1232/837/671 f 1165/826/660 1188/829/663 1187/846/680 1166/848/682 f 1424/866/696 1361/867/697 1301/868/698 1423/869/699 f 1381/870/700 1404/871/701 1405/872/702 1380/873/703 f 1450/874/704 1425/875/705 1426/876/706 1449/877/707 f 1360/878/708 1448/879/709 1447/880/710 1275/881/711 f 1312/882/712 1428/883/713 1427/884/714 1311/885/715 f 1311/885/715 1427/884/714 1426/876/706 1352/886/716 f 1300/887/717 1403/888/718 1402/889/719 1379/890/720 f 1301/868/698 1404/871/701 1403/888/718 1300/887/717 f 1361/867/697 1405/872/702 1404/871/701 1301/868/698 f 1403/888/718 1382/891/721 1383/892/722 1402/889/719 f 1408/893/723 1363/894/724 1362/895/725 1407/896/726 f 1407/896/726 1362/895/725 1356/897/727 1406/898/728 f 1360/878/708 1407/896/726 1406/898/728 1358/899/729 f 1275/881/711 1408/893/723 1407/896/726 1360/878/708 f 1406/898/728 1356/897/727 1302/900/730 1357/901/731 f 1422/902/732 1300/887/717 1379/890/720 1421/903/733 f 1423/869/699 1301/868/698 1300/887/717 1422/902/732 f 1363/894/724 1383/904/722 1382/905/721 1362/895/725 f 1362/895/725 1382/905/721 1381/906/700 1356/897/727 f 1358/899/729 1406/898/728 1357/901/731 1303/907/734 f 1311/885/715 1422/902/732 1421/903/733 1312/882/712 f 1353/908/735 1424/866/696 1423/869/699 1352/886/716 f 1352/886/716 1423/869/699 1422/902/732 1311/885/715 f 1352/886/716 1426/876/706 1425/875/705 1353/908/735 f 1358/899/729 1449/877/707 1448/879/709 1360/878/708 f 1303/907/734 1450/874/704 1449/877/707 1358/899/729 f 1356/897/727 1381/906/700 1380/909/703 1302/900/730 f 1428/883/713 1447/880/710 1448/879/709 1427/884/714 f 1427/884/714 1448/879/709 1449/877/707 1426/876/706 f 1381/870/700 1382/891/721 1403/888/718 1404/871/701 usemtl WhiteCloth_NONE f 2/910/736 1/911/737 4/912/738 5/913/739 f 5/913/739 6/914/740 3/915/741 2/910/736 f 7/916/742 8/917/743 5/913/739 4/912/738 f 8/917/743 9/918/744 6/914/740 5/913/739 f 10/919/745 11/920/746 8/917/743 7/916/742 f 11/920/746 12/921/747 9/918/744 8/917/743 f 15/922/748 13/923/749 11/920/746 10/919/745 f 13/923/749 14/924/750 12/921/747 11/920/746 usemtl Skin f 1093/925/751 1058/926/752 1088/927/753 1118/928/754 f 1059/929/669 1089/930/755 1230/931/756 1231/932/670 f 1230/931/756 1089/930/755 1242/933/757 1243/934/758 f 1091/935/759 1060/936/760 1058/926/752 1093/925/751 f 1091/937/759 1061/938/761 1103/939/762 1102/940/763 f 1061/941/761 1091/935/759 1093/925/751 1193/942/764 f 1090/943/765 1061/941/761 1193/942/764 1194/944/766 f 1061/938/761 1090/945/765 1104/946/767 1103/939/762 f 1104/946/767 1090/945/765 1092/947/768 1106/948/769 f 1060/936/760 1091/935/759 1228/949/770 1229/950/771 f 1090/943/765 1194/944/766 1195/951/772 1092/952/768 f 1194/953/766 1203/954/773 1202/955/774 1195/956/772 f 1194/953/766 1193/957/764 1204/958/775 1203/954/773 f 1193/957/764 1093/959/751 1108/960/776 1204/958/775 f 1094/961/777 1062/962/778 1226/963/779 1227/964/780 f 1226/963/779 1062/962/778 1068/965/781 1225/966/782 f 1062/967/778 1094/968/777 1098/969/783 1099/970/784 f 1062/967/778 1099/970/784 1100/971/785 1068/972/781 f 1243/973/758 1063/974/786 1253/975/787 1244/976/788 f 1214/977/789 1063/974/786 1243/973/758 1229/978/771 f 1063/974/786 1214/977/789 1107/979/790 1235/980/791 f 1064/981/792 1253/975/787 1063/974/786 1213/982/793 f 1066/983/794 1064/981/792 1213/982/793 1230/984/756 f 1253/975/787 1064/981/792 1097/985/795 1252/986/796 f 1064/981/792 1066/983/794 1073/987/797 1097/985/795 f 1065/988/798 1213/982/793 1063/974/786 1235/980/791 f 1065/988/798 1096/989/672 1212/990/675 1213/982/793 f 1066/991/794 1244/992/788 1245/993/799 1073/994/797 f 1244/976/788 1253/975/787 1252/986/796 1245/995/799 f 1252/986/796 1251/996/800 1246/997/801 1245/995/799 f 1251/996/800 1250/998/802 1247/999/803 1246/997/801 f 1250/998/802 1249/1000/804 1248/1001/805 1247/999/803 f 1098/969/783 1102/940/763 1215/1002/806 1216/1003/807 f 1091/937/759 1102/940/763 1098/969/783 1094/968/777 f 1102/940/763 1107/979/790 1214/977/789 1215/1002/806 f 1068/972/781 1100/971/785 1101/1004/808 1067/1005/809 f 1100/971/785 1218/1006/810 1219/1007/811 1101/1004/808 f 1134/1008/812 1067/1005/809 1101/1004/808 1135/1009/813 f 1067/1010/809 1134/1011/812 1223/1012/814 1224/1013/815 f 1225/966/782 1068/965/781 1067/1010/809 1224/1013/815 f 1237/1014/816 1162/1015/817 1184/1016/818 1238/1017/819 f 1181/1018/820 1110/1019/821 1121/1020/822 1180/1021/823 f 1110/1019/821 1117/1022/824 1120/1023/825 1121/1020/822 f 1184/1016/818 1183/1024/826 1170/1025/827 1169/1026/828 f 1183/1024/826 1182/1027/829 1171/1028/830 1170/1025/827 f 1182/1027/829 1181/1018/820 1172/1029/831 1171/1028/830 f 1172/1029/831 1181/1018/820 1180/1021/823 1173/1030/832 f 1150/1031/833 1159/1032/834 1158/1033/835 1151/1034/836 f 1160/1035/837 1159/1032/834 1150/1031/833 1149/1036/838 f 1151/1034/836 1158/1033/835 1157/1037/839 1152/1038/840 f 1159/1032/834 1114/1039/841 1116/1040/842 1158/1033/835 f 1114/1039/841 1112/1041/843 1070/1042/844 1116/1040/842 f 1112/1043/843 1150/1044/833 1151/1045/836 1070/1046/844 f 1093/959/751 1113/1047/845 1115/1048/846 1108/960/776 f 1113/1047/845 1112/1041/843 1114/1039/841 1115/1048/846 f 1069/1049/847 1157/1037/839 1158/1033/835 1116/1040/842 f 1069/1049/847 1128/1050/848 1156/1051/849 1157/1037/839 f 1128/1050/848 1069/1049/847 1071/1052/850 1072/1053/851 f 1069/1049/847 1116/1040/842 1070/1042/844 1071/1052/850 f 1070/1046/844 1151/1045/836 1152/1054/840 1071/1055/850 f 1160/1035/837 1149/1036/838 1118/1056/754 1161/1057/852 f 1072/1058/851 1071/1055/850 1152/1054/840 1153/1059/853 f 1129/1060/854 1072/1058/851 1153/1059/853 1154/1061/855 f 1128/1050/848 1072/1053/851 1129/1062/854 1080/1063/856 f 1110/1019/821 1111/1064/857 1119/1065/858 1117/1022/824 f 1117/1066/824 1172/1029/831 1173/1030/832 1120/1067/825 f 1123/1068/859 1075/1069/860 1122/1070/861 1125/1071/862 f 1124/1072/863 1123/1068/859 1125/1071/862 1074/1073/864 f 1073/987/797 1122/1070/861 1075/1069/860 1097/985/795 f 1122/1074/861 1073/994/797 1245/993/799 1246/1075/801 f 1074/1076/864 1125/1077/862 1247/1078/803 1248/1079/805 f 1075/1069/860 1123/1068/859 1250/998/802 1251/996/800 f 1097/985/795 1075/1069/860 1251/996/800 1252/986/796 f 1126/1080/865 1076/1081/866 1177/1082/867 1178/1083/868 f 1177/1082/867 1076/1081/866 1079/1084/869 1176/1085/870 f 1076/1081/866 1126/1080/865 1078/1086/871 1079/1084/869 f 1078/1087/871 1077/1088/872 1174/1089/873 1175/1090/874 f 1077/1091/872 1078/1086/871 1126/1080/865 1127/1092/875 f 1079/1093/869 1078/1087/871 1175/1090/874 1176/1085/870 f 1128/1050/848 1080/1063/856 1155/1094/876 1156/1051/849 f 1129/1062/854 1154/1095/855 1155/1094/876 1080/1063/856 f 1130/1096/877 1131/1097/878 1132/1098/879 1081/1099/880 f 1133/1100/881 1132/1098/879 1131/1097/878 1105/1101/882 f 1198/1102/883 1199/1103/884 1130/1096/877 1081/1099/880 f 1081/1104/880 1132/1105/879 1197/1106/885 1198/1107/883 f 1198/1102/883 1197/1108/885 1200/1109/886 1199/1103/884 f 1197/1108/885 1196/1110/887 1201/1111/888 1200/1109/886 f 1195/956/772 1202/955/774 1201/1111/888 1196/1110/887 f 1082/1112/889 1134/1008/812 1135/1009/813 1083/1113/890 f 1134/1011/812 1082/1114/889 1222/1115/891 1223/1012/814 f 1082/1112/889 1083/1113/890 1221/1116/892 1222/1117/891 f 1083/1113/890 1135/1009/813 1220/1118/893 1221/1116/892 f 1186/1119/679 1185/1120/894 1168/1121/895 1167/1122/681 f 1143/1123/896 1192/1124/683 1147/1125/686 1148/1126/897 f 1143/1123/896 1148/1126/897 1240/1127/898 1241/1128/899 f 1058/926/752 1241/1128/899 1240/1127/898 1088/927/753 f 1108/960/776 1115/1048/846 1160/1035/837 1161/1057/852 f 1115/1048/846 1114/1039/841 1159/1032/834 1160/1035/837 f 1109/1129/900 1108/960/776 1161/1057/852 1162/1015/817 f 1205/1130/693 1139/1131/901 1138/1132/902 1163/1133/678 f 1138/1132/902 1139/1131/901 1236/1134/903 1237/1014/816 f 1148/1126/897 1147/1125/686 1167/1122/681 1168/1121/895 f 1148/1126/897 1168/1121/895 1239/1135/904 1240/1127/898 f 1088/927/753 1240/1127/898 1239/1135/904 1169/1026/828 f 1117/1066/824 1119/1136/858 1171/1028/830 1172/1029/831 f 1119/1136/858 1118/928/754 1170/1025/827 1171/1028/830 f 1127/1092/875 1126/1080/865 1178/1083/868 1179/1137/905 f 1127/1092/875 1179/1137/905 1180/1021/823 1121/1020/822 f 1111/1064/857 1110/1019/821 1181/1018/820 1182/1027/829 f 1161/1057/852 1111/1064/857 1182/1027/829 1183/1024/826 f 1162/1015/817 1161/1057/852 1183/1024/826 1184/1016/818 f 1163/1133/678 1138/1132/902 1185/1120/894 1186/1119/679 f 1089/930/755 1059/929/669 1192/1124/683 1143/1123/896 f 1089/930/755 1143/1123/896 1241/1128/899 1242/933/757 f 1060/936/760 1242/933/757 1241/1128/899 1058/926/752 f 1195/951/772 1196/1138/887 1133/1139/881 1092/952/768 f 1132/1105/879 1133/1139/881 1196/1138/887 1197/1106/885 f 1131/1097/878 1130/1096/877 1199/1103/884 1200/1109/886 f 1105/1101/882 1131/1097/878 1200/1109/886 1201/1111/888 f 1105/1101/882 1201/1111/888 1202/955/774 1106/948/769 f 1104/946/767 1106/948/769 1202/955/774 1203/954/773 f 1103/939/762 1104/946/767 1203/954/773 1204/958/775 f 1102/940/763 1103/939/762 1204/958/775 1108/960/776 f 1235/980/791 1107/979/790 1109/1129/900 1236/1134/903 f 1096/989/672 1065/988/798 1139/1131/901 1205/1130/693 f 1139/1131/901 1065/988/798 1235/980/791 1236/1134/903 f 1218/1006/810 1100/971/785 1099/970/784 1217/1140/906 f 1091/935/759 1094/961/777 1227/964/780 1228/949/770 f 1185/1120/894 1238/1017/819 1239/1135/904 1168/1121/895 f 1185/1120/894 1138/1132/902 1237/1014/816 1238/1017/819 f 1125/1077/862 1122/1074/861 1246/1075/801 1247/1078/803 f 1249/1000/804 1124/1072/863 1074/1073/864 1248/1001/805 f 1123/1068/859 1124/1072/863 1249/1000/804 1250/998/802 f 1242/933/757 1060/936/760 1229/950/771 1243/934/758 f 1118/928/754 1088/927/753 1169/1026/828 1170/1025/827 f 1113/1141/845 1093/925/751 1118/928/754 1149/1142/838 f 1228/1143/770 1227/1144/780 1216/1003/807 1215/1002/806 f 1227/1144/780 1226/1145/779 1217/1140/906 1216/1003/807 f 1229/978/771 1228/1143/770 1215/1002/806 1214/977/789 f 1213/982/793 1212/990/675 1231/1146/670 1230/984/756 f 1066/991/794 1230/931/756 1243/934/758 1244/992/788 f 1099/970/784 1098/969/783 1216/1003/807 1217/1140/906 f 1135/1009/813 1101/1004/808 1219/1007/811 1220/1118/893 f 1218/1006/810 1217/1140/906 1226/1145/779 1225/1147/782 f 1218/1006/810 1225/1147/782 1224/1148/815 1219/1007/811 f 1107/979/790 1102/940/763 1108/960/776 1109/1129/900 f 1133/1100/881 1105/1101/882 1106/948/769 1092/947/768 f 1236/1134/903 1109/1129/900 1162/1015/817 1237/1014/816 f 1111/1064/857 1161/1057/852 1118/1056/754 1119/1065/858 f 1238/1017/819 1184/1016/818 1169/1026/828 1239/1135/904 f 1112/1043/843 1113/1141/845 1149/1142/838 1150/1044/833 f 1153/1149/853 1152/1038/840 1157/1037/839 1156/1051/849 f 1154/1095/855 1153/1149/853 1156/1051/849 1155/1094/876 f 1077/1088/872 1120/1067/825 1173/1030/832 1174/1089/873 f 1127/1092/875 1121/1020/822 1120/1023/825 1077/1091/872 f 1179/1137/905 1174/1089/873 1173/1030/832 1180/1021/823 f 1179/1137/905 1178/1083/868 1175/1090/874 1174/1089/873 f 1178/1083/868 1177/1082/867 1176/1085/870 1175/1090/874 f 1219/1007/811 1224/1148/815 1223/1150/814 1220/1118/893 f 1221/1116/892 1220/1118/893 1223/1150/814 1222/1117/891 f 1309/1151/907 1334/1152/908 1304/1153/909 1274/1154/910 f 1275/1155/711 1447/1156/710 1446/1157/911 1305/1158/912 f 1446/1157/911 1459/1159/913 1458/1160/914 1305/1158/912 f 1307/1161/915 1309/1151/907 1274/1154/910 1276/1162/916 f 1307/1163/915 1318/1164/917 1319/1165/918 1277/1166/919 f 1277/1167/919 1409/1168/920 1309/1151/907 1307/1161/915 f 1306/1169/921 1410/1170/922 1409/1168/920 1277/1167/919 f 1277/1166/919 1319/1165/918 1320/1171/923 1306/1172/921 f 1320/1171/923 1322/1173/924 1308/1174/925 1306/1172/921 f 1276/1162/916 1445/1175/926 1444/1176/927 1307/1161/915 f 1306/1169/921 1308/1177/925 1411/1178/928 1410/1170/922 f 1410/1179/922 1411/1180/928 1418/1181/929 1419/1182/930 f 1410/1179/922 1419/1182/930 1420/1183/931 1409/1184/920 f 1409/1184/920 1420/1183/931 1324/1185/932 1309/1186/907 f 1310/1187/933 1443/1188/934 1442/1189/935 1278/1190/936 f 1442/1189/935 1441/1191/937 1284/1192/938 1278/1190/936 f 1278/1193/936 1315/1194/939 1314/1195/940 1310/1196/933 f 1278/1193/936 1284/1197/938 1316/1198/941 1315/1194/939 f 1459/1199/913 1460/1200/942 1469/1201/943 1279/1202/944 f 1430/1203/945 1445/1204/926 1459/1199/913 1279/1202/944 f 1279/1202/944 1451/1205/946 1323/1206/947 1430/1203/945 f 1280/1207/948 1429/1208/949 1279/1202/944 1469/1201/943 f 1282/1209/950 1446/1210/911 1429/1208/949 1280/1207/948 f 1469/1201/943 1468/1211/951 1313/1212/952 1280/1207/948 f 1280/1207/948 1313/1212/952 1289/1213/953 1282/1209/950 f 1281/1214/954 1451/1205/946 1279/1202/944 1429/1208/949 f 1281/1214/954 1429/1208/949 1428/1215/713 1312/1216/712 f 1282/1217/950 1289/1218/953 1461/1219/955 1460/1220/942 f 1460/1200/942 1461/1221/955 1468/1211/951 1469/1201/943 f 1468/1211/951 1461/1221/955 1462/1222/956 1467/1223/957 f 1467/1223/957 1462/1222/956 1463/1224/958 1466/1225/959 f 1466/1225/959 1463/1224/958 1464/1226/960 1465/1227/961 f 1314/1195/940 1432/1228/962 1431/1229/963 1318/1164/917 f 1307/1163/915 1310/1196/933 1314/1195/940 1318/1164/917 f 1318/1164/917 1431/1229/963 1430/1203/945 1323/1206/947 f 1284/1197/938 1283/1230/964 1317/1231/965 1316/1198/941 f 1316/1198/941 1317/1231/965 1435/1232/966 1434/1233/967 f 1350/1234/968 1351/1235/969 1317/1231/965 1283/1230/964 f 1283/1236/964 1440/1237/970 1439/1238/971 1350/1239/968 f 1441/1191/937 1440/1237/970 1283/1236/964 1284/1192/938 f 1453/1240/972 1454/1241/973 1400/1242/974 1378/1243/975 f 1397/1244/976 1396/1245/977 1337/1246/978 1326/1247/979 f 1326/1247/979 1337/1246/978 1336/1248/980 1333/1249/981 f 1400/1242/974 1385/1250/982 1386/1251/983 1399/1252/984 f 1399/1252/984 1386/1251/983 1387/1253/985 1398/1254/986 f 1398/1254/986 1387/1253/985 1388/1255/987 1397/1244/976 f 1388/1255/987 1389/1256/988 1396/1245/977 1397/1244/976 f 1366/1257/989 1367/1258/990 1374/1259/991 1375/1260/992 f 1376/1261/993 1365/1262/994 1366/1257/989 1375/1260/992 f 1367/1258/990 1368/1263/995 1373/1264/996 1374/1259/991 f 1375/1260/992 1374/1259/991 1332/1265/997 1330/1266/998 f 1330/1266/998 1332/1265/997 1286/1267/999 1328/1268/1000 f 1328/1269/1000 1286/1270/999 1367/1271/990 1366/1272/989 f 1309/1186/907 1324/1185/932 1331/1273/1001 1329/1274/1002 f 1329/1274/1002 1331/1273/1001 1330/1266/998 1328/1268/1000 f 1285/1275/1003 1332/1265/997 1374/1259/991 1373/1264/996 f 1285/1275/1003 1373/1264/996 1372/1276/1004 1344/1277/1005 f 1344/1277/1005 1288/1278/1006 1287/1279/1007 1285/1275/1003 f 1285/1275/1003 1287/1279/1007 1286/1267/999 1332/1265/997 f 1286/1270/999 1287/1280/1007 1368/1281/995 1367/1271/990 f 1376/1261/993 1377/1282/1008 1334/1283/908 1365/1262/994 f 1288/1284/1006 1369/1285/1009 1368/1281/995 1287/1280/1007 f 1345/1286/1010 1370/1287/1011 1369/1285/1009 1288/1284/1006 f 1344/1277/1005 1296/1288/1012 1345/1289/1010 1288/1278/1006 f 1326/1247/979 1333/1249/981 1335/1290/1013 1327/1291/1014 f 1333/1292/981 1336/1293/980 1389/1256/988 1388/1255/987 f 1339/1294/1015 1341/1295/1016 1338/1296/1017 1291/1297/1018 f 1340/1298/1019 1290/1299/1020 1341/1295/1016 1339/1294/1015 f 1289/1213/953 1313/1212/952 1291/1297/1018 1338/1296/1017 f 1338/1300/1017 1462/1301/956 1461/1219/955 1289/1218/953 f 1290/1302/1020 1464/1303/960 1463/1304/958 1341/1305/1016 f 1291/1297/1018 1467/1223/957 1466/1225/959 1339/1294/1015 f 1313/1212/952 1468/1211/951 1467/1223/957 1291/1297/1018 f 1342/1306/1021 1394/1307/1022 1393/1308/1023 1292/1309/1024 f 1393/1308/1023 1392/1310/1025 1295/1311/1026 1292/1309/1024 f 1292/1309/1024 1295/1311/1026 1294/1312/1027 1342/1306/1021 f 1294/1313/1027 1391/1314/1028 1390/1315/1029 1293/1316/1030 f 1293/1317/1030 1343/1318/1031 1342/1306/1021 1294/1312/1027 f 1295/1319/1026 1392/1310/1025 1391/1314/1028 1294/1313/1027 f 1344/1277/1005 1372/1276/1004 1371/1320/1032 1296/1288/1012 f 1345/1289/1010 1296/1288/1012 1371/1320/1032 1370/1321/1011 f 1346/1322/1033 1297/1323/1034 1348/1324/1035 1347/1325/1036 f 1349/1326/1037 1321/1327/1038 1347/1325/1036 1348/1324/1035 f 1414/1328/1039 1297/1323/1034 1346/1322/1033 1415/1329/1040 f 1297/1330/1034 1414/1331/1039 1413/1332/1041 1348/1333/1035 f 1414/1328/1039 1415/1329/1040 1416/1334/1042 1413/1335/1041 f 1413/1335/1041 1416/1334/1042 1417/1336/1043 1412/1337/1044 f 1411/1180/928 1412/1337/1044 1417/1336/1043 1418/1181/929 f 1298/1338/1045 1299/1339/1046 1351/1235/969 1350/1234/968 f 1350/1239/968 1439/1238/971 1438/1340/1047 1298/1341/1045 f 1298/1338/1045 1438/1342/1047 1437/1343/1048 1299/1339/1046 f 1299/1339/1046 1437/1343/1048 1436/1344/1049 1351/1235/969 f 1402/1345/719 1383/1346/722 1384/1347/1050 1401/1348/1051 f 1359/1349/1052 1364/1350/1053 1363/1351/724 1408/1352/723 f 1359/1349/1052 1457/1353/1054 1456/1354/1055 1364/1350/1053 f 1274/1154/910 1304/1153/909 1456/1354/1055 1457/1353/1054 f 1324/1185/932 1377/1282/1008 1376/1261/993 1331/1273/1001 f 1331/1273/1001 1376/1261/993 1375/1260/992 1330/1266/998 f 1325/1355/1056 1378/1243/975 1377/1282/1008 1324/1185/932 f 1421/1356/733 1379/1357/720 1354/1358/1057 1355/1359/1058 f 1354/1358/1057 1453/1240/972 1452/1360/1059 1355/1359/1058 f 1364/1350/1053 1384/1347/1050 1383/1346/722 1363/1351/724 f 1364/1350/1053 1456/1354/1055 1455/1361/1060 1384/1347/1050 f 1304/1153/909 1385/1250/982 1455/1361/1060 1456/1354/1055 f 1333/1292/981 1388/1255/987 1387/1253/985 1335/1362/1013 f 1335/1362/1013 1387/1253/985 1386/1251/983 1334/1152/908 f 1343/1318/1031 1395/1363/1061 1394/1307/1022 1342/1306/1021 f 1343/1318/1031 1337/1246/978 1396/1245/977 1395/1363/1061 f 1327/1291/1014 1398/1254/986 1397/1244/976 1326/1247/979 f 1377/1282/1008 1399/1252/984 1398/1254/986 1327/1291/1014 f 1378/1243/975 1400/1242/974 1399/1252/984 1377/1282/1008 f 1379/1357/720 1402/1345/719 1401/1348/1051 1354/1358/1057 f 1305/1158/912 1359/1349/1052 1408/1352/723 1275/1155/711 f 1305/1158/912 1458/1160/914 1457/1353/1054 1359/1349/1052 f 1276/1162/916 1274/1154/910 1457/1353/1054 1458/1160/914 f 1411/1178/928 1308/1177/925 1349/1364/1037 1412/1365/1044 f 1348/1333/1035 1413/1332/1041 1412/1365/1044 1349/1364/1037 f 1347/1325/1036 1416/1334/1042 1415/1329/1040 1346/1322/1033 f 1321/1327/1038 1417/1336/1043 1416/1334/1042 1347/1325/1036 f 1321/1327/1038 1322/1173/924 1418/1181/929 1417/1336/1043 f 1320/1171/923 1419/1182/930 1418/1181/929 1322/1173/924 f 1319/1165/918 1420/1183/931 1419/1182/930 1320/1171/923 f 1318/1164/917 1324/1185/932 1420/1183/931 1319/1165/918 f 1451/1205/946 1452/1360/1059 1325/1355/1056 1323/1206/947 f 1312/1216/712 1421/1356/733 1355/1359/1058 1281/1214/954 f 1355/1359/1058 1452/1360/1059 1451/1205/946 1281/1214/954 f 1434/1233/967 1433/1366/1062 1315/1194/939 1316/1198/941 f 1307/1161/915 1444/1176/927 1443/1188/934 1310/1187/933 f 1401/1348/1051 1384/1347/1050 1455/1361/1060 1454/1241/973 f 1401/1348/1051 1454/1241/973 1453/1240/972 1354/1358/1057 f 1341/1305/1016 1463/1304/958 1462/1301/956 1338/1300/1017 f 1465/1227/961 1464/1226/960 1290/1299/1020 1340/1298/1019 f 1339/1294/1015 1466/1225/959 1465/1227/961 1340/1298/1019 f 1458/1160/914 1459/1159/913 1445/1175/926 1276/1162/916 f 1334/1152/908 1386/1251/983 1385/1250/982 1304/1153/909 f 1329/1367/1002 1365/1368/994 1334/1152/908 1309/1151/907 f 1444/1369/927 1431/1229/963 1432/1228/962 1443/1370/934 f 1443/1370/934 1432/1228/962 1433/1366/1062 1442/1371/935 f 1445/1204/926 1430/1203/945 1431/1229/963 1444/1369/927 f 1429/1208/949 1446/1210/911 1447/1372/710 1428/1215/713 f 1282/1217/950 1460/1220/942 1459/1159/913 1446/1157/911 f 1315/1194/939 1433/1366/1062 1432/1228/962 1314/1195/940 f 1351/1235/969 1436/1344/1049 1435/1232/966 1317/1231/965 f 1434/1233/967 1441/1373/937 1442/1371/935 1433/1366/1062 f 1434/1233/967 1435/1232/966 1440/1374/970 1441/1373/937 f 1323/1206/947 1325/1355/1056 1324/1185/932 1318/1164/917 f 1349/1326/1037 1308/1174/925 1322/1173/924 1321/1327/1038 f 1452/1360/1059 1453/1240/972 1378/1243/975 1325/1355/1056 f 1327/1291/1014 1335/1290/1013 1334/1283/908 1377/1282/1008 f 1454/1241/973 1455/1361/1060 1385/1250/982 1400/1242/974 f 1328/1269/1000 1366/1272/989 1365/1368/994 1329/1367/1002 f 1369/1375/1009 1372/1276/1004 1373/1264/996 1368/1263/995 f 1370/1321/1011 1371/1320/1032 1372/1276/1004 1369/1375/1009 f 1293/1316/1030 1390/1315/1029 1389/1256/988 1336/1293/980 f 1343/1318/1031 1293/1317/1030 1336/1248/980 1337/1246/978 f 1395/1363/1061 1396/1245/977 1389/1256/988 1390/1315/1029 f 1395/1363/1061 1390/1315/1029 1391/1314/1028 1394/1307/1022 f 1394/1307/1022 1391/1314/1028 1392/1310/1025 1393/1308/1023 f 1435/1232/966 1436/1344/1049 1439/1376/971 1440/1374/970 f 1437/1343/1048 1438/1342/1047 1439/1376/971 1436/1344/1049 f 2044/1377/1063 2045/1378/1064 1485/1379/1065 1475/1380/1066 f 1477/1381/1067 1487/1382/1068 1486/1383/1069 1476/1384/1070 f 1478/1385/1071 1488/1386/1072 1487/1382/1068 1477/1381/1067 f 1479/1387/1073 1489/1388/1074 1488/1386/1072 1478/1385/1071 f 1480/1389/1075 1490/1390/1076 1489/1388/1074 1479/1387/1073 f 1537/1391/1077 1538/1392/1078 1490/1390/1076 1480/1389/1075 f 1482/1393/1079 1492/1394/1080 1491/1395/1081 1481/1396/1082 f 1483/1397/1083 1493/1398/1084 1492/1394/1080 1482/1393/1079 f 1484/1399/1085 1494/1400/1086 1493/1398/1084 1483/1397/1083 f 1475/1380/1066 1485/1379/1065 1494/1400/1086 1484/1399/1085 f 2045/1378/1064 2046/1401/1087 1495/1402/1088 1485/1379/1065 f 2046/1401/1087 2047/1403/1089 1497/1404/1090 1495/1402/1088 f 1495/1402/1088 1503/1405/1091 1502/1406/1092 1485/1379/1065 f 1497/1404/1090 1504/1407/1093 1503/1405/1091 1495/1402/1088 f 1503/1405/1091 1500/1408/1094 1499/1409/1095 1502/1406/1092 f 1504/1407/1093 1501/1410/1096 1500/1408/1094 1503/1405/1091 f 2073/1411/1097 1509/1412/1098 1508/1413/1099 2072/1414/1100 f 1505/1415/1101 1494/1400/1086 1485/1379/1065 1502/1406/1092 f 1506/1416/1102 1493/1398/1084 1494/1400/1086 1505/1415/1101 f 1499/1409/1095 1512/1417/1103 1505/1415/1101 1502/1406/1092 f 1512/1417/1103 1511/1418/1104 1506/1416/1102 1505/1415/1101 f 1511/1418/1104 1510/1419/1105 1507/1420/1106 1506/1416/1102 f 1487/1382/1068 1514/1421/1107 1513/1422/1108 1486/1383/1069 f 1488/1386/1072 1515/1423/1109 1514/1421/1107 1487/1382/1068 f 1489/1388/1074 1516/1424/1110 1515/1423/1109 1488/1386/1072 f 1490/1390/1076 1517/1425/1111 1516/1424/1110 1489/1388/1074 f 1486/1383/1069 1513/1422/1108 1518/1426/1112 1496/1427/1113 f 1496/1427/1113 1518/1426/1112 1519/1428/1114 1498/1429/1115 f 1528/1430/1116 1527/1431/1117 1522/1432/1118 1521/1433/1119 f 1526/1434/1120 1525/1435/1121 1524/1436/1122 1523/1437/1123 f 1527/1431/1117 1526/1434/1120 1523/1437/1123 1522/1432/1118 f 1941/1438/1124 1561/1439/1125 1562/1440/1126 1940/1441/1127 f 1520/1442/1128 1508/1413/1099 1509/1412/1098 1529/1443/1129 f 1523/1437/1123 1611/1444/1130 1582/1445/1131 1522/1432/1118 f 1582/1445/1131 1583/1446/1132 1535/1447/1133 1522/1432/1118 f 1535/1447/1133 1534/1448/1134 1521/1433/1119 1522/1432/1118 f 1531/1449/1135 1533/1450/1136 1787/1451/1137 1788/1452/1138 f 1789/1453/1139 1584/1454/1140 1531/1449/1135 1788/1452/1138 f 1481/1396/1082 1491/1395/1081 2032/1455/1141 2033/1456/1142 f 1476/1384/1070 1486/1383/1069 1540/1457/1143 1539/1458/1144 f 1486/1383/1069 1496/1427/1113 1541/1459/1145 1540/1457/1143 f 1496/1427/1113 1498/1429/1115 1542/1460/1146 1541/1459/1145 f 1585/1461/1147 1586/1462/1148 1545/1463/1149 1546/1464/1150 f 1944/1465/1151 1559/1466/1152 1589/1467/1153 1943/1468/1154 f 1550/1469/1155 1527/1431/1117 1528/1430/1116 1547/1470/1156 f 1525/1435/1121 1526/1434/1120 1549/1471/1157 1548/1472/1158 f 1545/1463/1149 1586/1462/1148 1587/1473/1159 1544/1474/1160 f 1549/1471/1157 1526/1434/1120 1527/1431/1117 1550/1469/1155 f 1543/1475/1161 1590/1476/1162 1591/1477/1163 1566/1478/1164 f 1557/1479/1165 1593/1480/1166 1594/1481/1167 1556/1482/1168 f 1553/1483/1169 1592/1484/1170 1593/1480/1166 1557/1479/1165 f 1942/1485/1171 1560/1486/1172 1561/1439/1125 1941/1438/1124 f 1529/1443/1129 1509/1412/1098 1551/1487/1173 1558/1488/1174 f 1544/1474/1160 1587/1473/1159 1588/1489/1175 1565/1490/1176 f 1528/1430/1116 1561/1439/1125 1560/1486/1172 1547/1470/1156 f 1521/1433/1119 1562/1440/1126 1561/1439/1125 1528/1430/1116 f 2074/1491/1177 2075/1492/1178 1563/1493/1179 1578/1494/1180 f 1555/1495/1181 1554/1496/1182 1563/1493/1179 1564/1497/1183 f 1559/1466/1152 1565/1490/1176 1588/1489/1175 1589/1467/1153 f 1566/1478/1164 1591/1477/1163 1592/1484/1170 1553/1483/1169 f 1568/1498/1184 1500/1408/1094 1501/1410/1096 1567/1499/1185 f 1511/1418/1104 1512/1417/1103 1570/1500/1186 1571/1501/1187 f 1570/1500/1186 1512/1417/1103 1499/1409/1095 1569/1502/1188 f 1569/1502/1188 1499/1409/1095 1500/1408/1094 1568/1498/1184 f 1511/1418/1104 1571/1501/1187 1572/1503/1189 1573/1504/1190 f 1573/1504/1190 1572/1503/1189 1574/1505/1191 1575/1506/1192 f 1575/1506/1192 1574/1505/1191 1576/1507/1193 1577/1508/1194 f 2075/1492/1178 2076/1509/1195 1564/1497/1183 1563/1493/1179 f 1554/1496/1182 1552/1510/1196 1578/1494/1180 1563/1493/1179 f 1552/1510/1196 1551/1487/1173 1509/1412/1098 1578/1494/1180 f 2074/1491/1177 1578/1494/1180 1509/1412/1098 2073/1411/1097 f 1573/1504/1190 1579/1511/1197 1510/1419/1105 1511/1418/1104 f 1573/1504/1190 1575/1506/1192 1580/1512/1198 1579/1511/1197 f 1575/1506/1192 1577/1508/1194 1581/1513/1199 1580/1512/1198 f 1612/1514/1200 1530/1515/1201 1582/1445/1131 1611/1444/1130 f 1530/1515/1201 1536/1516/1202 1583/1446/1132 1582/1445/1131 f 1790/1517/1203 1532/1518/1204 1584/1454/1140 1789/1453/1139 f 1548/1472/1158 1549/1471/1157 1586/1462/1148 1585/1461/1147 f 1587/1473/1159 1586/1462/1148 1549/1471/1157 1550/1469/1155 f 1588/1489/1175 1587/1473/1159 1550/1469/1155 1547/1470/1156 f 1589/1467/1153 1588/1489/1175 1547/1470/1156 1560/1486/1172 f 1943/1468/1154 1589/1467/1153 1560/1486/1172 1942/1485/1171 f 1591/1477/1163 1590/1476/1162 1558/1488/1174 1551/1487/1173 f 1592/1484/1170 1591/1477/1163 1551/1487/1173 1552/1510/1196 f 1593/1480/1166 1592/1484/1170 1552/1510/1196 1554/1496/1182 f 1594/1481/1167 1593/1480/1166 1554/1496/1182 1555/1495/1181 f 1967/1519/1205 1599/1520/1206 1776/1521/1207 1777/1522/1208 f 1787/1451/1137 1533/1450/1136 1595/1523/1209 1786/1524/1210 f 1786/1524/1210 1595/1523/1209 1596/1525/1211 1785/1526/1212 f 1596/1525/1211 1597/1527/1213 1784/1528/1214 1785/1526/1212 f 1562/1440/1126 1521/1433/1119 1534/1448/1134 1600/1529/1215 f 1507/1420/1106 1603/1530/1216 1493/1398/1084 1506/1416/1102 f 1940/1441/1127 1562/1440/1126 1600/1529/1215 1939/1531/1217 f 1520/1442/1128 1601/1532/1218 1602/1533/1219 1508/1413/1099 f 1508/1413/1099 1602/1533/1219 2071/1534/1220 2072/1414/1100 f 1538/1392/1078 1606/1535/1221 1517/1425/1111 1490/1390/1076 f 1493/1398/1084 1603/1530/1216 1604/1536/1222 1492/1394/1080 f 1492/1394/1080 1604/1536/1222 1605/1537/1223 1491/1395/1081 f 1491/1395/1081 1605/1537/1223 2031/1538/1224 2032/1455/1141 f 2070/1539/1225 2071/1534/1220 1602/1533/1219 1608/1540/1226 f 2069/1541/1227 2070/1539/1225 1608/1540/1226 1610/1542/1228 f 1608/1540/1226 1602/1533/1219 1601/1532/1218 1607/1543/1229 f 1610/1542/1228 1608/1540/1226 1607/1543/1229 1609/1544/1230 f 1524/1436/1122 1612/1514/1200 1611/1444/1130 1523/1437/1123 f 1614/1545/1231 1613/1546/1232 1615/1547/1233 1616/1548/1234 f 1616/1548/1234 1615/1547/1233 1568/1498/1184 1567/1499/1185 f 1613/1546/1232 1621/1549/1235 1617/1550/1236 1615/1547/1233 f 1615/1547/1233 1617/1550/1236 1569/1502/1188 1568/1498/1184 f 1617/1550/1236 1618/1551/1237 1570/1500/1186 1569/1502/1188 f 1619/1552/1238 1620/1553/1239 1572/1503/1189 1571/1501/1187 f 1618/1551/1237 1619/1552/1238 1571/1501/1187 1570/1500/1186 f 1624/1554/1240 1631/1555/1241 1629/1556/1242 1630/1557/1243 f 1576/1507/1193 1622/1558/1244 1633/1559/1245 1634/1560/1246 f 1623/1561/1247 1574/1505/1191 1572/1503/1189 1620/1553/1239 f 1576/1507/1193 1574/1505/1191 1623/1561/1247 1622/1558/1244 f 1633/1559/1245 1622/1558/1244 1625/1562/1248 1632/1563/1249 f 1623/1561/1247 1620/1553/1239 1627/1564/1250 1626/1565/1251 f 1620/1553/1239 1619/1552/1238 1628/1566/1252 1627/1564/1250 f 1619/1552/1238 1618/1551/1237 1629/1556/1242 1628/1566/1252 f 1618/1551/1237 1617/1550/1236 1630/1557/1243 1629/1556/1242 f 1617/1550/1236 1621/1549/1235 1624/1554/1240 1630/1557/1243 f 1629/1556/1242 1631/1555/1241 1627/1564/1250 1628/1566/1252 f 1624/1554/1240 1632/1563/1249 1625/1562/1248 1631/1555/1241 f 1627/1564/1250 1631/1555/1241 1625/1562/1248 1626/1565/1251 f 1621/1549/1235 1633/1559/1245 1632/1563/1249 1624/1554/1240 f 1634/1560/1246 1633/1559/1245 1621/1549/1235 1613/1546/1232 f 1635/1567/1253 1634/1560/1246 1613/1546/1232 1614/1545/1231 f 2044/1377/1063 1475/1380/1066 1650/1568/1254 2043/1569/1255 f 1477/1381/1067 1476/1384/1070 1652/1570/1256 1653/1571/1257 f 1478/1385/1071 1477/1381/1067 1653/1571/1257 1654/1572/1258 f 1479/1387/1073 1478/1385/1071 1654/1572/1258 1655/1573/1259 f 1480/1389/1075 1479/1387/1073 1655/1573/1259 1656/1574/1260 f 1537/1391/1077 1480/1389/1075 1656/1574/1260 1657/1575/1261 f 1482/1393/1079 1481/1396/1082 1658/1576/1262 1659/1577/1263 f 1483/1397/1083 1482/1393/1079 1659/1577/1263 1648/1578/1264 f 1484/1399/1085 1483/1397/1083 1648/1578/1264 1649/1579/1265 f 1475/1380/1066 1484/1399/1085 1649/1579/1265 1650/1568/1254 f 1481/1396/1082 2033/1456/1142 2034/1580/1266 1658/1576/1262 f 1476/1384/1070 1539/1458/1144 1651/1581/1267 1652/1570/1256 f 1649/1579/1265 1648/1578/1264 1646/1582/1268 1647/1583/1269 f 1650/1568/1254 1649/1579/1265 1647/1583/1269 1636/1584/1270 f 2043/1569/1255 1650/1568/1254 1636/1584/1270 2042/1585/1271 f 1652/1570/1256 1651/1581/1267 1637/1586/1272 1638/1587/1273 f 1653/1571/1257 1652/1570/1256 1638/1587/1273 1639/1588/1274 f 1654/1572/1258 1653/1571/1257 1639/1588/1274 1640/1589/1275 f 1655/1573/1259 1654/1572/1258 1640/1589/1275 1641/1590/1276 f 1656/1574/1260 1655/1573/1259 1641/1590/1276 1642/1591/1277 f 1657/1575/1261 1656/1574/1260 1642/1591/1277 1643/1592/1278 f 1658/1576/1262 2034/1580/1266 2035/1593/1279 1644/1594/1280 f 1659/1577/1263 1658/1576/1262 1644/1594/1280 1645/1595/1281 f 1648/1578/1264 1659/1577/1263 1645/1595/1281 1646/1582/1268 f 1639/1588/1274 1638/1587/1273 1661/1596/1282 1662/1597/1283 f 1640/1589/1275 1639/1588/1274 1662/1597/1283 1663/1598/1284 f 1641/1590/1276 1640/1589/1275 1663/1598/1284 1664/1599/1285 f 1642/1591/1277 1641/1590/1276 1664/1599/1285 1665/1600/1286 f 1643/1592/1278 1642/1591/1277 1665/1600/1286 1666/1601/1287 f 1645/1595/1281 1644/1594/1280 1667/1602/1288 1668/1603/1289 f 1646/1582/1268 1645/1595/1281 1668/1603/1289 1669/1604/1290 f 1647/1583/1269 1646/1582/1268 1669/1604/1290 1670/1605/1291 f 1636/1584/1270 1647/1583/1269 1670/1605/1291 1660/1606/1292 f 1644/1594/1280 2035/1593/1279 2036/1607/1293 1667/1602/1288 f 1662/1597/1283 1661/1596/1282 1673/1608/1294 1674/1609/1295 f 1663/1598/1284 1662/1597/1283 1674/1609/1295 1675/1610/1296 f 1664/1599/1285 1663/1598/1284 1675/1610/1296 1676/1611/1297 f 1665/1600/1286 1664/1599/1285 1676/1611/1297 1677/1612/1298 f 1666/1601/1287 1665/1600/1286 1677/1612/1298 1678/1613/1299 f 1668/1603/1289 1667/1602/1288 1679/1614/1300 1680/1615/1301 f 1669/1604/1290 1668/1603/1289 1680/1615/1301 1681/1616/1302 f 1670/1605/1291 1669/1604/1290 1681/1616/1302 1682/1617/1303 f 1660/1606/1292 1670/1605/1291 1682/1617/1303 1671/1618/1304 f 1667/1602/1288 2036/1607/1293 2037/1619/1305 1679/1614/1300 f 1674/1609/1295 1673/1608/1294 1672/1620/1306 1685/1621/1307 f 2037/1619/1305 2038/1622/1308 1680/1615/1301 1679/1614/1300 f 1675/1610/1296 1683/1623/1309 1684/1624/1310 1676/1611/1297 f 1681/1616/1302 2039/1625/1311 2040/1626/1312 1682/1617/1303 f 2038/1622/1308 2039/1625/1311 1681/1616/1302 1680/1615/1301 f 1676/1611/1297 1684/1624/1310 1678/1613/1299 1677/1612/1298 f 1674/1609/1295 1685/1621/1307 1683/1623/1309 1675/1610/1296 f 1682/1617/1303 2040/1626/1312 2041/1627/1313 1671/1618/1304 f 1513/1422/1108 1514/1421/1107 1687/1628/1314 1686/1629/1315 f 1514/1421/1107 1515/1423/1109 1688/1630/1316 1687/1628/1314 f 1515/1423/1109 1516/1424/1110 1689/1631/1317 1688/1630/1316 f 1516/1424/1110 1517/1425/1111 1690/1632/1318 1689/1631/1317 f 1518/1426/1112 1513/1422/1108 1686/1629/1315 1691/1633/1319 f 1519/1428/1114 1518/1426/1112 1691/1633/1319 1692/1634/1320 f 2031/1538/1224 1605/1537/1223 1693/1635/1321 2030/1636/1322 f 1517/1425/1111 1606/1535/1221 1694/1637/1323 1690/1632/1318 f 2068/1638/1324 2069/1541/1227 1610/1542/1228 1696/1639/1325 f 1696/1639/1325 1610/1542/1228 1609/1544/1230 1695/1640/1326 f 2011/1641/1327 2012/1642/1328 2013/1643/1329 2014/1644/1330 f 1707/1645/1331 2012/1642/1328 1597/1527/1213 1596/1525/1211 f 1718/1646/1332 1699/1647/1333 2085/1648/1334 2086/1649/1335 f 1047/1650/1336 1043/1651/1337 1716/1652/1338 1704/1653/1339 f 1717/1654/1340 1700/1655/1341 1704/1653/1339 1716/1652/1338 f 1699/1647/1333 1707/1645/1331 2084/1656/1342 2085/1648/1334 f 1949/1657/1343 1950/1658/1344 1725/1659/1345 1724/1660/1346 f 1595/1523/1209 1708/1661/1347 1707/1645/1331 1596/1525/1211 f 2084/1656/1342 1707/1645/1331 1708/1661/1347 2083/1662/1348 f 1595/1523/1209 1533/1450/1136 1711/1663/1349 1708/1661/1347 f 2083/1662/1348 1708/1661/1347 1711/1663/1349 2082/1664/1350 f 1709/1665/1351 1584/1454/1140 1532/1518/1204 1705/1666/1352 f 2080/1667/1353 1709/1665/1351 1705/1666/1352 2079/1668/1354 f 1710/1669/1355 1531/1449/1135 1584/1454/1140 1709/1665/1351 f 2081/1670/1356 1710/1669/1355 1709/1665/1351 2080/1667/1353 f 1533/1450/1136 1531/1449/1135 1710/1669/1355 1711/1663/1349 f 2082/1664/1350 1711/1663/1349 1710/1669/1355 2081/1670/1356 f 1726/1671/1357 1951/1672/1358 1952/1673/1359 1727/1674/1360 f 1700/1655/1341 1712/1675/1361 1713/1676/1362 1704/1653/1339 f 1704/1653/1339 1713/1676/1362 1046/1677/1363 1047/1650/1336 f 1725/1659/1345 1950/1658/1344 1951/1672/1358 1726/1671/1357 f 1948/1678/1364 1949/1657/1343 1724/1660/1346 1723/1679/1365 f 1043/1651/1337 1049/1680/1366 1703/1681/1367 1716/1652/1338 f 1701/1682/1368 1717/1654/1340 1716/1652/1338 1703/1681/1367 f 1702/1683/1369 1718/1646/1332 2086/1649/1335 2087/1684/1370 f 1718/1646/1332 1702/1683/1369 1697/1685/1371 1719/1686/1372 f 1720/1687/1373 1719/1686/1372 1697/1685/1371 1698/1688/1374 f 1947/1689/1375 1948/1678/1364 1723/1679/1365 1722/1690/1376 f 1721/1691/1377 1946/1692/1378 1947/1689/1375 1722/1690/1376 f 1728/1693/1379 1727/1674/1360 1952/1673/1359 1953/1694/1380 f 1954/1695/1381 1729/1696/1382 1728/1693/1379 1953/1694/1380 f 1957/1697/1383 1958/1698/1384 1706/1699/1385 1715/1700/1386 f 1715/1700/1386 1714/1701/1387 1734/1702/1388 1733/1703/1389 f 1735/1704/1390 1734/1702/1388 1714/1701/1387 1712/1675/1361 f 1736/1705/1391 1735/1704/1390 1712/1675/1361 1700/1655/1341 f 1737/1706/1392 1736/1705/1391 1700/1655/1341 1717/1654/1340 f 1738/1707/1393 1737/1706/1392 1717/1654/1340 1701/1682/1368 f 2030/1636/1322 1693/1635/1321 1765/1708/1394 2029/1709/1395 f 1690/1632/1318 1694/1637/1323 1766/1710/1396 1740/1711/1397 f 1742/1712/1398 1774/1713/1399 1741/1714/1400 1764/1715/1401 f 2068/1638/1324 1696/1639/1325 1742/1712/1398 2067/1716/1402 f 1689/1631/1317 1690/1632/1318 1740/1711/1397 1743/1717/1403 f 1692/1634/1320 1691/1633/1319 1749/1718/1404 1747/1719/1405 f 1745/1720/1406 1688/1630/1316 1689/1631/1317 1743/1717/1403 f 1746/1721/1407 1687/1628/1314 1688/1630/1316 1745/1720/1406 f 1748/1722/1408 1686/1629/1315 1687/1628/1314 1746/1721/1407 f 1749/1718/1404 1691/1633/1319 1686/1629/1315 1748/1722/1408 f 1743/1717/1403 1740/1711/1397 1767/1723/1409 1768/1724/1410 f 1745/1720/1406 1743/1717/1403 1768/1724/1410 1769/1725/1411 f 1746/1721/1407 1745/1720/1406 1769/1725/1411 1770/1726/1412 f 1748/1722/1408 1746/1721/1407 1770/1726/1412 1771/1727/1413 f 1749/1718/1404 1748/1722/1408 1771/1727/1413 1772/1728/1414 f 1747/1719/1405 1749/1718/1404 1772/1728/1414 1773/1729/1415 f 2026/1730/1416 1935/1731/1417 1782/1732/1418 2025/1733/1419 f 2066/1734/1420 1764/1715/1401 1779/1735/1421 2065/1736/1422 f 2028/1737/1423 1780/1738/1424 1781/1739/1425 2027/1740/1426 f 2025/1733/1419 1782/1732/1418 1945/1741/1427 2024/1742/1428 f 1933/1743/1429 1934/1744/1430 1750/1745/1431 1751/1746/1432 f 1932/1747/1433 1933/1743/1429 1751/1746/1432 1752/1748/1434 f 1931/1749/1435 1932/1747/1433 1752/1748/1434 1753/1750/1436 f 1930/1751/1437 1931/1749/1435 1753/1750/1436 1754/1752/1438 f 1929/1753/1439 1930/1751/1437 1754/1752/1438 1755/1754/1440 f 1928/1755/1441 1929/1753/1439 1755/1754/1440 1756/1756/1442 f 1927/1757/1443 1928/1755/1441 1756/1756/1442 1757/1758/1444 f 1740/1711/1397 1766/1710/1396 1739/1759/1445 1767/1723/1409 f 1696/1639/1325 1695/1640/1326 1774/1713/1399 1742/1712/1398 f 1936/1760/1446 1937/1761/1447 1777/1522/1208 1776/1521/1207 f 1937/1761/1447 1938/1762/1448 1778/1763/1449 1777/1522/1208 f 1938/1762/1448 1939/1531/1217 1600/1529/1215 1778/1763/1449 f 1966/1764/1450 1967/1519/1205 1777/1522/1208 1778/1763/1449 f 1600/1529/1215 1965/1765/1451 1966/1764/1450 1778/1763/1449 f 1534/1448/1134 1964/1766/1452 1965/1765/1451 1600/1529/1215 f 1963/1767/1453 1964/1766/1452 1534/1448/1134 1535/1447/1133 f 1583/1446/1132 1962/1768/1454 1963/1767/1453 1535/1447/1133 f 1536/1516/1202 1961/1769/1455 1962/1768/1454 1583/1446/1132 f 1597/1527/1213 1598/1770/1456 1783/1771/1457 1784/1528/1214 f 1792/1772/1458 1254/1773/1459 1050/1774/1460 1793/1775/1461 f 1738/1707/1393 1701/1682/1368 1794/1776/1462 1796/1777/1463 f 1956/1778/1464 1051/1779/1465 1056/1780/1466 1795/1781/1467 f 1729/1696/1382 1954/1695/1381 1955/1782/1468 1797/1783/1469 f 1794/1776/1462 1792/1772/1458 1793/1775/1461 1796/1777/1463 f 1956/1778/1464 1795/1781/1467 1797/1783/1469 1955/1782/1468 f 1758/1784/1470 1744/1785/1471 2016/1786/1472 2017/1787/1473 f 1763/1788/1474 1760/1789/1475 2021/1790/1476 2022/1791/1477 f 1791/1792/1478 1758/1784/1470 2017/1787/1473 2018/1793/1479 f 1761/1794/1480 1762/1795/1481 2019/1796/1482 2020/1797/1483 f 1760/1789/1475 1761/1794/1480 2020/1797/1483 2021/1790/1476 f 1759/1798/1484 1763/1788/1474 2022/1791/1477 2023/1799/1485 f 1744/1785/1471 1775/1800/1486 2015/1801/1487 2016/1786/1472 f 1762/1795/1481 1791/1792/1478 2018/1793/1479 2019/1796/1482 f 2015/1801/1487 1775/1800/1486 2011/1641/1327 2014/1644/1330 f 1751/1746/1432 1750/1745/1431 1807/1802/1488 1808/1803/1489 f 1752/1748/1434 1751/1746/1432 1808/1803/1489 1809/1804/1490 f 1753/1750/1436 1752/1748/1434 1809/1804/1490 1810/1805/1491 f 1754/1752/1438 1753/1750/1436 1810/1805/1491 1811/1806/1492 f 1755/1754/1440 1754/1752/1438 1811/1806/1492 1812/1807/1493 f 1756/1756/1442 1755/1754/1440 1812/1807/1493 1813/1808/1494 f 1757/1758/1444 1756/1756/1442 1813/1808/1494 1814/1809/1495 f 1881/1810/1496 1815/1811/1497 1883/1812/1498 1882/1813/1499 f 1848/1814/1500 1816/1815/1501 1850/1816/1502 1849/1817/1503 f 1829/1818/1504 1817/1819/1505 1831/1820/1506 1830/1821/1507 f 1821/1822/1508 1818/1823/1509 1823/1824/1510 1822/1825/1511 f 1805/1826/1512 1801/1827/1513 1819/1828/1514 1820/1829/1515 f 1807/1802/1488 1805/1826/1512 1820/1829/1515 1808/1803/1489 f 1819/1828/1514 1821/1822/1508 1822/1825/1511 1820/1829/1515 f 1822/1825/1511 1809/1804/1490 1808/1803/1489 1820/1829/1515 f 1823/1824/1510 1810/1805/1491 1809/1804/1490 1822/1825/1511 f 1804/1830/1516 1803/1831/1517 1824/1832/1518 1826/1833/1519 f 1801/1827/1513 1800/1834/1520 1825/1835/1521 1819/1828/1514 f 1800/1834/1520 1804/1830/1516 1826/1833/1519 1825/1835/1521 f 1827/1836/1522 1818/1823/1509 1821/1822/1508 1828/1837/1523 f 1821/1822/1508 1819/1828/1514 1825/1835/1521 1828/1837/1523 f 1824/1832/1518 1829/1818/1504 1830/1821/1507 1826/1833/1519 f 1830/1821/1507 1828/1837/1523 1825/1835/1521 1826/1833/1519 f 1831/1820/1506 1827/1836/1522 1828/1837/1523 1830/1821/1507 f 1832/1838/1524 1839/1839/1525 1838/1840/1526 1837/1841/1527 f 1835/1842/1528 1834/1843/1529 1833/1844/1530 1836/1845/1531 f 1823/1824/1510 1818/1823/1509 1834/1843/1529 1835/1842/1528 f 1811/1806/1492 1810/1805/1491 1823/1824/1510 1835/1842/1528 f 1812/1807/1493 1811/1806/1492 1835/1842/1528 1836/1845/1531 f 1836/1845/1531 1833/1844/1530 1837/1841/1527 1838/1840/1526 f 1813/1808/1494 1812/1807/1493 1836/1845/1531 1838/1840/1526 f 1839/1839/1525 1814/1809/1495 1813/1808/1494 1838/1840/1526 f 1843/1846/1532 1840/1847/1533 1845/1848/1534 1844/1849/1535 f 1841/1850/1536 1833/1844/1530 1834/1843/1529 1842/1851/1537 f 1834/1843/1529 1818/1823/1509 1827/1836/1522 1842/1851/1537 f 1817/1819/1505 1843/1846/1532 1844/1849/1535 1831/1820/1506 f 1844/1849/1535 1842/1851/1537 1827/1836/1522 1831/1820/1506 f 1845/1848/1534 1841/1850/1536 1842/1851/1537 1844/1849/1535 f 1846/1852/1538 1832/1838/1524 1837/1841/1527 1847/1853/1539 f 1837/1841/1527 1833/1844/1530 1841/1850/1536 1847/1853/1539 f 1840/1847/1533 1848/1814/1500 1849/1817/1503 1845/1848/1534 f 1849/1817/1503 1847/1853/1539 1841/1850/1536 1845/1848/1534 f 1850/1816/1502 1846/1852/1538 1847/1853/1539 1849/1817/1503 f 1862/1854/1540 1851/1855/1541 1864/1856/1542 1863/1857/1543 f 1855/1858/1544 1852/1859/1545 1857/1860/1546 1856/1861/1547 f 1802/1862/1548 1799/1863/1549 1853/1864/1550 1854/1865/1551 f 1803/1831/1517 1802/1862/1548 1854/1865/1551 1824/1832/1518 f 1853/1864/1550 1855/1858/1544 1856/1861/1547 1854/1865/1551 f 1856/1861/1547 1829/1818/1504 1824/1832/1518 1854/1865/1551 f 1857/1860/1546 1817/1819/1505 1829/1818/1504 1856/1861/1547 f 1806/1866/1552 1720/1687/1373 1698/1688/1374 1859/1867/1553 f 1799/1863/1549 1798/1868/1554 1858/1869/1555 1853/1864/1550 f 1798/1868/1554 1806/1866/1552 1859/1867/1553 1858/1869/1555 f 1860/1870/1556 1852/1859/1545 1855/1858/1544 1861/1871/1557 f 1855/1858/1544 1853/1864/1550 1858/1869/1555 1861/1871/1557 f 1698/1688/1374 1862/1854/1540 1863/1857/1543 1859/1867/1553 f 1863/1857/1543 1861/1871/1557 1858/1869/1555 1859/1867/1553 f 1864/1856/1542 1860/1870/1556 1861/1871/1557 1863/1857/1543 f 1865/1872/1558 1872/1873/1559 1871/1874/1560 1870/1875/1561 f 1868/1876/1562 1867/1877/1563 1866/1878/1564 1869/1879/1565 f 1857/1860/1546 1852/1859/1545 1867/1877/1563 1868/1876/1562 f 1843/1846/1532 1817/1819/1505 1857/1860/1546 1868/1876/1562 f 1840/1847/1533 1843/1846/1532 1868/1876/1562 1869/1879/1565 f 1869/1879/1565 1866/1878/1564 1870/1875/1561 1871/1874/1560 f 1848/1814/1500 1840/1847/1533 1869/1879/1565 1871/1874/1560 f 1872/1873/1559 1816/1815/1501 1848/1814/1500 1871/1874/1560 f 1876/1880/1566 1873/1881/1567 1878/1882/1568 1877/1883/1569 f 1874/1884/1570 1866/1878/1564 1867/1877/1563 1875/1885/1571 f 1867/1877/1563 1852/1859/1545 1860/1870/1556 1875/1885/1571 f 1851/1855/1541 1876/1880/1566 1877/1883/1569 1864/1856/1542 f 1877/1883/1569 1875/1885/1571 1860/1870/1556 1864/1856/1542 f 1878/1882/1568 1874/1884/1570 1875/1885/1571 1877/1883/1569 f 1879/1886/1572 1865/1872/1558 1870/1875/1561 1880/1887/1573 f 1870/1875/1561 1866/1878/1564 1874/1884/1570 1880/1887/1573 f 1873/1881/1567 1881/1810/1496 1882/1813/1499 1878/1882/1568 f 1882/1813/1499 1880/1887/1573 1874/1884/1570 1878/1882/1568 f 1883/1812/1498 1879/1886/1572 1880/1887/1573 1882/1813/1499 f 1046/1677/1363 1713/1676/1362 1885/1888/1574 1045/1889/1575 f 1885/1888/1574 1713/1676/1362 1712/1675/1361 1714/1701/1387 f 1886/1890/1576 1885/1888/1574 1714/1701/1387 1715/1700/1386 f 1045/1889/1575 1885/1888/1574 1886/1890/1576 1048/1891/1577 f 1886/1890/1576 1715/1700/1386 1706/1699/1385 1884/1892/1578 f 1884/1892/1578 1044/1893/1579 1048/1891/1577 1886/1890/1576 f 1792/1772/1458 1703/1681/1367 1049/1680/1366 1254/1773/1459 f 1792/1772/1458 1794/1776/1462 1701/1682/1368 1703/1681/1367 f 1702/1683/1369 2087/1684/1370 1895/1894/1580 2077/1895/1581 f 1887/1896/1582 1899/1897/1583 1052/1898/1584 1057/1899/1585 f 1055/1900/1586 1892/1901/1587 1795/1781/1467 1056/1780/1466 f 1053/1902/1588 1891/1903/1589 2078/1904/1590 1054/1905/1591 f 1893/1906/1592 1898/1907/1593 1899/1897/1583 1887/1896/1582 f 1894/1908/1594 2077/1895/1581 2078/1904/1590 1891/1903/1589 f 1895/1894/1580 1797/1783/1469 1795/1781/1467 1892/1901/1587 f 1889/1909/1595 1898/1907/1593 1893/1906/1592 1888/1910/1596 f 1702/1683/1369 2077/1895/1581 1894/1908/1594 1890/1911/1597 f 1702/1683/1369 1890/1911/1597 1896/1912/1598 1697/1685/1371 f 1890/1911/1597 1889/1909/1595 1897/1913/1599 1896/1912/1598 f 1862/1854/1540 1698/1688/1374 1697/1685/1371 1896/1912/1598 f 1851/1855/1541 1862/1854/1540 1896/1912/1598 1897/1913/1599 f 1890/1911/1597 1894/1908/1594 1898/1907/1593 1889/1909/1595 f 1899/1897/1583 1898/1907/1593 1894/1908/1594 1891/1903/1589 f 1052/1898/1584 1899/1897/1583 1891/1903/1589 1053/1902/1588 f 1815/1811/1497 1881/1810/1496 1900/1914/1600 1901/1915/1601 f 1873/1881/1567 1876/1880/1566 1902/1916/1602 1903/1917/1603 f 1876/1880/1566 1851/1855/1541 1897/1913/1599 1902/1916/1602 f 1881/1810/1496 1873/1881/1567 1903/1917/1603 1900/1914/1600 f 1900/1914/1600 1904/1918/1604 1905/1919/1605 1901/1915/1601 f 1902/1916/1602 1906/1920/1606 1907/1921/1607 1903/1917/1603 f 1889/1909/1595 1906/1920/1606 1902/1916/1602 1897/1913/1599 f 1907/1921/1607 1904/1918/1604 1900/1914/1600 1903/1917/1603 f 1905/1919/1605 1904/1918/1604 1908/1922/1608 1909/1923/1609 f 1907/1921/1607 1906/1920/1606 1910/1924/1610 1911/1925/1611 f 1906/1920/1606 1889/1909/1595 1888/1910/1596 1910/1924/1610 f 1904/1918/1604 1907/1921/1607 1911/1925/1611 1908/1922/1608 f 1913/1926/1612 1256/1927/1613 1255/1928/1614 1912/1929/1615 f 1914/1930/1616 1257/1931/1617 1256/1927/1613 1913/1926/1612 f 1919/1932/1618 1258/1933/1619 1257/1931/1617 1914/1930/1616 f 1925/1934/1620 1926/1935/1621 1909/1923/1609 1908/1922/1608 f 1925/1934/1620 1908/1922/1608 1911/1925/1611 1924/1936/1622 f 1913/1926/1612 1915/1937/1623 1917/1938/1624 1914/1930/1616 f 1913/1926/1612 1912/1929/1615 1916/1939/1625 1915/1937/1623 f 1924/1936/1622 1911/1925/1611 1910/1924/1610 1923/1940/1626 f 1914/1930/1616 1917/1938/1624 1918/1941/1627 1919/1932/1618 f 1923/1940/1626 1910/1924/1610 1888/1910/1596 1922/1942/1628 f 1919/1932/1618 1918/1941/1627 1920/1943/1629 1921/1944/1630 f 1259/1945/1631 1258/1933/1619 1919/1932/1618 1921/1944/1630 f 1920/1943/1629 1922/1942/1628 1888/1910/1596 1893/1906/1592 f 1921/1944/1630 1920/1943/1629 1893/1906/1592 1887/1896/1582 f 1057/1899/1585 1259/1945/1631 1921/1944/1630 1887/1896/1582 f 1918/1941/1627 1923/1940/1626 1922/1942/1628 1920/1943/1629 f 1917/1938/1624 1924/1936/1622 1923/1940/1626 1918/1941/1627 f 1915/1937/1623 1925/1934/1620 1924/1936/1622 1917/1938/1624 f 1915/1937/1623 1916/1939/1625 1926/1935/1621 1925/1934/1620 f 1773/1729/1415 1772/1728/1414 1928/1755/1441 1927/1757/1443 f 1772/1728/1414 1771/1727/1413 1929/1753/1439 1928/1755/1441 f 1771/1727/1413 1770/1726/1412 1930/1751/1437 1929/1753/1439 f 1770/1726/1412 1769/1725/1411 1931/1749/1435 1930/1751/1437 f 1769/1725/1411 1768/1724/1410 1932/1747/1433 1931/1749/1435 f 1768/1724/1410 1767/1723/1409 1933/1743/1429 1932/1747/1433 f 1767/1723/1409 1739/1759/1445 1934/1744/1430 1933/1743/1429 f 2027/1740/1426 1781/1739/1425 1935/1731/1417 2026/1730/1416 f 1695/1640/1326 1609/1544/1230 1937/1761/1447 1936/1760/1446 f 1609/1544/1230 1607/1543/1229 1938/1762/1448 1937/1761/1447 f 1607/1543/1229 1601/1532/1218 1939/1531/1217 1938/1762/1448 f 1520/1442/1128 1940/1441/1127 1939/1531/1217 1601/1532/1218 f 1529/1443/1129 1941/1438/1124 1940/1441/1127 1520/1442/1128 f 1558/1488/1174 1942/1485/1171 1941/1438/1124 1529/1443/1129 f 1590/1476/1162 1943/1468/1154 1942/1485/1171 1558/1488/1174 f 1543/1475/1161 1944/1465/1151 1943/1468/1154 1590/1476/1162 f 1557/1479/1165 1556/1482/1168 2093/1946/1632 2094/1947/1633 f 1553/1483/1169 1557/1479/1165 2094/1947/1633 2095/1948/1634 f 1566/1478/1164 1553/1483/1169 2095/1948/1634 2091/1949/1635 f 1546/1464/1150 1545/1463/1149 2097/1950/1636 2098/1951/1637 f 1545/1463/1149 1544/1474/1160 2096/1952/1638 2097/1950/1636 f 2090/1953/1639 1559/1466/1152 1944/1465/1151 2088/1954/1640 f 2024/1742/1428 1945/1741/1427 1759/1798/1484 2023/1799/1485 f 1946/1692/1378 1730/1955/1641 1731/1956/1642 1947/1689/1375 f 1731/1956/1642 1732/1957/1643 1948/1678/1364 1947/1689/1375 f 1732/1957/1643 1733/1703/1389 1949/1657/1343 1948/1678/1364 f 1733/1703/1389 1734/1702/1388 1950/1658/1344 1949/1657/1343 f 1951/1672/1358 1950/1658/1344 1734/1702/1388 1735/1704/1390 f 1952/1673/1359 1951/1672/1358 1735/1704/1390 1736/1705/1391 f 1953/1694/1380 1952/1673/1359 1736/1705/1391 1737/1706/1392 f 1738/1707/1393 1954/1695/1381 1953/1694/1380 1737/1706/1392 f 1955/1782/1468 1954/1695/1381 1738/1707/1393 1796/1777/1463 f 1793/1775/1461 1956/1778/1464 1955/1782/1468 1796/1777/1463 f 1793/1775/1461 1050/1774/1460 1051/1779/1465 1956/1778/1464 f 1957/1697/1383 1715/1700/1386 1733/1703/1389 1732/1957/1643 f 1731/1956/1642 1960/1958/1644 1957/1697/1383 1732/1957/1643 f 1731/1956/1642 1730/1955/1641 1959/1959/1645 1960/1958/1644 f 1957/1697/1383 1960/1958/1644 1959/1959/1645 1958/1698/1384 f 1961/1769/1455 1790/1517/1203 1789/1453/1139 1962/1768/1454 f 1963/1767/1453 1962/1768/1454 1789/1453/1139 1788/1452/1138 f 1788/1452/1138 1787/1451/1137 1964/1766/1452 1963/1767/1453 f 1965/1765/1451 1964/1766/1452 1787/1451/1137 1786/1524/1210 f 1966/1764/1450 1965/1765/1451 1786/1524/1210 1785/1526/1212 f 1785/1526/1212 1784/1528/1214 1967/1519/1205 1966/1764/1450 f 1784/1528/1214 1783/1771/1457 1599/1520/1206 1967/1519/1205 f 2064/1960/1646 1983/1961/1647 1968/1962/1648 2063/1963/1649 f 1973/1964/1650 1970/1965/1651 1971/1966/1652 1972/1967/1653 f 1974/1968/1654 1975/1969/1655 1970/1965/1651 1973/1964/1650 f 1982/1970/1656 1969/1971/1657 1975/1969/1655 1974/1968/1654 f 1972/1967/1653 1971/1966/1652 1976/1972/1658 1981/1973/1659 f 1981/1973/1659 1976/1972/1658 1977/1974/1660 1980/1975/1661 f 1980/1975/1661 1977/1974/1660 1978/1976/1662 1979/1977/1663 f 2065/1736/1422 1779/1735/1421 1983/1961/1647 2064/1960/1646 f 1935/1731/1417 1973/1964/1650 1988/1978/1664 1782/1732/1418 f 1974/1968/1654 1973/1964/1650 1935/1731/1417 1781/1739/1425 f 1980/1975/1661 1979/1977/1663 1985/1979/1665 1986/1980/1666 f 1981/1973/1659 1980/1975/1661 1986/1980/1666 1987/1981/1667 f 1972/1967/1653 1981/1973/1659 1987/1981/1667 1984/1982/1668 f 1982/1970/1656 1974/1968/1654 1781/1739/1425 1780/1738/1424 f 1973/1964/1650 1972/1967/1653 1984/1982/1668 1988/1978/1664 f 1945/1741/1427 1782/1732/1418 1988/1978/1664 1984/1982/1668 f 1759/1798/1484 1945/1741/1427 1984/1982/1668 1987/1981/1667 f 1763/1788/1474 1759/1798/1484 1987/1981/1667 1986/1980/1666 f 1760/1789/1475 1763/1788/1474 1986/1980/1666 1985/1979/1665 f 1979/1977/1663 1978/1976/1662 1989/1983/1669 1996/1984/1670 f 1996/1984/1670 1989/1983/1669 1990/1985/1671 1995/1986/1672 f 1995/1986/1672 1990/1985/1671 1991/1987/1673 1994/1988/1674 f 1994/1988/1674 1991/1987/1673 1992/1989/1675 1993/1990/1676 f 1994/1988/1674 1993/1990/1676 1997/1991/1677 1998/1992/1678 f 1995/1986/1672 1994/1988/1674 1998/1992/1678 1999/1993/1679 f 1996/1984/1670 1995/1986/1672 1999/1993/1679 2000/1994/1680 f 1979/1977/1663 1996/1984/1670 2000/1994/1680 1985/1979/1665 f 1999/1993/1679 1762/1795/1481 1761/1794/1480 2000/1994/1680 f 2000/1994/1680 1761/1794/1480 1760/1789/1475 1985/1979/1665 f 1758/1784/1470 1791/1792/1478 1998/1992/1678 1997/1991/1677 f 1998/1992/1678 1791/1792/1478 1762/1795/1481 1999/1993/1679 f 1993/1990/1676 1992/1989/1675 2001/1995/1681 2004/1996/1682 f 2003/1997/1683 2002/1998/1684 1599/1520/1206 1783/1771/1457 f 2004/1996/1682 2001/1995/1681 2002/1998/1684 2003/1997/1683 f 1758/1784/1470 1997/1991/1677 2005/1999/1685 1744/1785/1471 f 2003/1997/1683 2006/2000/1686 2005/1999/1685 2004/1996/1682 f 1744/1785/1471 2005/1999/1685 2006/2000/1686 1775/1800/1486 f 1598/1770/1456 2006/2000/1686 2003/1997/1683 1783/1771/1457 f 2004/1996/1682 2005/1999/1685 1997/1991/1677 1993/1990/1676 f 2007/2001/1687 1774/1713/1399 1695/1640/1326 1936/1760/1446 f 1741/1714/1400 1774/1713/1399 2007/2001/1687 2008/2002/1688 f 2008/2002/1688 2007/2001/1687 2009/2003/1689 2010/2004/1690 f 2009/2003/1689 2007/2001/1687 1936/1760/1446 1776/1521/1207 f 2002/1998/1684 2009/2003/1689 1776/1521/1207 1599/1520/1206 f 2010/2004/1690 2009/2003/1689 2002/1998/1684 2001/1995/1681 f 1775/1800/1486 2006/2000/1686 1598/1770/1456 2011/1641/1327 f 1598/1770/1456 1597/1527/1213 2012/1642/1328 2011/1641/1327 f 2012/1642/1328 1707/1645/1331 1699/1647/1333 2013/1643/1329 f 2014/1644/1330 2013/1643/1329 1719/1686/1372 1720/1687/1373 f 1806/1866/1552 2015/1801/1487 2014/1644/1330 1720/1687/1373 f 2016/1786/1472 2015/1801/1487 1806/1866/1552 1798/1868/1554 f 2017/1787/1473 2016/1786/1472 1798/1868/1554 1799/1863/1549 f 2018/1793/1479 2017/1787/1473 1799/1863/1549 1802/1862/1548 f 2019/1796/1482 2018/1793/1479 1802/1862/1548 1803/1831/1517 f 2020/1797/1483 2019/1796/1482 1803/1831/1517 1804/1830/1516 f 2021/1790/1476 2020/1797/1483 1804/1830/1516 1800/1834/1520 f 2022/1791/1477 2021/1790/1476 1800/1834/1520 1801/1827/1513 f 2023/1799/1485 2022/1791/1477 1801/1827/1513 1805/1826/1512 f 1807/1802/1488 2024/1742/1428 2023/1799/1485 1805/1826/1512 f 1750/1745/1431 2025/1733/1419 2024/1742/1428 1807/1802/1488 f 1934/1744/1430 2026/1730/1416 2025/1733/1419 1750/1745/1431 f 1739/1759/1445 2027/1740/1426 2026/1730/1416 1934/1744/1430 f 2029/1709/1395 2027/1740/1426 1739/1759/1445 1766/1710/1396 f 1764/1715/1401 2066/1734/1420 2067/1716/1402 1742/1712/1398 f 2013/1643/1329 1699/1647/1333 1718/1646/1332 1719/1686/1372 f 1694/1637/1323 2030/1636/1322 2029/1709/1395 1766/1710/1396 f 1606/1535/1221 2031/1538/1224 2030/1636/1322 1694/1637/1323 f 2032/1455/1141 2031/1538/1224 1606/1535/1221 1538/1392/1078 f 2033/1456/1142 2032/1455/1141 1538/1392/1078 1537/1391/1077 f 2034/1580/1266 2033/1456/1142 1537/1391/1077 1657/1575/1261 f 2035/1593/1279 2034/1580/1266 1657/1575/1261 1643/1592/1278 f 2036/1607/1293 2035/1593/1279 1643/1592/1278 1666/1601/1287 f 2037/1619/1305 2036/1607/1293 1666/1601/1287 1678/1613/1299 f 1678/1613/1299 1684/1624/1310 2038/1622/1308 2037/1619/1305 f 1684/1624/1310 1683/1623/1309 2039/1625/1311 2038/1622/1308 f 2040/1626/1312 2039/1625/1311 1683/1623/1309 1685/1621/1307 f 2041/1627/1313 2040/1626/1312 1685/1621/1307 1672/1620/1306 f 1765/1708/1394 2028/1737/1423 2027/1740/1426 2029/1709/1395 f 1651/1581/1267 2043/1569/1255 2042/1585/1271 1637/1586/1272 f 1539/1458/1144 2044/1377/1063 2043/1569/1255 1651/1581/1267 f 1539/1458/1144 1540/1457/1143 2045/1378/1064 2044/1377/1063 f 1540/1457/1143 1541/1459/1145 2046/1401/1087 2045/1378/1064 f 1541/1459/1145 1542/1460/1146 2047/1403/1089 2046/1401/1087 f 1661/1596/1282 2048/2005/1691 1672/1620/1306 1673/1608/1294 f 1637/1586/1272 2048/2005/1691 1661/1596/1282 1638/1587/1273 f 2042/1585/1271 2049/2006/1692 2048/2005/1691 1637/1586/1272 f 1672/1620/1306 2048/2005/1691 2049/2006/1692 2041/1627/1313 f 1636/1584/1270 1660/1606/1292 2049/2006/1692 2042/1585/1271 f 2041/1627/1313 2049/2006/1692 1660/1606/1292 1671/1618/1304 f 1779/1735/1421 2054/2007/1693 2055/2008/1694 1983/1961/1647 f 2010/2004/1690 2001/1995/1681 1992/1989/1675 2053/2009/1695 f 2053/2009/1695 1992/1989/1675 1991/1987/1673 2052/2010/1696 f 2052/2010/1696 1991/1987/1673 1990/1985/1671 2051/2011/1697 f 2051/2011/1697 1990/1985/1671 1989/1983/1669 2050/2012/1698 f 2056/2013/1699 2058/2014/1700 2057/2015/1701 1968/1962/1648 f 2056/2013/1699 1968/1962/1648 1983/1961/1647 2055/2008/1694 f 1741/1714/1400 2054/2007/1693 1779/1735/1421 1764/1715/1401 f 1741/1714/1400 2008/2002/1688 2010/2004/1690 2054/2007/1693 f 2055/2008/1694 2054/2007/1693 2010/2004/1690 2053/2009/1695 f 2052/2010/1696 2056/2013/1699 2055/2008/1694 2053/2009/1695 f 2051/2011/1697 2050/2012/1698 2057/2015/1701 2058/2014/1700 f 2051/2011/1697 2058/2014/1700 2056/2013/1699 2052/2010/1696 f 1970/1965/1651 1975/1969/1655 1969/1971/1657 2059/2016/1702 f 2062/2017/1703 2063/1963/1649 1968/1962/1648 2057/2015/1701 f 1971/1966/1652 1970/1965/1651 2059/2016/1702 2060/2018/1704 f 2061/2019/1705 2062/2017/1703 2057/2015/1701 2050/2012/1698 f 1977/1974/1660 1976/1972/1658 1971/1966/1652 2060/2018/1704 f 2061/2019/1705 1978/1976/1662 1977/1974/1660 2060/2018/1704 f 2060/2018/1704 2059/2016/1702 2062/2017/1703 2061/2019/1705 f 2059/2016/1702 1969/1971/1657 2063/1963/1649 2062/2017/1703 f 1982/1970/1656 2064/1960/1646 2063/1963/1649 1969/1971/1657 f 1780/1738/1424 2065/1736/1422 2064/1960/1646 1982/1970/1656 f 2028/1737/1423 2066/1734/1420 2065/1736/1422 1780/1738/1424 f 2067/1716/1402 2066/1734/1420 2028/1737/1423 1765/1708/1394 f 1693/1635/1321 2068/1638/1324 2067/1716/1402 1765/1708/1394 f 1693/1635/1321 1605/1537/1223 2069/1541/1227 2068/1638/1324 f 1605/1537/1223 1604/1536/1222 2070/1539/1225 2069/1541/1227 f 1604/1536/1222 1603/1530/1216 2071/1534/1220 2070/1539/1225 f 2072/1414/1100 2071/1534/1220 1603/1530/1216 1507/1420/1106 f 1510/1419/1105 2073/1411/1097 2072/1414/1100 1507/1420/1106 f 1579/1511/1197 2074/1491/1177 2073/1411/1097 1510/1419/1105 f 1579/1511/1197 1580/1512/1198 2075/1492/1178 2074/1491/1177 f 1580/1512/1198 1581/1513/1199 2076/1509/1195 2075/1492/1178 f 1989/1983/1669 1978/1976/1662 2061/2019/1705 2050/2012/1698 f 1577/1508/1194 1576/1507/1193 1634/1560/1246 1635/1567/1253 f 1622/1558/1244 1623/1561/1247 1626/1565/1251 1625/1562/1248 f 2078/1904/1590 2077/1895/1581 1895/1894/1580 1892/1901/1587 f 1054/1905/1591 2078/1904/1590 1892/1901/1587 1055/1900/1586 f 1722/1690/1376 2080/1667/1353 2079/1668/1354 1721/1691/1377 f 1723/1679/1365 2081/1670/1356 2080/1667/1353 1722/1690/1376 f 1724/1660/1346 2082/1664/1350 2081/1670/1356 1723/1679/1365 f 1725/1659/1345 2083/1662/1348 2082/1664/1350 1724/1660/1346 f 1726/1671/1357 2084/1656/1342 2083/1662/1348 1725/1659/1345 f 2085/1648/1334 2084/1656/1342 1726/1671/1357 1727/1674/1360 f 2086/1649/1335 2085/1648/1334 1727/1674/1360 1728/1693/1379 f 2087/1684/1370 2086/1649/1335 1728/1693/1379 1729/1696/1382 f 1895/1894/1580 2087/1684/1370 1729/1696/1382 1797/1783/1469 f 2089/2020/1706 1565/1490/1176 1559/1466/1152 2090/1953/1639 f 2092/2021/1707 1543/1475/1161 1566/1478/1164 2091/1949/1635 f 1944/1465/1151 1543/1475/1161 2092/2021/1707 2088/2022/1640 f 1544/1474/1160 1565/1490/1176 2089/2020/1706 2096/1952/1638 f 2611/2023/1708 2099/2024/1709 2109/2025/1710 2612/2026/1711 f 2101/2027/1712 2100/2028/1713 2110/2029/1714 2111/2030/1715 f 2102/2031/1716 2101/2027/1712 2111/2030/1715 2112/2032/1717 f 2103/2033/1718 2102/2031/1716 2112/2032/1717 2113/2034/1719 f 2104/2035/1720 2103/2033/1718 2113/2034/1719 2114/2036/1721 f 2151/2037/1722 2104/2035/1720 2114/2036/1721 2152/2038/1723 f 2106/2039/1724 2105/2040/1725 2115/2041/1726 2116/2042/1727 f 2107/2043/1728 2106/2039/1724 2116/2042/1727 2117/2044/1729 f 2108/2045/1730 2107/2043/1728 2117/2044/1729 2118/2046/1731 f 2099/2024/1709 2108/2045/1730 2118/2046/1731 2109/2025/1710 f 2612/2026/1711 2109/2025/1710 2119/2047/1732 2613/2048/1733 f 2613/2048/1733 2119/2047/1732 1497/1404/1090 2047/1403/1089 f 2119/2047/1732 2109/2025/1710 2123/2049/1734 2124/2050/1735 f 1497/1404/1090 2119/2047/1732 2124/2050/1735 1504/1407/1093 f 2124/2050/1735 2123/2049/1734 2121/2051/1736 2122/2052/1737 f 1504/1407/1093 2124/2050/1735 2122/2052/1737 1501/1410/1096 f 2639/2053/1738 2638/2054/1739 2128/2055/1740 2129/2056/1741 f 2125/2057/1742 2123/2049/1734 2109/2025/1710 2118/2046/1731 f 2126/2058/1743 2125/2057/1742 2118/2046/1731 2117/2044/1729 f 2121/2051/1736 2123/2049/1734 2125/2057/1742 2132/2059/1744 f 2132/2059/1744 2125/2057/1742 2126/2058/1743 2131/2060/1745 f 2131/2060/1745 2126/2058/1743 2127/2061/1746 2130/2062/1747 f 2111/2030/1715 2110/2029/1714 2133/2063/1748 2134/2064/1749 f 2112/2032/1717 2111/2030/1715 2134/2064/1749 2135/2065/1750 f 2113/2034/1719 2112/2032/1717 2135/2065/1750 2136/2066/1751 f 2114/2036/1721 2113/2034/1719 2136/2066/1751 2137/2067/1752 f 2110/2029/1714 2120/2068/1753 2138/2069/1754 2133/2063/1748 f 2120/2068/1753 1498/1429/1115 1519/1428/1114 2138/2069/1754 f 2145/2070/1755 2140/2071/1756 2141/2072/1757 2144/2073/1758 f 2143/2074/1759 2142/2075/1760 1524/1436/1122 1525/1435/1121 f 2144/2073/1758 2141/2072/1757 2142/2075/1760 2143/2074/1759 f 2512/2076/1761 2511/2077/1762 2171/2078/1763 2170/2079/1764 f 2139/2080/1765 2146/2081/1766 2129/2056/1741 2128/2055/1740 f 2142/2075/1760 2141/2072/1757 2187/2082/1767 2214/2083/1768 f 2187/2082/1767 2141/2072/1757 2150/2084/1769 2188/2085/1770 f 2150/2084/1769 2141/2072/1757 2140/2071/1756 2149/2086/1771 f 2147/2087/1772 2379/2088/1773 2378/2089/1774 2148/2090/1775 f 2380/2091/1776 2379/2088/1773 2147/2087/1772 2189/2092/1777 f 2105/2040/1725 2600/2093/1778 2599/2094/1779 2115/2041/1726 f 2100/2028/1713 2153/2095/1780 2154/2096/1781 2110/2029/1714 f 2110/2029/1714 2154/2096/1781 2155/2097/1782 2120/2068/1753 f 2120/2068/1753 2155/2097/1782 1542/1460/1146 1498/1429/1115 f 1585/1461/1147 1546/1464/1150 2158/2098/1783 2190/2099/1784 f 2515/2100/1785 2514/2101/1786 2193/2102/1787 2168/2103/1788 f 2161/2104/1789 2159/2105/1790 2145/2070/1755 2144/2073/1758 f 1525/1435/1121 1548/1472/1158 2160/2106/1791 2143/2074/1759 f 2158/2098/1783 2157/2107/1792 2191/2108/1793 2190/2099/1784 f 2160/2106/1791 2161/2104/1789 2144/2073/1758 2143/2074/1759 f 2156/2109/1794 2174/2110/1795 2195/2111/1796 2194/2112/1797 f 2166/2113/1798 1556/1482/1168 1594/1481/1167 2197/2114/1799 f 2164/2115/1800 2166/2113/1798 2197/2114/1799 2196/2116/1801 f 2513/2117/1802 2512/2076/1761 2170/2079/1764 2169/2118/1803 f 2146/2081/1766 2167/2119/1804 2162/2120/1805 2129/2056/1741 f 2157/2107/1792 2173/2121/1806 2192/2122/1807 2191/2108/1793 f 2145/2070/1755 2159/2105/1790 2169/2118/1803 2170/2079/1764 f 2140/2071/1756 2145/2070/1755 2170/2079/1764 2171/2078/1763 f 2640/2123/1808 2184/2124/1809 2172/2125/1810 2641/2126/1811 f 1555/1495/1181 1564/1497/1183 2172/2125/1810 2165/2127/1812 f 2168/2103/1788 2193/2102/1787 2192/2122/1807 2173/2121/1806 f 2174/2110/1795 2164/2115/1800 2196/2116/1801 2195/2111/1796 f 2175/2128/1813 1567/1499/1185 1501/1410/1096 2122/2052/1737 f 2131/2060/1745 2178/2129/1814 2177/2130/1815 2132/2059/1744 f 2177/2130/1815 2176/2131/1816 2121/2051/1736 2132/2059/1744 f 2176/2131/1816 2175/2128/1813 2122/2052/1737 2121/2051/1736 f 2131/2060/1745 2180/2132/1817 2179/2133/1818 2178/2129/1814 f 2180/2132/1817 2182/2134/1819 2181/2135/1820 2179/2133/1818 f 2182/2134/1819 1577/1508/1194 2183/2136/1821 2181/2135/1820 f 2641/2126/1811 2172/2125/1810 1564/1497/1183 2076/1509/1195 f 2165/2127/1812 2172/2125/1810 2184/2124/1809 2163/2137/1822 f 2163/2137/1822 2184/2124/1809 2129/2056/1741 2162/2120/1805 f 2640/2123/1808 2639/2053/1738 2129/2056/1741 2184/2124/1809 f 2180/2132/1817 2131/2060/1745 2130/2062/1747 2185/2138/1823 f 2180/2132/1817 2185/2138/1823 2186/2139/1824 2182/2134/1819 f 2182/2134/1819 2186/2139/1824 1581/1513/1199 1577/1508/1194 f 1612/1514/1200 2214/2083/1768 2187/2082/1767 1530/1515/1201 f 1530/1515/1201 2187/2082/1767 2188/2085/1770 1536/1516/1202 f 1790/1517/1203 2380/2091/1776 2189/2092/1777 1532/1518/1204 f 1548/1472/1158 1585/1461/1147 2190/2099/1784 2160/2106/1791 f 2191/2108/1793 2161/2104/1789 2160/2106/1791 2190/2099/1784 f 2192/2122/1807 2159/2105/1790 2161/2104/1789 2191/2108/1793 f 2193/2102/1787 2169/2118/1803 2159/2105/1790 2192/2122/1807 f 2514/2101/1786 2513/2117/1802 2169/2118/1803 2193/2102/1787 f 2195/2111/1796 2162/2120/1805 2167/2119/1804 2194/2112/1797 f 2196/2116/1801 2163/2137/1822 2162/2120/1805 2195/2111/1796 f 2197/2114/1799 2165/2127/1812 2163/2137/1822 2196/2116/1801 f 1594/1481/1167 1555/1495/1181 2165/2127/1812 2197/2114/1799 f 2534/2140/1825 2368/2141/1826 2367/2142/1827 2202/2143/1828 f 2378/2089/1774 2377/2144/1829 2198/2145/1830 2148/2090/1775 f 2377/2144/1829 2376/2146/1831 2199/2147/1832 2198/2145/1830 f 2199/2147/1832 2376/2146/1831 2375/2148/1833 2200/2149/1834 f 2171/2078/1763 2203/2150/1835 2149/2086/1771 2140/2071/1756 f 2127/2061/1746 2126/2058/1743 2117/2044/1729 2206/2151/1836 f 2511/2077/1762 2510/2152/1837 2203/2150/1835 2171/2078/1763 f 2139/2080/1765 2128/2055/1740 2205/2153/1838 2204/2154/1839 f 2128/2055/1740 2638/2054/1739 2637/2155/1840 2205/2153/1838 f 2152/2038/1723 2114/2036/1721 2137/2067/1752 2209/2156/1841 f 2117/2044/1729 2116/2042/1727 2207/2157/1842 2206/2151/1836 f 2116/2042/1727 2115/2041/1726 2208/2158/1843 2207/2157/1842 f 2115/2041/1726 2599/2094/1779 2598/2159/1844 2208/2158/1843 f 2636/2160/1845 2211/2161/1846 2205/2153/1838 2637/2155/1840 f 2635/2162/1847 2213/2163/1848 2211/2161/1846 2636/2160/1845 f 2211/2161/1846 2210/2164/1849 2204/2154/1839 2205/2153/1838 f 2213/2163/1848 2212/2165/1850 2210/2164/1849 2211/2161/1846 f 1524/1436/1122 2142/2075/1760 2214/2083/1768 1612/1514/1200 f 1614/1545/1231 1616/1548/1234 2216/2166/1851 2215/2167/1852 f 1616/1548/1234 1567/1499/1185 2175/2128/1813 2216/2166/1851 f 2215/2167/1852 2216/2166/1851 2217/2168/1853 2221/2169/1854 f 2216/2166/1851 2175/2128/1813 2176/2131/1816 2217/2168/1853 f 2217/2168/1853 2176/2131/1816 2177/2130/1815 2218/2170/1855 f 2219/2171/1856 2178/2129/1814 2179/2133/1818 2220/2172/1857 f 2218/2170/1855 2177/2130/1815 2178/2129/1814 2219/2171/1856 f 2224/2173/1858 2230/2174/1859 2229/2175/1860 2231/2176/1861 f 2183/2136/1821 2234/2177/1862 2233/2178/1863 2222/2179/1864 f 2223/2180/1865 2220/2172/1857 2179/2133/1818 2181/2135/1820 f 2183/2136/1821 2222/2179/1864 2223/2180/1865 2181/2135/1820 f 2233/2178/1863 2232/2181/1866 2225/2182/1867 2222/2179/1864 f 2223/2180/1865 2226/2183/1868 2227/2184/1869 2220/2172/1857 f 2220/2172/1857 2227/2184/1869 2228/2185/1870 2219/2171/1856 f 2219/2171/1856 2228/2185/1870 2229/2175/1860 2218/2170/1855 f 2218/2170/1855 2229/2175/1860 2230/2174/1859 2217/2168/1853 f 2217/2168/1853 2230/2174/1859 2224/2173/1858 2221/2169/1854 f 2229/2175/1860 2228/2185/1870 2227/2184/1869 2231/2176/1861 f 2224/2173/1858 2231/2176/1861 2225/2182/1867 2232/2181/1866 f 2227/2184/1869 2226/2183/1868 2225/2182/1867 2231/2176/1861 f 2221/2169/1854 2224/2173/1858 2232/2181/1866 2233/2178/1863 f 2234/2177/1862 2215/2167/1852 2221/2169/1854 2233/2178/1863 f 1635/1567/1253 1614/1545/1231 2215/2167/1852 2234/2177/1862 f 2611/2023/1708 2610/2186/1871 2249/2187/1872 2099/2024/1709 f 2101/2027/1712 2252/2188/1873 2251/2189/1874 2100/2028/1713 f 2102/2031/1716 2253/2190/1875 2252/2188/1873 2101/2027/1712 f 2103/2033/1718 2254/2191/1876 2253/2190/1875 2102/2031/1716 f 2104/2035/1720 2255/2192/1877 2254/2191/1876 2103/2033/1718 f 2151/2037/1722 2256/2193/1878 2255/2192/1877 2104/2035/1720 f 2106/2039/1724 2258/2194/1879 2257/2195/1880 2105/2040/1725 f 2107/2043/1728 2247/2196/1881 2258/2194/1879 2106/2039/1724 f 2108/2045/1730 2248/2197/1882 2247/2196/1881 2107/2043/1728 f 2099/2024/1709 2249/2187/1872 2248/2197/1882 2108/2045/1730 f 2105/2040/1725 2257/2195/1880 2601/2198/1883 2600/2093/1778 f 2100/2028/1713 2251/2189/1874 2250/2199/1884 2153/2095/1780 f 2248/2197/1882 2246/2200/1885 2245/2201/1886 2247/2196/1881 f 2249/2187/1872 2235/2202/1887 2246/2200/1885 2248/2197/1882 f 2610/2186/1871 2609/2203/1888 2235/2202/1887 2249/2187/1872 f 2251/2189/1874 2237/2204/1889 2236/2205/1890 2250/2199/1884 f 2252/2188/1873 2238/2206/1891 2237/2204/1889 2251/2189/1874 f 2253/2190/1875 2239/2207/1892 2238/2206/1891 2252/2188/1873 f 2254/2191/1876 2240/2208/1893 2239/2207/1892 2253/2190/1875 f 2255/2192/1877 2241/2209/1894 2240/2208/1893 2254/2191/1876 f 2256/2193/1878 2242/2210/1895 2241/2209/1894 2255/2192/1877 f 2257/2195/1880 2243/2211/1896 2602/2212/1897 2601/2198/1883 f 2258/2194/1879 2244/2213/1898 2243/2211/1896 2257/2195/1880 f 2247/2196/1881 2245/2201/1886 2244/2213/1898 2258/2194/1879 f 2238/2206/1891 2261/2214/1899 2260/2215/1900 2237/2204/1889 f 2239/2207/1892 2262/2216/1901 2261/2214/1899 2238/2206/1891 f 2240/2208/1893 2263/2217/1902 2262/2216/1901 2239/2207/1892 f 2241/2209/1894 2264/2218/1903 2263/2217/1902 2240/2208/1893 f 2242/2210/1895 2265/2219/1904 2264/2218/1903 2241/2209/1894 f 2244/2213/1898 2267/2220/1905 2266/2221/1906 2243/2211/1896 f 2245/2201/1886 2268/2222/1907 2267/2220/1905 2244/2213/1898 f 2246/2200/1885 2269/2223/1908 2268/2222/1907 2245/2201/1886 f 2235/2202/1887 2259/2224/1909 2269/2223/1908 2246/2200/1885 f 2243/2211/1896 2266/2221/1906 2603/2225/1910 2602/2212/1897 f 2261/2214/1899 2273/2226/1911 2272/2227/1912 2260/2215/1900 f 2262/2216/1901 2274/2228/1913 2273/2226/1911 2261/2214/1899 f 2263/2217/1902 2275/2229/1914 2274/2228/1913 2262/2216/1901 f 2264/2218/1903 2276/2230/1915 2275/2229/1914 2263/2217/1902 f 2265/2219/1904 2277/2231/1916 2276/2230/1915 2264/2218/1903 f 2267/2220/1905 2279/2232/1917 2278/2233/1918 2266/2221/1906 f 2268/2222/1907 2280/2234/1919 2279/2232/1917 2267/2220/1905 f 2269/2223/1908 2281/2235/1920 2280/2234/1919 2268/2222/1907 f 2259/2224/1909 2270/2236/1921 2281/2235/1920 2269/2223/1908 f 2266/2221/1906 2278/2233/1918 2604/2237/1922 2603/2225/1910 f 2273/2226/1911 2284/2238/1923 2271/2239/1924 2272/2227/1912 f 2604/2237/1922 2278/2233/1918 2279/2232/1917 2605/2240/1925 f 2274/2228/1913 2275/2229/1914 2283/2241/1926 2282/2242/1927 f 2280/2234/1919 2281/2235/1920 2607/2243/1928 2606/2244/1929 f 2605/2240/1925 2279/2232/1917 2280/2234/1919 2606/2244/1929 f 2275/2229/1914 2276/2230/1915 2277/2231/1916 2283/2241/1926 f 2273/2226/1911 2274/2228/1913 2282/2242/1927 2284/2238/1923 f 2281/2235/1920 2270/2236/1921 2608/2245/1930 2607/2243/1928 f 2133/2063/1748 2285/2246/1931 2286/2247/1932 2134/2064/1749 f 2134/2064/1749 2286/2247/1932 2287/2248/1933 2135/2065/1750 f 2135/2065/1750 2287/2248/1933 2288/2249/1934 2136/2066/1751 f 2136/2066/1751 2288/2249/1934 2289/2250/1935 2137/2067/1752 f 2138/2069/1754 2290/2251/1936 2285/2246/1931 2133/2063/1748 f 1519/1428/1114 1692/1634/1320 2290/2251/1936 2138/2069/1754 f 2598/2159/1844 2597/2252/1937 2291/2253/1938 2208/2158/1843 f 2137/2067/1752 2289/2250/1935 2292/2254/1939 2209/2156/1841 f 2634/2255/1940 2294/2256/1941 2213/2163/1848 2635/2162/1847 f 2294/2256/1941 2293/2257/1942 2212/2165/1850 2213/2163/1848 f 2578/2258/1943 2581/2259/1944 2580/2260/1945 2579/2261/1946 f 2303/2262/1947 2199/2147/1832 2200/2149/1834 2579/2261/1946 f 2314/2263/1948 2650/2264/1949 2649/2265/1950 2297/2266/1951 f 2312/2267/1952 1260/2268/1953 1263/2269/1954 2302/2270/1955 f 2313/2271/1956 2312/2267/1952 2302/2270/1955 2298/2272/1957 f 2297/2266/1951 2649/2265/1950 2648/2273/1958 2303/2262/1947 f 2519/2274/1959 2319/2275/1960 2320/2276/1961 2520/2277/1962 f 2198/2145/1830 2199/2147/1832 2303/2262/1947 2304/2278/1963 f 2648/2273/1958 2647/2279/1964 2304/2278/1963 2303/2262/1947 f 2198/2145/1830 2304/2278/1963 2307/2280/1965 2148/2090/1775 f 2647/2279/1964 2646/2281/1966 2307/2280/1965 2304/2278/1963 f 2305/2282/1967 1705/1666/1352 1532/1518/1204 2189/2092/1777 f 2644/2283/1968 2079/1668/1354 1705/1666/1352 2305/2282/1967 f 2306/2284/1969 2305/2282/1967 2189/2092/1777 2147/2087/1772 f 2645/2285/1970 2644/2283/1968 2305/2282/1967 2306/2284/1969 f 2148/2090/1775 2307/2280/1965 2306/2284/1969 2147/2087/1772 f 2646/2281/1966 2645/2285/1970 2306/2284/1969 2307/2280/1965 f 2321/2286/1971 2322/2287/1972 2522/2288/1973 2521/2289/1974 f 2298/2272/1957 2302/2270/1955 2309/2290/1975 2308/2291/1976 f 2302/2270/1955 1263/2269/1954 1262/2292/1977 2309/2290/1975 f 2320/2276/1961 2321/2286/1971 2521/2289/1974 2520/2277/1962 f 2518/2293/1978 2318/2294/1979 2319/2275/1960 2519/2274/1959 f 2301/2295/1980 1265/2296/1981 1260/2268/1953 2312/2267/1952 f 2299/2297/1982 2301/2295/1980 2312/2267/1952 2313/2271/1956 f 2300/2298/1983 2651/2299/1984 2650/2264/1949 2314/2263/1948 f 2314/2263/1948 2315/2300/1985 2295/2301/1986 2300/2298/1983 f 2316/2302/1987 2296/2303/1988 2295/2301/1986 2315/2300/1985 f 2517/2304/1989 2317/2305/1990 2318/2294/1979 2518/2293/1978 f 1721/1691/1377 2317/2305/1990 2517/2304/1989 1946/1692/1378 f 2323/2306/1991 2523/2307/1992 2522/2288/1973 2322/2287/1972 f 2524/2308/1993 2523/2307/1992 2323/2306/1991 2324/2309/1994 f 2527/2310/1995 2311/2311/1996 1706/1699/1385 1958/1698/1384 f 2311/2311/1996 2327/2312/1997 2328/2313/1998 2310/2314/1999 f 2329/2315/2000 2308/2291/1976 2310/2314/1999 2328/2313/1998 f 2330/2316/2001 2298/2272/1957 2308/2291/1976 2329/2315/2000 f 2331/2317/2002 2313/2271/1956 2298/2272/1957 2330/2316/2001 f 2332/2318/2003 2299/2297/1982 2313/2271/1956 2331/2317/2002 f 2597/2252/1937 2596/2319/2004 2357/2320/2005 2291/2253/1938 f 2289/2250/1935 2334/2321/2006 2358/2322/2007 2292/2254/1939 f 2336/2323/2008 2356/2324/2009 2335/2325/2010 2365/2326/2011 f 2634/2255/1940 2633/2327/2012 2336/2323/2008 2294/2256/1941 f 2288/2249/1934 2337/2328/2013 2334/2321/2006 2289/2250/1935 f 1692/1634/1320 1747/1719/1405 2342/2329/2014 2290/2251/1936 f 2339/2330/2015 2337/2328/2013 2288/2249/1934 2287/2248/1933 f 2340/2331/2016 2339/2330/2015 2287/2248/1933 2286/2247/1932 f 2341/2332/2017 2340/2331/2016 2286/2247/1932 2285/2246/1931 f 2342/2329/2014 2341/2332/2017 2285/2246/1931 2290/2251/1936 f 2337/2328/2013 2360/2333/2018 2359/2334/2019 2334/2321/2006 f 2339/2330/2015 2361/2335/2020 2360/2333/2018 2337/2328/2013 f 2340/2331/2016 2362/2336/2021 2361/2335/2020 2339/2330/2015 f 2341/2332/2017 2363/2337/2022 2362/2336/2021 2340/2331/2016 f 2342/2329/2014 2364/2338/2023 2363/2337/2022 2341/2332/2017 f 1747/1719/1405 1773/1729/1415 2364/2338/2023 2342/2329/2014 f 2593/2339/2024 2592/2340/2025 2373/2341/2026 2506/2342/2027 f 2632/2343/2028 2631/2344/2029 2370/2345/2030 2356/2324/2009 f 2595/2346/2031 2594/2347/2032 2372/2348/2033 2371/2349/2034 f 2592/2340/2025 2591/2350/2035 2516/2351/2036 2373/2341/2026 f 2504/2352/2037 2344/2353/2038 2343/2354/2039 2505/2355/2040 f 2503/2356/2041 2345/2357/2042 2344/2353/2038 2504/2352/2037 f 2502/2358/2043 2346/2359/2044 2345/2357/2042 2503/2356/2041 f 2501/2360/2045 2347/2361/2046 2346/2359/2044 2502/2358/2043 f 2500/2362/2047 2348/2363/2048 2347/2361/2046 2501/2360/2045 f 2499/2364/2049 2349/2365/2050 2348/2363/2048 2500/2362/2047 f 1927/2366/1443 1757/2367/1444 2349/2365/2050 2499/2364/2049 f 2334/2321/2006 2359/2334/2019 2333/2368/2051 2358/2322/2007 f 2294/2256/1941 2336/2323/2008 2365/2326/2011 2293/2257/1942 f 2507/2369/2052 2367/2142/1827 2368/2141/1826 2508/2370/2053 f 2508/2370/2053 2368/2141/1826 2369/2371/2054 2509/2372/2055 f 2509/2372/2055 2369/2371/2054 2203/2150/1835 2510/2152/1837 f 2533/2373/2056 2369/2371/2054 2368/2141/1826 2534/2140/1825 f 2203/2150/1835 2369/2371/2054 2533/2373/2056 2532/2374/2057 f 2149/2086/1771 2203/2150/1835 2532/2374/2057 2531/2375/2058 f 2530/2376/2059 2150/2084/1769 2149/2086/1771 2531/2375/2058 f 2188/2085/1770 2150/2084/1769 2530/2376/2059 2529/2377/2060 f 1536/1516/1202 2188/2085/1770 2529/2377/2060 1961/1769/1455 f 2200/2149/1834 2375/2148/1833 2374/2378/2061 2201/2379/2062 f 2382/2380/2063 2383/2381/2064 1266/2382/2065 1470/2383/2066 f 2332/2318/2003 2386/2384/2067 2384/2385/2068 2299/2297/1982 f 2526/2386/2069 2385/2387/2070 1272/2388/2071 1267/2389/2072 f 2324/2309/1994 2387/2390/2073 2525/2391/2074 2524/2308/1993 f 2384/2385/2068 2386/2384/2067 2383/2381/2064 2382/2380/2063 f 2526/2386/2069 2525/2391/2074 2387/2390/2073 2385/2387/2070 f 2350/2392/2075 2584/2393/2076 2583/2394/2077 2338/2395/2078 f 2355/2396/2079 2589/2397/2080 2588/2398/2081 2352/2399/2082 f 2381/2400/2083 2585/2401/2084 2584/2393/2076 2350/2392/2075 f 2353/2402/2085 2587/2403/2086 2586/2404/2087 2354/2405/2088 f 2352/2399/2082 2588/2398/2081 2587/2403/2086 2353/2402/2085 f 2351/2406/2089 2590/2407/2090 2589/2397/2080 2355/2396/2079 f 2338/2395/2078 2583/2394/2077 2582/2408/2091 2366/2409/2092 f 2354/2405/2088 2586/2404/2087 2585/2401/2084 2381/2400/2083 f 2582/2408/2091 2581/2259/1944 2578/2258/1943 2366/2409/2092 f 2344/2353/2038 2398/2410/2093 2397/2411/2094 2343/2354/2039 f 2345/2357/2042 2399/2412/2095 2398/2410/2093 2344/2353/2038 f 2346/2359/2044 2400/2413/2096 2399/2412/2095 2345/2357/2042 f 2347/2361/2046 2401/2414/2097 2400/2413/2096 2346/2359/2044 f 2348/2363/2048 2402/2415/2098 2401/2414/2097 2347/2361/2046 f 2349/2365/2050 2403/2416/2099 2402/2415/2098 2348/2363/2048 f 1757/2367/1444 1814/2417/1495 2403/2416/2099 2349/2365/2050 f 2461/2418/2100 2462/2419/2101 1883/2420/1498 1815/2421/1497 f 2432/2422/2102 2433/2423/2103 1850/2424/1502 1816/2425/1501 f 2416/2426/2104 2417/2427/2105 2418/2428/2106 2404/2429/2107 f 2408/2430/2108 2409/2431/2109 2410/2432/2110 2405/2433/2111 f 2395/2434/2112 2407/2435/2113 2406/2436/2114 2391/2437/2115 f 2397/2411/2094 2398/2410/2093 2407/2435/2113 2395/2434/2112 f 2406/2436/2114 2407/2435/2113 2409/2431/2109 2408/2430/2108 f 2409/2431/2109 2407/2435/2113 2398/2410/2093 2399/2412/2095 f 2410/2432/2110 2409/2431/2109 2399/2412/2095 2400/2413/2096 f 2394/2438/2116 2413/2439/2117 2411/2440/2118 2393/2441/2119 f 2391/2437/2115 2406/2436/2114 2412/2442/2120 2390/2443/2121 f 2390/2443/2121 2412/2442/2120 2413/2439/2117 2394/2438/2116 f 2414/2444/2122 2415/2445/2123 2408/2430/2108 2405/2433/2111 f 2408/2430/2108 2415/2445/2123 2412/2442/2120 2406/2436/2114 f 2411/2440/2118 2413/2439/2117 2417/2427/2105 2416/2426/2104 f 2417/2427/2105 2413/2439/2117 2412/2442/2120 2415/2445/2123 f 2418/2428/2106 2417/2427/2105 2415/2445/2123 2414/2444/2122 f 1832/2446/1524 2423/2447/2124 2424/2448/2125 1839/2449/1525 f 2421/2450/2126 2422/2451/2127 2419/2452/2128 2420/2453/2129 f 2410/2432/2110 2421/2450/2126 2420/2453/2129 2405/2433/2111 f 2401/2414/2097 2421/2450/2126 2410/2432/2110 2400/2413/2096 f 2402/2415/2098 2422/2451/2127 2421/2450/2126 2401/2414/2097 f 2422/2451/2127 2424/2448/2125 2423/2447/2124 2419/2452/2128 f 2403/2416/2099 2424/2448/2125 2422/2451/2127 2402/2415/2098 f 1839/2449/1525 2424/2448/2125 2403/2416/2099 1814/2417/1495 f 2428/2454/2130 2429/2455/2131 2430/2456/2132 2425/2457/2133 f 2426/2458/2134 2427/2459/2135 2420/2453/2129 2419/2452/2128 f 2420/2453/2129 2427/2459/2135 2414/2444/2122 2405/2433/2111 f 2404/2429/2107 2418/2428/2106 2429/2455/2131 2428/2454/2130 f 2429/2455/2131 2418/2428/2106 2414/2444/2122 2427/2459/2135 f 2430/2456/2132 2429/2455/2131 2427/2459/2135 2426/2458/2134 f 1846/2460/1538 2431/2461/2136 2423/2447/2124 1832/2446/1524 f 2423/2447/2124 2431/2461/2136 2426/2458/2134 2419/2452/2128 f 2425/2457/2133 2430/2456/2132 2433/2423/2103 2432/2422/2102 f 2433/2423/2103 2430/2456/2132 2426/2458/2134 2431/2461/2136 f 1850/2424/1502 2433/2423/2103 2431/2461/2136 1846/2460/1538 f 2445/2462/2137 2446/2463/2138 2447/2464/2139 2434/2465/2140 f 2438/2466/2141 2439/2467/2142 2440/2468/2143 2435/2469/2144 f 2392/2470/2145 2437/2471/2146 2436/2472/2147 2389/2473/2148 f 2393/2441/2119 2411/2440/2118 2437/2471/2146 2392/2470/2145 f 2436/2472/2147 2437/2471/2146 2439/2467/2142 2438/2466/2141 f 2439/2467/2142 2437/2471/2146 2411/2440/2118 2416/2426/2104 f 2440/2468/2143 2439/2467/2142 2416/2426/2104 2404/2429/2107 f 2396/2474/2149 2442/2475/2150 2296/2303/1988 2316/2302/1987 f 2389/2473/2148 2436/2472/2147 2441/2476/2151 2388/2477/2152 f 2388/2477/2152 2441/2476/2151 2442/2475/2150 2396/2474/2149 f 2443/2478/2153 2444/2479/2154 2438/2466/2141 2435/2469/2144 f 2438/2466/2141 2444/2479/2154 2441/2476/2151 2436/2472/2147 f 2296/2303/1988 2442/2475/2150 2446/2463/2138 2445/2462/2137 f 2446/2463/2138 2442/2475/2150 2441/2476/2151 2444/2479/2154 f 2447/2464/2139 2446/2463/2138 2444/2479/2154 2443/2478/2153 f 1865/2480/1558 2452/2481/2155 2453/2482/2156 1872/2483/1559 f 2450/2484/2157 2451/2485/2158 2448/2486/2159 2449/2487/2160 f 2440/2468/2143 2450/2484/2157 2449/2487/2160 2435/2469/2144 f 2428/2454/2130 2450/2484/2157 2440/2468/2143 2404/2429/2107 f 2425/2457/2133 2451/2485/2158 2450/2484/2157 2428/2454/2130 f 2451/2485/2158 2453/2482/2156 2452/2481/2155 2448/2486/2159 f 2432/2422/2102 2453/2482/2156 2451/2485/2158 2425/2457/2133 f 1872/2483/1559 2453/2482/2156 2432/2422/2102 1816/2425/1501 f 2457/2488/2161 2458/2489/2162 2459/2490/2163 2454/2491/2164 f 2455/2492/2165 2456/2493/2166 2449/2487/2160 2448/2486/2159 f 2449/2487/2160 2456/2493/2166 2443/2478/2153 2435/2469/2144 f 2434/2465/2140 2447/2464/2139 2458/2489/2162 2457/2488/2161 f 2458/2489/2162 2447/2464/2139 2443/2478/2153 2456/2493/2166 f 2459/2490/2163 2458/2489/2162 2456/2493/2166 2455/2492/2165 f 1879/2494/1572 2460/2495/2167 2452/2481/2155 1865/2480/1558 f 2452/2481/2155 2460/2495/2167 2455/2492/2165 2448/2486/2159 f 2454/2491/2164 2459/2490/2163 2462/2419/2101 2461/2418/2100 f 2462/2419/2101 2459/2490/2163 2455/2492/2165 2460/2495/2167 f 1883/2420/1498 2462/2419/2101 2460/2495/2167 1879/2494/1572 f 1262/2292/1977 1261/2496/2168 2463/2497/2169 2309/2290/1975 f 2463/2497/2169 2310/2314/1999 2308/2291/1976 2309/2290/1975 f 2464/2498/2170 2311/2311/1996 2310/2314/1999 2463/2497/2169 f 1261/2496/2168 1264/2499/2171 2464/2498/2170 2463/2497/2169 f 2464/2498/2170 1884/1892/1578 1706/1699/1385 2311/2311/1996 f 1884/1892/1578 2464/2498/2170 1264/2499/2171 1044/1893/1579 f 2382/2380/2063 1470/2383/2066 1265/2296/1981 2301/2295/1980 f 2382/2380/2063 2301/2295/1980 2299/2297/1982 2384/2385/2068 f 2300/2298/1983 2642/2500/2172 2473/2501/2173 2651/2299/1984 f 2465/2502/2174 1273/2503/2175 1268/2504/2176 2477/2505/2177 f 1271/2506/2178 1272/2388/2071 2385/2387/2070 2470/2507/2179 f 1269/2508/2180 1270/2509/2181 2643/2510/2182 2469/2511/2183 f 2471/2512/2184 2465/2502/2174 2477/2505/2177 2476/2513/2185 f 2472/2514/2186 2469/2511/2183 2643/2510/2182 2642/2500/2172 f 2473/2501/2173 2470/2507/2179 2385/2387/2070 2387/2390/2073 f 2467/2515/2187 2466/2516/2188 2471/2512/2184 2476/2513/2185 f 2300/2298/1983 2468/2517/2189 2472/2514/2186 2642/2500/2172 f 2300/2298/1983 2295/2301/1986 2474/2518/2190 2468/2517/2189 f 2468/2517/2189 2474/2518/2190 2475/2519/2191 2467/2515/2187 f 2445/2462/2137 2474/2518/2190 2295/2301/1986 2296/2303/1988 f 2434/2465/2140 2475/2519/2191 2474/2518/2190 2445/2462/2137 f 2468/2517/2189 2467/2515/2187 2476/2513/2185 2472/2514/2186 f 2477/2505/2177 2469/2511/2183 2472/2514/2186 2476/2513/2185 f 1268/2504/2176 1269/2508/2180 2469/2511/2183 2477/2505/2177 f 1815/2421/1497 1901/2520/1601 2478/2521/2192 2461/2418/2100 f 2454/2491/2164 2480/2522/2193 2479/2523/2194 2457/2488/2161 f 2457/2488/2161 2479/2523/2194 2475/2519/2191 2434/2465/2140 f 2461/2418/2100 2478/2521/2192 2480/2522/2193 2454/2491/2164 f 2478/2521/2192 1901/2520/1601 1905/2524/1605 2481/2525/2195 f 2479/2523/2194 2480/2522/2193 2483/2526/2196 2482/2527/2197 f 2467/2515/2187 2475/2519/2191 2479/2523/2194 2482/2527/2197 f 2483/2526/2196 2480/2522/2193 2478/2521/2192 2481/2525/2195 f 1905/2524/1605 1909/2528/1609 2484/2529/2198 2481/2525/2195 f 2483/2526/2196 2486/2530/2199 2485/2531/2200 2482/2527/2197 f 2482/2527/2197 2485/2531/2200 2466/2516/2188 2467/2515/2187 f 2481/2525/2195 2484/2529/2198 2486/2530/2199 2483/2526/2196 f 2487/2532/2201 1912/2533/1615 1255/2534/1614 1471/2535/2202 f 2488/2536/2203 2487/2532/2201 1471/2535/2202 1472/2537/2204 f 2492/2538/2205 2488/2536/2203 1472/2537/2204 1473/2539/2206 f 2498/2540/2207 2484/2529/2198 1909/2528/1609 1926/2541/1621 f 2498/2540/2207 2497/2542/2208 2486/2530/2199 2484/2529/2198 f 2487/2532/2201 2488/2536/2203 2490/2543/2209 2489/2544/2210 f 2487/2532/2201 2489/2544/2210 1916/2545/1625 1912/2533/1615 f 2497/2542/2208 2496/2546/2211 2485/2531/2200 2486/2530/2199 f 2488/2536/2203 2492/2538/2205 2491/2547/2212 2490/2543/2209 f 2496/2546/2211 2495/2548/2213 2466/2516/2188 2485/2531/2200 f 2492/2538/2205 2494/2549/2214 2493/2550/2215 2491/2547/2212 f 1474/2551/2216 2494/2549/2214 2492/2538/2205 1473/2539/2206 f 2493/2550/2215 2471/2512/2184 2466/2516/2188 2495/2548/2213 f 2494/2549/2214 2465/2502/2174 2471/2512/2184 2493/2550/2215 f 1273/2503/2175 2465/2502/2174 2494/2549/2214 1474/2551/2216 f 2491/2547/2212 2493/2550/2215 2495/2548/2213 2496/2546/2211 f 2490/2543/2209 2491/2547/2212 2496/2546/2211 2497/2542/2208 f 2489/2544/2210 2490/2543/2209 2497/2542/2208 2498/2540/2207 f 2489/2544/2210 2498/2540/2207 1926/2541/1621 1916/2545/1625 f 1773/1729/1415 1927/2366/1443 2499/2364/2049 2364/2338/2023 f 2364/2338/2023 2499/2364/2049 2500/2362/2047 2363/2337/2022 f 2363/2337/2022 2500/2362/2047 2501/2360/2045 2362/2336/2021 f 2362/2336/2021 2501/2360/2045 2502/2358/2043 2361/2335/2020 f 2361/2335/2020 2502/2358/2043 2503/2356/2041 2360/2333/2018 f 2360/2333/2018 2503/2356/2041 2504/2352/2037 2359/2334/2019 f 2359/2334/2019 2504/2352/2037 2505/2355/2040 2333/2368/2051 f 2594/2347/2032 2593/2339/2024 2506/2342/2027 2372/2348/2033 f 2293/2257/1942 2507/2369/2052 2508/2370/2053 2212/2165/1850 f 2212/2165/1850 2508/2370/2053 2509/2372/2055 2210/2164/1849 f 2210/2164/1849 2509/2372/2055 2510/2152/1837 2204/2154/1839 f 2139/2080/1765 2204/2154/1839 2510/2152/1837 2511/2077/1762 f 2146/2081/1766 2139/2080/1765 2511/2077/1762 2512/2076/1761 f 2167/2119/1804 2146/2081/1766 2512/2076/1761 2513/2117/1802 f 2194/2112/1797 2167/2119/1804 2513/2117/1802 2514/2101/1786 f 2156/2109/1794 2194/2112/1797 2514/2101/1786 2515/2100/1785 f 2166/2113/1798 2657/2552/2217 2093/1946/1632 1556/1482/1168 f 2164/2115/1800 2658/2553/2218 2657/2552/2217 2166/2113/1798 f 2174/2110/1795 2655/2554/2219 2658/2553/2218 2164/2115/1800 f 1546/1464/1150 2098/1951/1637 2660/2555/2220 2158/2098/1783 f 2158/2098/1783 2660/2555/2220 2659/2556/2221 2157/2107/1792 f 2654/2557/2222 2652/2558/2223 2515/2100/1785 2168/2103/1788 f 2591/2350/2035 2590/2407/2090 2351/2406/2089 2516/2351/2036 f 1946/1692/1378 2517/2304/1989 2325/2559/2224 1730/1955/1641 f 2325/2559/2224 2517/2304/1989 2518/2293/1978 2326/2560/2225 f 2326/2560/2225 2518/2293/1978 2519/2274/1959 2327/2312/1997 f 2327/2312/1997 2519/2274/1959 2520/2277/1962 2328/2313/1998 f 2521/2289/1974 2329/2315/2000 2328/2313/1998 2520/2277/1962 f 2522/2288/1973 2330/2316/2001 2329/2315/2000 2521/2289/1974 f 2523/2307/1992 2331/2317/2002 2330/2316/2001 2522/2288/1973 f 2332/2318/2003 2331/2317/2002 2523/2307/1992 2524/2308/1993 f 2525/2391/2074 2386/2384/2067 2332/2318/2003 2524/2308/1993 f 2383/2381/2064 2386/2384/2067 2525/2391/2074 2526/2386/2069 f 2383/2381/2064 2526/2386/2069 1267/2389/2072 1266/2382/2065 f 2527/2310/1995 2326/2560/2225 2327/2312/1997 2311/2311/1996 f 2325/2559/2224 2326/2560/2225 2527/2310/1995 2528/2561/2226 f 2325/2559/2224 2528/2561/2226 1959/1959/1645 1730/1955/1641 f 2527/2310/1995 1958/1698/1384 1959/1959/1645 2528/2561/2226 f 1961/1769/1455 2529/2377/2060 2380/2091/1776 1790/1517/1203 f 2530/2376/2059 2379/2088/1773 2380/2091/1776 2529/2377/2060 f 2379/2088/1773 2530/2376/2059 2531/2375/2058 2378/2089/1774 f 2532/2374/2057 2377/2144/1829 2378/2089/1774 2531/2375/2058 f 2533/2373/2056 2376/2146/1831 2377/2144/1829 2532/2374/2057 f 2376/2146/1831 2533/2373/2056 2534/2140/1825 2375/2148/1833 f 2375/2148/1833 2534/2140/1825 2202/2143/1828 2374/2378/2061 f 2630/2562/2227 2629/2563/2228 2535/2564/2229 2550/2565/2230 f 2540/2566/2231 2539/2567/2232 2538/2568/2233 2537/2569/2234 f 2541/2570/2235 2540/2566/2231 2537/2569/2234 2542/2571/2236 f 2549/2572/2237 2541/2570/2235 2542/2571/2236 2536/2573/2238 f 2539/2567/2232 2548/2574/2239 2543/2575/2240 2538/2568/2233 f 2548/2574/2239 2547/2576/2241 2544/2577/2242 2543/2575/2240 f 2547/2576/2241 2546/2578/2243 2545/2579/2244 2544/2577/2242 f 2631/2344/2029 2630/2562/2227 2550/2565/2230 2370/2345/2030 f 2506/2342/2027 2373/2341/2026 2555/2580/2245 2540/2566/2231 f 2541/2570/2235 2372/2348/2033 2506/2342/2027 2540/2566/2231 f 2547/2576/2241 2553/2581/2246 2552/2582/2247 2546/2578/2243 f 2548/2574/2239 2554/2583/2248 2553/2581/2246 2547/2576/2241 f 2539/2567/2232 2551/2584/2249 2554/2583/2248 2548/2574/2239 f 2549/2572/2237 2371/2349/2034 2372/2348/2033 2541/2570/2235 f 2540/2566/2231 2555/2580/2245 2551/2584/2249 2539/2567/2232 f 2516/2351/2036 2551/2584/2249 2555/2580/2245 2373/2341/2026 f 2351/2406/2089 2554/2583/2248 2551/2584/2249 2516/2351/2036 f 2355/2396/2079 2553/2581/2246 2554/2583/2248 2351/2406/2089 f 2352/2399/2082 2552/2582/2247 2553/2581/2246 2355/2396/2079 f 2546/2578/2243 2563/2585/2250 2556/2586/2251 2545/2579/2244 f 2563/2585/2250 2562/2587/2252 2557/2588/2253 2556/2586/2251 f 2562/2587/2252 2561/2589/2254 2558/2590/2255 2557/2588/2253 f 2561/2589/2254 2560/2591/2256 2559/2592/2257 2558/2590/2255 f 2561/2589/2254 2565/2593/2258 2564/2594/2259 2560/2591/2256 f 2562/2587/2252 2566/2595/2260 2565/2593/2258 2561/2589/2254 f 2563/2585/2250 2567/2596/2261 2566/2595/2260 2562/2587/2252 f 2546/2578/2243 2552/2582/2247 2567/2596/2261 2563/2585/2250 f 2566/2595/2260 2567/2596/2261 2353/2402/2085 2354/2405/2088 f 2567/2596/2261 2552/2582/2247 2352/2399/2082 2353/2402/2085 f 2350/2392/2075 2564/2594/2259 2565/2593/2258 2381/2400/2083 f 2565/2593/2258 2566/2595/2260 2354/2405/2088 2381/2400/2083 f 2560/2591/2256 2571/2597/2262 2568/2598/2263 2559/2592/2257 f 2570/2599/2264 2374/2378/2061 2202/2143/1828 2569/2600/2265 f 2571/2597/2262 2570/2599/2264 2569/2600/2265 2568/2598/2263 f 2350/2392/2075 2338/2395/2078 2572/2601/2266 2564/2594/2259 f 2570/2599/2264 2571/2597/2262 2572/2601/2266 2573/2602/2267 f 2338/2395/2078 2366/2409/2092 2573/2602/2267 2572/2601/2266 f 2201/2379/2062 2374/2378/2061 2570/2599/2264 2573/2602/2267 f 2571/2597/2262 2560/2591/2256 2564/2594/2259 2572/2601/2266 f 2574/2603/2268 2507/2369/2052 2293/2257/1942 2365/2326/2011 f 2335/2325/2010 2575/2604/2269 2574/2603/2268 2365/2326/2011 f 2575/2604/2269 2577/2605/2270 2576/2606/2271 2574/2603/2268 f 2576/2606/2271 2367/2142/1827 2507/2369/2052 2574/2603/2268 f 2569/2600/2265 2202/2143/1828 2367/2142/1827 2576/2606/2271 f 2577/2605/2270 2568/2598/2263 2569/2600/2265 2576/2606/2271 f 2366/2409/2092 2578/2258/1943 2201/2379/2062 2573/2602/2267 f 2201/2379/2062 2578/2258/1943 2579/2261/1946 2200/2149/1834 f 2579/2261/1946 2580/2260/1945 2297/2266/1951 2303/2262/1947 f 2581/2259/1944 2316/2302/1987 2315/2300/1985 2580/2260/1945 f 2396/2474/2149 2316/2302/1987 2581/2259/1944 2582/2408/2091 f 2583/2394/2077 2388/2477/2152 2396/2474/2149 2582/2408/2091 f 2584/2393/2076 2389/2473/2148 2388/2477/2152 2583/2394/2077 f 2585/2401/2084 2392/2470/2145 2389/2473/2148 2584/2393/2076 f 2586/2404/2087 2393/2441/2119 2392/2470/2145 2585/2401/2084 f 2587/2403/2086 2394/2438/2116 2393/2441/2119 2586/2404/2087 f 2588/2398/2081 2390/2443/2121 2394/2438/2116 2587/2403/2086 f 2589/2397/2080 2391/2437/2115 2390/2443/2121 2588/2398/2081 f 2590/2407/2090 2395/2434/2112 2391/2437/2115 2589/2397/2080 f 2397/2411/2094 2395/2434/2112 2590/2407/2090 2591/2350/2035 f 2343/2354/2039 2397/2411/2094 2591/2350/2035 2592/2340/2025 f 2505/2355/2040 2343/2354/2039 2592/2340/2025 2593/2339/2024 f 2333/2368/2051 2505/2355/2040 2593/2339/2024 2594/2347/2032 f 2596/2319/2004 2358/2322/2007 2333/2368/2051 2594/2347/2032 f 2356/2324/2009 2336/2323/2008 2633/2327/2012 2632/2343/2028 f 2580/2260/1945 2315/2300/1985 2314/2263/1948 2297/2266/1951 f 2292/2254/1939 2358/2322/2007 2596/2319/2004 2597/2252/1937 f 2209/2156/1841 2292/2254/1939 2597/2252/1937 2598/2159/1844 f 2599/2094/1779 2152/2038/1723 2209/2156/1841 2598/2159/1844 f 2600/2093/1778 2151/2037/1722 2152/2038/1723 2599/2094/1779 f 2601/2198/1883 2256/2193/1878 2151/2037/1722 2600/2093/1778 f 2602/2212/1897 2242/2210/1895 2256/2193/1878 2601/2198/1883 f 2603/2225/1910 2265/2219/1904 2242/2210/1895 2602/2212/1897 f 2604/2237/1922 2277/2231/1916 2265/2219/1904 2603/2225/1910 f 2277/2231/1916 2604/2237/1922 2605/2240/1925 2283/2241/1926 f 2283/2241/1926 2605/2240/1925 2606/2244/1929 2282/2242/1927 f 2607/2243/1928 2284/2238/1923 2282/2242/1927 2606/2244/1929 f 2608/2245/1930 2271/2239/1924 2284/2238/1923 2607/2243/1928 f 2357/2320/2005 2596/2319/2004 2594/2347/2032 2595/2346/2031 f 2250/2199/1884 2236/2205/1890 2609/2203/1888 2610/2186/1871 f 2153/2095/1780 2250/2199/1884 2610/2186/1871 2611/2023/1708 f 2153/2095/1780 2611/2023/1708 2612/2026/1711 2154/2096/1781 f 2154/2096/1781 2612/2026/1711 2613/2048/1733 2155/2097/1782 f 2155/2097/1782 2613/2048/1733 2047/1403/1089 1542/1460/1146 f 2260/2215/1900 2272/2227/1912 2271/2239/1924 2614/2607/2272 f 2236/2205/1890 2237/2204/1889 2260/2215/1900 2614/2607/2272 f 2609/2203/1888 2236/2205/1890 2614/2607/2272 2615/2608/2273 f 2271/2239/1924 2608/2245/1930 2615/2608/2273 2614/2607/2272 f 2235/2202/1887 2609/2203/1888 2615/2608/2273 2259/2224/1909 f 2608/2245/1930 2270/2236/1921 2259/2224/1909 2615/2608/2273 f 2370/2345/2030 2550/2565/2230 2621/2609/2274 2620/2610/2275 f 2577/2605/2270 2619/2611/2276 2559/2592/2257 2568/2598/2263 f 2619/2611/2276 2618/2612/2277 2558/2590/2255 2559/2592/2257 f 2618/2612/2277 2617/2613/2278 2557/2588/2253 2558/2590/2255 f 2617/2613/2278 2616/2614/2279 2556/2586/2251 2557/2588/2253 f 2622/2615/2280 2535/2564/2229 2623/2616/2281 2624/2617/2282 f 2622/2615/2280 2621/2609/2274 2550/2565/2230 2535/2564/2229 f 2335/2325/2010 2356/2324/2009 2370/2345/2030 2620/2610/2275 f 2335/2325/2010 2620/2610/2275 2577/2605/2270 2575/2604/2269 f 2621/2609/2274 2619/2611/2276 2577/2605/2270 2620/2610/2275 f 2618/2612/2277 2619/2611/2276 2621/2609/2274 2622/2615/2280 f 2617/2613/2278 2624/2617/2282 2623/2616/2281 2616/2614/2279 f 2617/2613/2278 2618/2612/2277 2622/2615/2280 2624/2617/2282 f 2537/2569/2234 2625/2618/2283 2536/2573/2238 2542/2571/2236 f 2628/2619/2284 2623/2616/2281 2535/2564/2229 2629/2563/2228 f 2538/2568/2233 2626/2620/2285 2625/2618/2283 2537/2569/2234 f 2627/2621/2286 2616/2614/2279 2623/2616/2281 2628/2619/2284 f 2544/2577/2242 2626/2620/2285 2538/2568/2233 2543/2575/2240 f 2627/2621/2286 2626/2620/2285 2544/2577/2242 2545/2579/2244 f 2626/2620/2285 2627/2621/2286 2628/2619/2284 2625/2618/2283 f 2625/2618/2283 2628/2619/2284 2629/2563/2228 2536/2573/2238 f 2549/2572/2237 2536/2573/2238 2629/2563/2228 2630/2562/2227 f 2371/2349/2034 2549/2572/2237 2630/2562/2227 2631/2344/2029 f 2595/2346/2031 2371/2349/2034 2631/2344/2029 2632/2343/2028 f 2633/2327/2012 2357/2320/2005 2595/2346/2031 2632/2343/2028 f 2291/2253/1938 2357/2320/2005 2633/2327/2012 2634/2255/1940 f 2291/2253/1938 2634/2255/1940 2635/2162/1847 2208/2158/1843 f 2208/2158/1843 2635/2162/1847 2636/2160/1845 2207/2157/1842 f 2207/2157/1842 2636/2160/1845 2637/2155/1840 2206/2151/1836 f 2638/2054/1739 2127/2061/1746 2206/2151/1836 2637/2155/1840 f 2130/2062/1747 2127/2061/1746 2638/2054/1739 2639/2053/1738 f 2185/2138/1823 2130/2062/1747 2639/2053/1738 2640/2123/1808 f 2185/2138/1823 2640/2123/1808 2641/2126/1811 2186/2139/1824 f 2186/2139/1824 2641/2126/1811 2076/1509/1195 1581/1513/1199 f 2556/2586/2251 2616/2614/2279 2627/2621/2286 2545/2579/2244 f 1577/1508/1194 1635/1567/1253 2234/2177/1862 2183/2136/1821 f 2222/2179/1864 2225/2182/1867 2226/2183/1868 2223/2180/1865 f 2643/2510/2182 2470/2507/2179 2473/2501/2173 2642/2500/2172 f 1270/2509/2181 1271/2506/2178 2470/2507/2179 2643/2510/2182 f 2317/2305/1990 1721/1691/1377 2079/1668/1354 2644/2283/1968 f 2318/2294/1979 2317/2305/1990 2644/2283/1968 2645/2285/1970 f 2319/2275/1960 2318/2294/1979 2645/2285/1970 2646/2281/1966 f 2320/2276/1961 2319/2275/1960 2646/2281/1966 2647/2279/1964 f 2321/2286/1971 2320/2276/1961 2647/2279/1964 2648/2273/1958 f 2649/2265/1950 2322/2287/1972 2321/2286/1971 2648/2273/1958 f 2650/2264/1949 2323/2306/1991 2322/2287/1972 2649/2265/1950 f 2651/2299/1984 2324/2309/1994 2323/2306/1991 2650/2264/1949 f 2473/2501/2173 2387/2390/2073 2324/2309/1994 2651/2299/1984 f 2653/2622/2287 2654/2557/2222 2168/2103/1788 2173/2121/1806 f 2656/2623/2288 2655/2554/2219 2174/2110/1795 2156/2109/1794 f 2515/2100/1785 2652/2624/2223 2656/2623/2288 2156/2109/1794 f 2157/2107/1792 2659/2556/2221 2653/2622/2287 2173/2121/1806 usemtl BlackCloth f 338/2625/2289 330/2626/2290 331/2627/2291 339/2628/2292 f 332/2629/2293 340/2630/2294 339/2628/2292 331/2627/2291 f 333/2631/2295 341/2632/2296 340/2630/2294 332/2629/2293 f 334/2633/2297 342/2634/2298 341/2632/2296 333/2631/2295 f 335/2635/2299 343/2636/2300 342/2634/2298 334/2633/2297 f 336/2637/2301 344/2638/2302 343/2636/2300 335/2635/2299 f 337/2639/2303 345/2640/2304 344/2638/2302 336/2637/2301 f 330/2626/2290 338/2625/2289 345/2641/2304 337/2642/2303 f 347/2643/2305 354/2644/2306 426/2645/2307 346/2646/2308 f 348/2647/2309 355/2648/2310 354/2644/2306 347/2643/2305 f 349/2649/2311 356/2650/2312 355/2648/2310 348/2647/2309 f 350/2651/2313 357/2652/2314 356/2650/2312 349/2649/2311 f 351/2653/2315 358/2654/2316 357/2652/2314 350/2651/2313 f 352/2655/2317 432/2656/2318 358/2654/2316 351/2653/2315 f 353/2657/2319 359/2658/2320 432/2656/2318 352/2655/2317 f 346/2646/2308 426/2645/2307 359/2659/2320 353/2660/2319 f 331/2627/2291 330/2626/2290 369/2661/2321 368/2662/2322 f 368/2662/2322 369/2661/2321 360/2663/2323 361/2664/2324 f 332/2629/2293 331/2627/2291 368/2662/2322 370/2665/2325 f 370/2665/2325 368/2662/2322 361/2664/2324 362/2666/2326 f 333/2631/2295 332/2629/2293 370/2665/2325 371/2667/2327 f 371/2667/2327 370/2665/2325 362/2666/2326 363/2668/2328 f 334/2633/2297 333/2631/2295 371/2667/2327 372/2669/2329 f 372/2669/2329 371/2667/2327 363/2668/2328 364/2670/2330 f 335/2635/2299 334/2633/2297 372/2669/2329 373/2671/2331 f 373/2671/2331 372/2669/2329 364/2670/2330 365/2672/2332 f 336/2637/2301 335/2635/2299 373/2671/2331 374/2673/2333 f 374/2673/2333 373/2671/2331 365/2672/2332 366/2674/2334 f 337/2639/2303 336/2637/2301 374/2673/2333 375/2675/2335 f 375/2675/2335 374/2673/2333 366/2674/2334 367/2676/2336 f 330/2626/2290 337/2642/2303 375/2677/2335 369/2661/2321 f 369/2661/2321 375/2677/2335 367/2678/2336 360/2663/2323 f 355/2648/2310 377/2679/2337 376/2680/2338 354/2644/2306 f 356/2650/2312 378/2681/2339 377/2679/2337 355/2648/2310 f 357/2652/2314 379/2682/2340 378/2681/2339 356/2650/2312 f 358/2654/2316 380/2683/2341 379/2682/2340 357/2652/2314 f 377/2679/2337 382/2684/2342 381/2685/2343 376/2680/2338 f 378/2681/2339 383/2686/2344 382/2684/2342 377/2679/2337 f 379/2682/2340 384/2687/2345 383/2686/2344 378/2681/2339 f 380/2683/2341 385/2688/2346 384/2687/2345 379/2682/2340 f 382/2684/2342 387/2689/2347 386/2690/2348 381/2685/2343 f 383/2686/2344 388/2691/2349 387/2689/2347 382/2684/2342 f 384/2687/2345 389/2692/2350 388/2691/2349 383/2686/2344 f 385/2688/2346 390/2693/2351 389/2692/2350 384/2687/2345 f 385/2688/2346 465/2694/2352 466/2695/2353 390/2693/2351 f 380/2683/2341 464/2696/2354 465/2694/2352 385/2688/2346 f 358/2654/2316 432/2656/2318 464/2696/2354 380/2683/2341 f 426/2645/2307 354/2644/2306 376/2680/2338 391/2697/2355 f 376/2680/2338 381/2685/2343 392/2698/2356 391/2697/2355 f 381/2685/2343 386/2690/2348 393/2699/2357 392/2698/2356 f 338/2625/2289 339/2628/2292 395/2700/2358 394/2701/2359 f 394/2701/2359 395/2700/2358 347/2643/2305 346/2646/2308 f 339/2628/2292 340/2630/2294 396/2702/2360 395/2700/2358 f 395/2700/2358 396/2702/2360 348/2647/2309 347/2643/2305 f 340/2630/2294 341/2632/2296 397/2703/2361 396/2702/2360 f 396/2702/2360 397/2703/2361 349/2649/2311 348/2647/2309 f 341/2632/2296 342/2634/2298 398/2704/2362 397/2703/2361 f 397/2703/2361 398/2704/2362 350/2651/2313 349/2649/2311 f 342/2634/2298 343/2636/2300 399/2705/2363 398/2704/2362 f 398/2704/2362 399/2705/2363 351/2653/2315 350/2651/2313 f 343/2636/2300 344/2638/2302 400/2706/2364 399/2705/2363 f 399/2705/2363 400/2706/2364 352/2655/2317 351/2653/2315 f 344/2638/2302 345/2640/2304 401/2707/2365 400/2706/2364 f 400/2706/2364 401/2707/2365 353/2657/2319 352/2655/2317 f 345/2641/2304 338/2625/2289 394/2701/2359 401/2708/2365 f 401/2708/2365 394/2701/2359 346/2646/2308 353/2660/2319 f 403/2709/2366 402/2710/2367 410/2711/2368 411/2712/2369 f 404/2713/2370 403/2709/2366 411/2712/2369 412/2714/2371 f 405/2715/2372 404/2713/2370 412/2714/2371 413/2716/2373 f 406/2717/2374 405/2715/2372 413/2716/2373 414/2718/2375 f 407/2719/2376 406/2717/2374 414/2718/2375 415/2720/2377 f 408/2721/2378 407/2719/2376 415/2720/2377 416/2722/2379 f 409/2723/2380 408/2721/2378 416/2722/2379 417/2724/2381 f 402/2710/2367 409/2725/2380 417/2726/2381 410/2711/2368 f 419/2727/2382 418/2728/2383 426/2729/2307 427/2730/2384 f 420/2731/2385 419/2727/2382 427/2730/2384 428/2732/2386 f 421/2733/2387 420/2731/2385 428/2732/2386 429/2734/2388 f 422/2735/2389 421/2733/2387 429/2734/2388 430/2736/2390 f 423/2737/2391 422/2735/2389 430/2736/2390 431/2738/2392 f 424/2739/2393 423/2737/2391 431/2738/2392 432/2740/2318 f 425/2741/2394 424/2739/2393 432/2740/2318 359/2742/2320 f 418/2728/2383 425/2743/2394 359/2744/2320 426/2729/2307 f 442/2745/2395 402/2710/2367 403/2709/2366 441/2746/2396 f 441/2746/2396 434/2747/2397 433/2748/2398 442/2745/2395 f 404/2713/2370 443/2749/2399 441/2746/2396 403/2709/2366 f 443/2749/2399 435/2750/2400 434/2747/2397 441/2746/2396 f 405/2715/2372 444/2751/2401 443/2749/2399 404/2713/2370 f 444/2751/2401 436/2752/2402 435/2750/2400 443/2749/2399 f 406/2717/2374 445/2753/2403 444/2751/2401 405/2715/2372 f 445/2753/2403 437/2754/2404 436/2752/2402 444/2751/2401 f 407/2719/2376 446/2755/2405 445/2753/2403 406/2717/2374 f 446/2755/2405 438/2756/2406 437/2754/2404 445/2753/2403 f 408/2721/2378 447/2757/2407 446/2755/2405 407/2719/2376 f 447/2757/2407 439/2758/2408 438/2756/2406 446/2755/2405 f 409/2723/2380 448/2759/2409 447/2757/2407 408/2721/2378 f 448/2759/2409 440/2760/2410 439/2758/2408 447/2757/2407 f 402/2710/2367 442/2745/2395 448/2761/2409 409/2725/2380 f 442/2745/2395 433/2748/2398 440/2762/2410 448/2761/2409 f 428/2732/2386 427/2730/2384 449/2763/2411 450/2764/2412 f 429/2734/2388 428/2732/2386 450/2764/2412 451/2765/2413 f 430/2736/2390 429/2734/2388 451/2765/2413 452/2766/2414 f 431/2738/2392 430/2736/2390 452/2766/2414 453/2767/2415 f 450/2764/2412 449/2763/2411 454/2768/2416 455/2769/2417 f 451/2765/2413 450/2764/2412 455/2769/2417 456/2770/2418 f 452/2766/2414 451/2765/2413 456/2770/2418 457/2771/2419 f 453/2767/2415 452/2766/2414 457/2771/2419 458/2772/2420 f 455/2769/2417 454/2768/2416 459/2773/2421 460/2774/2422 f 456/2770/2418 455/2769/2417 460/2774/2422 461/2775/2423 f 457/2771/2419 456/2770/2418 461/2775/2423 462/2776/2424 f 458/2772/2420 457/2771/2419 462/2776/2424 463/2777/2425 f 458/2772/2420 463/2777/2425 466/2778/2353 465/2779/2352 f 453/2767/2415 458/2772/2420 465/2779/2352 464/2780/2354 f 431/2738/2392 453/2767/2415 464/2780/2354 432/2740/2318 f 426/2729/2307 391/2781/2355 449/2763/2411 427/2730/2384 f 449/2763/2411 391/2781/2355 392/2782/2356 454/2768/2416 f 454/2768/2416 392/2782/2356 393/2783/2357 459/2773/2421 f 410/2711/2368 467/2784/2426 468/2785/2427 411/2712/2369 f 467/2784/2426 418/2728/2383 419/2727/2382 468/2785/2427 f 411/2712/2369 468/2785/2427 469/2786/2428 412/2714/2371 f 468/2785/2427 419/2727/2382 420/2731/2385 469/2786/2428 f 412/2714/2371 469/2786/2428 470/2787/2429 413/2716/2373 f 469/2786/2428 420/2731/2385 421/2733/2387 470/2787/2429 f 413/2716/2373 470/2787/2429 471/2788/2430 414/2718/2375 f 470/2787/2429 421/2733/2387 422/2735/2389 471/2788/2430 f 414/2718/2375 471/2788/2430 472/2789/2431 415/2720/2377 f 471/2788/2430 422/2735/2389 423/2737/2391 472/2789/2431 f 415/2720/2377 472/2789/2431 473/2790/2432 416/2722/2379 f 472/2789/2431 423/2737/2391 424/2739/2393 473/2790/2432 f 416/2722/2379 473/2790/2432 474/2791/2433 417/2724/2381 f 473/2790/2432 424/2739/2393 425/2741/2394 474/2791/2433 f 417/2726/2381 474/2792/2433 467/2784/2426 410/2711/2368 f 474/2792/2433 425/2743/2394 418/2728/2383 467/2784/2426 f 722/2793/2434 721/2794/2435 734/2795/2436 735/2796/2437 f 723/2797/2438 722/2793/2434 735/2796/2437 736/2798/2439 f 724/2799/2440 723/2797/2438 736/2798/2439 737/2800/2441 f 719/2801/2442 734/2795/2436 721/2794/2435 720/2802/2443 f 724/2799/2440 737/2800/2441 729/2803/2444 725/2804/2445 f 735/2796/2437 734/2795/2436 738/2805/2446 739/2806/2447 f 736/2798/2439 735/2796/2437 739/2806/2447 740/2807/2448 f 737/2800/2441 736/2798/2439 740/2807/2448 741/2808/2449 f 718/2809/2450 738/2805/2446 734/2795/2436 719/2801/2442 f 726/2810/2451 729/2803/2444 737/2800/2441 741/2808/2449 f 738/2805/2446 742/2811/2452 743/2812/2453 739/2806/2447 f 739/2806/2447 743/2812/2453 744/2813/2454 740/2807/2448 f 740/2807/2448 744/2813/2454 745/2814/2455 741/2808/2449 f 717/2815/2456 742/2811/2452 738/2805/2446 718/2809/2450 f 726/2810/2451 741/2808/2449 745/2814/2455 727/2816/2457 f 742/2811/2452 2819/2817/2458 2820/2818/2459 743/2812/2453 f 743/2812/2453 2820/2818/2459 2821/2819/2460 744/2813/2454 f 744/2813/2454 2821/2819/2460 2822/2820/2461 745/2814/2455 f 727/2816/2457 745/2814/2455 2822/2820/2461 2823/2821/2462 f 2824/2822/2463 2819/2817/2458 742/2811/2452 717/2815/2456 f 2825/2823/2464 754/2824/2465 755/2825/2466 2826/2826/2467 f 2826/2826/2467 755/2825/2466 756/2827/2468 2827/2828/2469 f 2827/2829/2469 756/2830/2468 757/2831/2470 2828/2832/2471 f 714/2833/2472 754/2824/2465 2825/2823/2464 2829/2834/2473 f 2830/2835/2474 2828/2832/2471 757/2831/2470 885/2836/2475 f 754/2824/2465 758/2837/2476 759/2838/2477 755/2825/2466 f 755/2825/2466 759/2838/2477 760/2839/2478 756/2827/2468 f 756/2830/2468 760/2840/2478 761/2841/2479 757/2831/2470 f 713/2842/2480 758/2837/2476 754/2824/2465 714/2833/2472 f 885/2836/2475 757/2831/2470 761/2841/2479 886/2843/2481 f 763/2844/2482 762/2845/2483 770/2846/2484 771/2847/2485 f 764/2848/2486 763/2844/2482 771/2847/2485 772/2849/2487 f 765/2850/2488 764/2848/2486 772/2849/2487 773/2851/2489 f 766/2852/2490 765/2850/2488 773/2851/2489 774/2853/2491 f 767/2854/2492 766/2855/2490 774/2856/2491 775/2857/2493 f 768/2858/2494 767/2854/2492 775/2857/2493 776/2859/2495 f 769/2860/2496 768/2858/2494 776/2859/2495 777/2861/2497 f 762/2845/2483 769/2860/2496 777/2861/2497 770/2846/2484 f 759/2838/2477 765/2850/2488 766/2852/2490 760/2839/2478 f 712/2862/2498 778/2863/2499 758/2837/2476 713/2842/2480 f 758/2837/2476 778/2863/2499 779/2864/2500 759/2838/2477 f 759/2838/2477 779/2864/2500 764/2848/2486 765/2850/2488 f 760/2840/2478 766/2855/2490 767/2854/2492 780/2865/2501 f 760/2840/2478 780/2865/2501 781/2866/2502 761/2841/2479 f 886/2843/2481 761/2841/2479 781/2866/2502 731/2867/2503 f 770/2846/2484 782/2868/2504 783/2869/2505 771/2847/2485 f 771/2847/2485 783/2869/2505 784/2870/2506 772/2849/2487 f 772/2849/2487 784/2870/2506 785/2871/2507 773/2851/2489 f 773/2851/2489 785/2871/2507 786/2872/2508 774/2853/2491 f 774/2856/2491 786/2873/2508 787/2874/2509 775/2857/2493 f 775/2857/2493 787/2874/2509 788/2875/2510 776/2859/2495 f 776/2859/2495 788/2875/2510 789/2876/2511 777/2861/2497 f 777/2861/2497 789/2876/2511 782/2868/2504 770/2846/2484 f 763/2844/2482 764/2848/2486 779/2864/2500 790/2877/2512 f 767/2854/2492 768/2858/2494 791/2878/2513 780/2865/2501 f 780/2865/2501 791/2878/2513 792/2879/2514 781/2866/2502 f 731/2867/2503 781/2866/2502 792/2879/2514 732/2880/2515 f 711/2881/2516 793/2882/2517 778/2863/2499 712/2862/2498 f 778/2863/2499 793/2882/2517 790/2877/2512 779/2864/2500 f 762/2845/2483 794/2883/2518 795/2884/2519 769/2860/2496 f 762/2845/2483 763/2844/2482 790/2877/2512 794/2883/2518 f 768/2858/2494 769/2860/2496 795/2884/2519 791/2878/2513 f 710/2885/2520 796/2886/2521 793/2882/2517 711/2881/2516 f 790/2877/2512 793/2882/2517 796/2886/2521 794/2883/2518 f 791/2878/2513 795/2884/2519 797/2887/2522 792/2879/2514 f 732/2880/2515 792/2879/2514 797/2887/2522 733/2888/2523 f 2831/2889/2524 2832/2890/2525 804/2891/2526 803/2892/2527 f 2834/2893/2528 2833/2894/2529 805/2895/2530 806/2896/2531 f 2833/2894/2529 2831/2889/2524 803/2892/2527 805/2895/2530 f 2835/2897/2532 2859/2898/2533 808/2899/2534 807/2900/2535 f 2832/2890/2525 2835/2897/2532 807/2900/2535 804/2891/2526 f 803/2892/2527 804/2891/2526 809/2901/2536 810/2902/2537 f 806/2896/2531 805/2895/2530 812/2903/2538 811/2904/2539 f 805/2895/2530 803/2892/2527 810/2902/2537 812/2903/2538 f 807/2900/2535 808/2899/2534 814/2905/2540 813/2906/2541 f 804/2891/2526 807/2900/2535 813/2906/2541 809/2901/2536 f 795/2884/2519 794/2883/2518 816/2907/2542 815/2908/2543 f 815/2908/2543 816/2907/2542 798/2909/2544 799/2910/2545 f 796/2886/2521 710/2885/2520 818/2911/2546 817/2912/2547 f 817/2912/2547 818/2911/2546 801/2913/2548 800/2914/2549 f 794/2883/2518 796/2886/2521 817/2912/2547 816/2907/2542 f 816/2907/2542 817/2912/2547 800/2914/2549 798/2909/2544 f 733/2888/2523 797/2887/2522 819/2915/2550 971/2916/2551 f 971/2916/2551 819/2915/2550 802/2917/2552 956/2918/2553 f 797/2887/2522 795/2884/2519 815/2908/2543 819/2915/2550 f 819/2915/2550 815/2908/2543 799/2910/2545 802/2917/2552 f 782/2868/2504 820/2919/2554 821/2920/2555 783/2869/2505 f 783/2869/2505 821/2920/2555 822/2921/2556 784/2870/2506 f 784/2870/2506 822/2921/2556 823/2922/2557 785/2871/2507 f 785/2871/2507 823/2922/2557 824/2923/2558 786/2872/2508 f 786/2873/2508 824/2924/2558 825/2925/2559 787/2874/2509 f 787/2874/2509 825/2925/2559 826/2926/2560 788/2875/2510 f 788/2875/2510 826/2926/2560 827/2927/2561 789/2876/2511 f 789/2876/2511 827/2927/2561 820/2919/2554 782/2868/2504 f 820/2919/2554 829/2928/2562 828/2929/2563 821/2920/2555 f 821/2920/2555 828/2929/2563 830/2930/2564 822/2921/2556 f 822/2921/2556 830/2930/2564 831/2931/2565 823/2922/2557 f 823/2922/2557 831/2931/2565 832/2932/2566 824/2923/2558 f 824/2924/2558 832/2933/2566 833/2934/2567 825/2925/2559 f 825/2925/2559 833/2934/2567 834/2935/2568 826/2926/2560 f 826/2926/2560 834/2935/2568 835/2936/2569 827/2927/2561 f 827/2927/2561 835/2936/2569 829/2928/2562 820/2919/2554 f 829/2928/2562 836/2937/2570 837/2938/2571 828/2929/2563 f 828/2929/2563 837/2938/2571 838/2939/2572 830/2930/2564 f 830/2930/2564 838/2939/2572 839/2940/2573 831/2931/2565 f 831/2931/2565 839/2940/2573 840/2941/2574 832/2932/2566 f 832/2933/2566 840/2942/2574 841/2943/2575 833/2934/2567 f 833/2934/2567 841/2943/2575 842/2944/2576 834/2935/2568 f 834/2935/2568 842/2944/2576 843/2945/2577 835/2936/2569 f 835/2936/2569 843/2945/2577 836/2937/2570 829/2928/2562 f 836/2937/2570 2836/2946/2578 2837/2947/2579 837/2938/2571 f 837/2938/2571 2837/2947/2579 2838/2948/2580 838/2939/2572 f 838/2939/2572 2838/2948/2580 2839/2949/2581 839/2940/2573 f 839/2940/2573 2839/2949/2581 2840/2950/2582 840/2941/2574 f 840/2942/2574 2840/2951/2582 2841/2952/2583 841/2943/2575 f 841/2943/2575 2841/2952/2583 2842/2953/2584 842/2944/2576 f 842/2944/2576 2842/2953/2584 2843/2954/2585 843/2945/2577 f 843/2945/2577 2843/2954/2585 2836/2946/2578 836/2937/2570 f 844/2955/2586 845/2956/2587 852/2957/2588 853/2958/2589 f 845/2956/2587 846/2959/2590 854/2960/2591 852/2957/2588 f 846/2959/2590 847/2961/2592 855/2962/2593 854/2960/2591 f 847/2961/2592 848/2963/2594 856/2964/2595 855/2962/2593 f 848/2965/2594 849/2966/2596 857/2967/2597 856/2968/2595 f 849/2966/2596 850/2969/2598 858/2970/2599 857/2967/2597 f 850/2969/2598 851/2971/2600 859/2972/2601 858/2970/2599 f 851/2971/2600 844/2955/2586 853/2958/2589 859/2972/2601 f 853/2958/2589 852/2957/2588 861/2973/2602 860/2974/2603 f 852/2957/2588 854/2960/2591 862/2975/2604 861/2973/2602 f 854/2960/2591 855/2962/2593 863/2976/2605 862/2975/2604 f 855/2962/2593 856/2964/2595 864/2977/2606 863/2976/2605 f 856/2968/2595 857/2967/2597 865/2978/2607 864/2979/2606 f 857/2967/2597 858/2970/2599 866/2980/2608 865/2978/2607 f 858/2970/2599 859/2972/2601 867/2981/2609 866/2980/2608 f 859/2972/2601 853/2958/2589 860/2974/2603 867/2981/2609 f 880/2982/2610 888/2983/2611 887/2984/2612 879/2985/2613 f 881/2986/2614 889/2987/2615 888/2983/2611 880/2982/2610 f 882/2988/2616 890/2989/2617 889/2987/2615 881/2986/2614 f 877/2990/2618 878/2991/2619 879/2985/2613 887/2984/2612 f 882/2988/2616 883/2992/2620 884/2993/2621 890/2989/2617 f 888/2983/2611 892/2994/2622 891/2995/2623 887/2984/2612 f 889/2987/2615 893/2996/2624 892/2994/2622 888/2983/2611 f 890/2989/2617 894/2997/2625 893/2996/2624 889/2987/2615 f 876/2998/2626 877/2990/2618 887/2984/2612 891/2995/2623 f 726/2810/2451 894/2997/2625 890/2989/2617 884/2993/2621 f 891/2995/2623 892/2994/2622 896/2999/2627 895/3000/2628 f 892/2994/2622 893/2996/2624 897/3001/2629 896/2999/2627 f 893/2996/2624 894/2997/2625 898/3002/2630 897/3001/2629 f 875/3003/2631 876/2998/2626 891/2995/2623 895/3000/2628 f 726/2810/2451 727/2816/2457 898/3002/2630 894/2997/2625 f 895/3000/2628 896/2999/2627 2845/3004/2632 2844/3005/2633 f 896/2999/2627 897/3001/2629 2846/3006/2634 2845/3004/2632 f 897/3001/2629 898/3002/2630 2847/3007/2635 2846/3006/2634 f 727/2816/2457 2823/2821/2462 2847/3007/2635 898/3002/2630 f 2848/3008/2636 875/3003/2631 895/3000/2628 2844/3005/2633 f 2849/3009/2637 2850/3010/2638 908/3011/2639 907/3012/2640 f 2850/3010/2638 2851/3013/2641 909/3014/2642 908/3011/2639 f 2851/3015/2641 2852/3016/2643 910/3017/2644 909/3018/2642 f 872/3019/2645 2853/3020/2646 2849/3009/2637 907/3012/2640 f 2830/2835/2474 885/2836/2475 910/3017/2644 2852/3016/2643 f 907/3012/2640 908/3011/2639 912/3021/2647 911/3022/2648 f 908/3011/2639 909/3014/2642 913/3023/2649 912/3021/2647 f 909/3018/2642 910/3017/2644 914/3024/2650 913/3025/2649 f 871/3026/2651 872/3019/2645 907/3012/2640 911/3022/2648 f 885/2836/2475 886/2843/2481 914/3024/2650 910/3017/2644 f 916/3027/2652 924/3028/2653 923/3029/2654 915/3030/2655 f 917/3031/2656 925/3032/2657 924/3028/2653 916/3027/2652 f 918/3033/2658 926/3034/2659 925/3032/2657 917/3031/2656 f 919/3035/2660 927/3036/2661 926/3034/2659 918/3033/2658 f 920/3037/2662 928/3038/2663 927/3039/2661 919/3040/2660 f 921/3041/2664 929/3042/2665 928/3038/2663 920/3037/2662 f 922/3043/2666 930/3044/2667 929/3042/2665 921/3041/2664 f 915/3030/2655 923/3029/2654 930/3044/2667 922/3043/2666 f 912/3021/2647 913/3023/2649 919/3035/2660 918/3033/2658 f 870/3045/2668 871/3026/2651 911/3022/2648 931/3046/2669 f 911/3022/2648 912/3021/2647 932/3047/2670 931/3046/2669 f 912/3021/2647 918/3033/2658 917/3031/2656 932/3047/2670 f 913/3025/2649 933/3048/2671 920/3037/2662 919/3040/2660 f 913/3025/2649 914/3024/2650 934/3049/2672 933/3048/2671 f 886/2843/2481 731/2867/2503 934/3049/2672 914/3024/2650 f 923/3029/2654 924/3028/2653 936/3050/2673 935/3051/2674 f 924/3028/2653 925/3032/2657 937/3052/2675 936/3050/2673 f 925/3032/2657 926/3034/2659 938/3053/2676 937/3052/2675 f 926/3034/2659 927/3036/2661 939/3054/2677 938/3053/2676 f 927/3039/2661 928/3038/2663 940/3055/2678 939/3056/2677 f 928/3038/2663 929/3042/2665 941/3057/2679 940/3055/2678 f 929/3042/2665 930/3044/2667 942/3058/2680 941/3057/2679 f 930/3044/2667 923/3029/2654 935/3051/2674 942/3058/2680 f 916/3027/2652 943/3059/2681 932/3047/2670 917/3031/2656 f 920/3037/2662 933/3048/2671 944/3060/2682 921/3041/2664 f 933/3048/2671 934/3049/2672 945/3061/2683 944/3060/2682 f 731/2867/2503 732/2880/2515 945/3061/2683 934/3049/2672 f 869/3062/2684 870/3045/2668 931/3046/2669 946/3063/2685 f 931/3046/2669 932/3047/2670 943/3059/2681 946/3063/2685 f 915/3030/2655 922/3043/2666 948/3064/2686 947/3065/2687 f 915/3030/2655 947/3065/2687 943/3059/2681 916/3027/2652 f 921/3041/2664 944/3060/2682 948/3064/2686 922/3043/2666 f 868/3066/2688 869/3062/2684 946/3063/2685 949/3067/2689 f 943/3059/2681 947/3065/2687 949/3067/2689 946/3063/2685 f 944/3060/2682 945/3061/2683 950/3068/2690 948/3064/2686 f 732/2880/2515 733/2888/2523 950/3068/2690 945/3061/2683 f 2854/3069/2691 957/3070/2692 958/3071/2693 2855/3072/2694 f 2857/3073/2695 960/3074/2696 959/3075/2697 2856/3076/2698 f 2856/3076/2698 959/3075/2697 957/3070/2692 2854/3069/2691 f 2858/3077/2699 961/3078/2700 808/2899/2534 2859/2898/2533 f 2855/3072/2694 958/3071/2693 961/3078/2700 2858/3077/2699 f 957/3070/2692 963/3079/2701 962/3080/2702 958/3071/2693 f 960/3074/2696 964/3081/2703 965/3082/2704 959/3075/2697 f 959/3075/2697 965/3082/2704 963/3079/2701 957/3070/2692 f 961/3078/2700 966/3083/2705 814/2905/2540 808/2899/2534 f 958/3071/2693 962/3080/2702 966/3083/2705 961/3078/2700 f 948/3064/2686 967/3084/2706 968/3085/2707 947/3065/2687 f 967/3084/2706 952/3086/2708 951/3087/2709 968/3085/2707 f 970/3088/2710 868/3066/2688 949/3067/2689 969/3089/2711 f 969/3089/2711 953/3090/2712 954/3091/2713 970/3088/2710 f 947/3065/2687 968/3085/2707 969/3089/2711 949/3067/2689 f 968/3085/2707 951/3087/2709 953/3090/2712 969/3089/2711 f 733/2888/2523 971/2916/2551 972/3092/2714 950/3068/2690 f 971/2916/2551 956/2918/2553 955/3093/2715 972/3092/2714 f 950/3068/2690 972/3092/2714 967/3084/2706 948/3064/2686 f 972/3092/2714 955/3093/2715 952/3086/2708 967/3084/2706 f 935/3051/2674 936/3050/2673 974/3094/2716 973/3095/2717 f 936/3050/2673 937/3052/2675 975/3096/2718 974/3094/2716 f 937/3052/2675 938/3053/2676 976/3097/2719 975/3096/2718 f 938/3053/2676 939/3054/2677 977/3098/2720 976/3097/2719 f 939/3056/2677 940/3055/2678 978/3099/2721 977/3100/2720 f 940/3055/2678 941/3057/2679 979/3101/2722 978/3099/2721 f 941/3057/2679 942/3058/2680 980/3102/2723 979/3101/2722 f 942/3058/2680 935/3051/2674 973/3095/2717 980/3102/2723 f 973/3095/2717 974/3094/2716 981/3103/2724 982/3104/2725 f 974/3094/2716 975/3096/2718 983/3105/2726 981/3103/2724 f 975/3096/2718 976/3097/2719 984/3106/2727 983/3105/2726 f 976/3097/2719 977/3098/2720 985/3107/2728 984/3106/2727 f 977/3100/2720 978/3099/2721 986/3108/2729 985/3109/2728 f 978/3099/2721 979/3101/2722 987/3110/2730 986/3108/2729 f 979/3101/2722 980/3102/2723 988/3111/2731 987/3110/2730 f 980/3102/2723 973/3095/2717 982/3104/2725 988/3111/2731 f 982/3104/2725 981/3103/2724 990/3112/2732 989/3113/2733 f 981/3103/2724 983/3105/2726 991/3114/2734 990/3112/2732 f 983/3105/2726 984/3106/2727 992/3115/2735 991/3114/2734 f 984/3106/2727 985/3107/2728 993/3116/2736 992/3115/2735 f 985/3109/2728 986/3108/2729 994/3117/2737 993/3118/2736 f 986/3108/2729 987/3110/2730 995/3119/2738 994/3117/2737 f 987/3110/2730 988/3111/2731 996/3120/2739 995/3119/2738 f 988/3111/2731 982/3104/2725 989/3113/2733 996/3120/2739 f 989/3113/2733 990/3112/2732 2861/3121/2740 2860/3122/2741 f 990/3112/2732 991/3114/2734 2862/3123/2742 2861/3121/2740 f 991/3114/2734 992/3115/2735 2863/3124/2743 2862/3123/2742 f 992/3115/2735 993/3116/2736 2864/3125/2744 2863/3124/2743 f 993/3118/2736 994/3117/2737 2865/3126/2745 2864/3127/2744 f 994/3117/2737 995/3119/2738 2866/3128/2746 2865/3126/2745 f 995/3119/2738 996/3120/2739 2867/3129/2747 2866/3128/2746 f 996/3120/2739 989/3113/2733 2860/3122/2741 2867/3129/2747 f 997/3130/2748 1006/3131/2749 1005/3132/2750 998/3133/2751 f 998/3133/2751 1005/3132/2750 1007/3134/2752 999/3135/2753 f 999/3135/2753 1007/3134/2752 1008/3136/2754 1000/3137/2755 f 1000/3137/2755 1008/3136/2754 1009/3138/2756 1001/3139/2757 f 1001/3140/2757 1009/3141/2756 1010/3142/2758 1002/3143/2759 f 1002/3143/2759 1010/3142/2758 1011/3144/2760 1003/3145/2761 f 1003/3145/2761 1011/3144/2760 1012/3146/2762 1004/3147/2763 f 1004/3147/2763 1012/3146/2762 1006/3131/2749 997/3130/2748 f 1006/3131/2749 1013/3148/2764 1014/3149/2765 1005/3132/2750 f 1005/3132/2750 1014/3149/2765 1015/3150/2766 1007/3134/2752 f 1007/3134/2752 1015/3150/2766 1016/3151/2767 1008/3136/2754 f 1008/3136/2754 1016/3151/2767 1017/3152/2768 1009/3138/2756 f 1009/3141/2756 1017/3153/2768 1018/3154/2769 1010/3142/2758 f 1010/3142/2758 1018/3154/2769 1019/3155/2770 1011/3144/2760 f 1011/3144/2760 1019/3155/2770 1020/3156/2771 1012/3146/2762 f 1012/3146/2762 1020/3156/2771 1013/3148/2764 1006/3131/2749 ================================================ FILE: demo/simplify.html ================================================ meshoptimizer - demo
================================================ FILE: demo/tests.cpp ================================================ #include "../src/meshoptimizer.h" #include #include #include #include #include // This file uses assert() to verify algorithm correctness #undef NDEBUG #include struct PV { unsigned short px, py, pz; unsigned char nu, nv; // octahedron encoded normal, aliases .pw unsigned short tx, ty; }; // note: 4 6 5 triangle here is a combo-breaker: // we encode it without rotating, a=next, c=next - this means we do *not* bump next to 6 // which means that the next triangle can't be encoded via next sequencing! static const unsigned int kIndexBuffer[] = {0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9}; static const unsigned char kIndexDataV0[] = { 0xe0, 0xf0, 0x10, 0xfe, 0xff, 0xf0, 0x0c, 0xff, 0x02, 0x02, 0x02, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00, // clang-format :-/ }; // note: this exercises two features of v1 format, restarts (0 1 2) and last static const unsigned int kIndexBufferTricky[] = {0, 1, 2, 2, 1, 3, 0, 1, 2, 2, 1, 5, 2, 1, 4}; static const unsigned char kIndexDataV1[] = { 0xe1, 0xf0, 0x10, 0xfe, 0x1f, 0x3d, 0x00, 0x0a, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00, // clang-format :-/ }; static const unsigned int kIndexSequence[] = {0, 1, 51, 2, 49, 1000}; static const unsigned char kIndexSequenceV1[] = { 0xd1, 0x00, 0x04, 0xcd, 0x01, 0x04, 0x07, 0x98, 0x1f, 0x00, 0x00, 0x00, 0x00, // clang-format :-/ }; static const PV kVertexBuffer[] = { {0, 0, 0, 0, 0, 0, 0}, {300, 0, 0, 0, 0, 500, 0}, {0, 300, 0, 0, 0, 0, 500}, {300, 300, 0, 0, 0, 500, 500}, }; static const unsigned char kVertexDataV0[] = { 0xa0, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x58, 0x57, 0x58, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x00, 0x58, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x17, 0x18, 0x17, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x00, 0x17, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // clang-format :-/ }; static const unsigned char kVertexDataV1[] = { 0xa1, 0xee, 0xaa, 0xee, 0x00, 0x4b, 0x4b, 0x4b, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x7d, 0x7d, 0x7d, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x62, // clang-format :-/ }; // This binary blob is a valid v1 encoding of vertex buffer but it used a custom version of // the encoder that exercised all features of the format; because of this it is much larger // and will never be produced by the encoder itself. static const unsigned char kVertexDataV1Custom[] = { 0xa1, 0xd4, 0x94, 0xd4, 0x01, 0x0e, 0x00, 0x58, 0x57, 0x58, 0x02, 0x02, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x7d, 0x7d, 0x7d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x62, // clang-format :-/ }; static void decodeIndexV0() { const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); std::vector buffer(kIndexDataV0, kIndexDataV0 + sizeof(kIndexDataV0)); unsigned int decoded[index_count]; assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, kIndexBuffer, sizeof(kIndexBuffer)) == 0); } static void decodeIndexV1() { const size_t index_count = sizeof(kIndexBufferTricky) / sizeof(kIndexBufferTricky[0]); std::vector buffer(kIndexDataV1, kIndexDataV1 + sizeof(kIndexDataV1)); unsigned int decoded[index_count]; assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, kIndexBufferTricky, sizeof(kIndexBufferTricky)) == 0); } static void decodeIndexV1More() { const unsigned char input[] = { 0xe1, 0xf0, 0x10, 0xfe, 0xff, 0xf0, 0x0c, 0xff, 0x02, 0x02, 0x02, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00, // clang-format }; const unsigned int ib[] = {0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9}; const size_t index_count = sizeof(ib) / sizeof(ib[0]); std::vector buffer(input, input + sizeof(input)); unsigned int decoded[index_count]; assert(meshopt_decodeIndexBuffer(decoded, index_count, 4, &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, ib, sizeof(ib)) == 0); } static void decodeIndexV1ThreeEdges() { const unsigned char input[] = { 0xe1, 0xf0, 0x20, 0x30, 0x40, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00, // clang-format }; const unsigned int ib[] = {0, 1, 2, 1, 0, 3, 2, 1, 4, 0, 2, 5}; const size_t index_count = sizeof(ib) / sizeof(ib[0]); std::vector buffer(input, input + sizeof(input)); unsigned int decoded[index_count]; assert(meshopt_decodeIndexBuffer(decoded, index_count, 4, &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, ib, sizeof(ib)) == 0); } static void decodeIndex16() { const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); const size_t vertex_count = 10; std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); unsigned short decoded[index_count]; assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0); for (size_t i = 0; i < index_count; ++i) assert(decoded[i] == kIndexBuffer[i]); } static void encodeIndexMemorySafe() { const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); const size_t vertex_count = 10; std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); // check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access for (size_t i = 0; i <= buffer.size(); ++i) { std::vector shortbuffer(i); size_t result = meshopt_encodeIndexBuffer(i == 0 ? NULL : &shortbuffer[0], i, kIndexBuffer, index_count); if (i == buffer.size()) assert(result == buffer.size()); else assert(result == 0); } } static void decodeIndexMemorySafe() { const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); const size_t vertex_count = 10; std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); // check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access unsigned int decoded[index_count]; for (size_t i = 0; i <= buffer.size(); ++i) { std::vector shortbuffer(buffer.begin(), buffer.begin() + i); int result = meshopt_decodeIndexBuffer(decoded, index_count, i == 0 ? NULL : &shortbuffer[0], i); if (i == buffer.size()) assert(result == 0); else assert(result < 0); } } static void decodeIndexRejectExtraBytes() { const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); const size_t vertex_count = 10; std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); // check that decoder doesn't accept extra bytes after a valid stream std::vector largebuffer(buffer); largebuffer.push_back(0); unsigned int decoded[index_count]; assert(meshopt_decodeIndexBuffer(decoded, index_count, &largebuffer[0], largebuffer.size()) < 0); } static void decodeIndexRejectMalformedHeaders() { const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); const size_t vertex_count = 10; std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); // check that decoder doesn't accept malformed headers std::vector brokenbuffer(buffer); brokenbuffer[0] = 0; unsigned int decoded[index_count]; assert(meshopt_decodeIndexBuffer(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0); } static void decodeIndexRejectInvalidVersion() { const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); const size_t vertex_count = 10; std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); // check that decoder doesn't accept invalid version std::vector brokenbuffer(buffer); brokenbuffer[0] |= 0x0f; unsigned int decoded[index_count]; assert(meshopt_decodeIndexBuffer(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0); } static void decodeIndexMalformedVByte() { const unsigned char input[] = { 0xe1, 0x20, 0x20, 0x20, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xff, 0xff, 0xff, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // clang-format :-/ }; unsigned int decoded[66]; assert(meshopt_decodeIndexBuffer(decoded, 66, input, sizeof(input)) < 0); } static void roundtripIndexTricky() { const size_t index_count = sizeof(kIndexBufferTricky) / sizeof(kIndexBufferTricky[0]); const size_t vertex_count = 6; std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBufferTricky, index_count)); unsigned int decoded[index_count]; assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, kIndexBufferTricky, sizeof(kIndexBufferTricky)) == 0); } static void encodeIndexEmpty() { std::vector buffer(meshopt_encodeIndexBufferBound(0, 0)); buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), NULL, 0)); assert(meshopt_decodeIndexBuffer(static_cast(NULL), 0, &buffer[0], buffer.size()) == 0); } static void decodeIndexSequence() { const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); std::vector buffer(kIndexSequenceV1, kIndexSequenceV1 + sizeof(kIndexSequenceV1)); unsigned int decoded[index_count]; assert(meshopt_decodeIndexSequence(decoded, index_count, &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, kIndexSequence, sizeof(kIndexSequence)) == 0); } static void decodeIndexSequence16() { const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); const size_t vertex_count = 1001; std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); unsigned short decoded[index_count]; assert(meshopt_decodeIndexSequence(decoded, index_count, &buffer[0], buffer.size()) == 0); for (size_t i = 0; i < index_count; ++i) assert(decoded[i] == kIndexSequence[i]); } static void encodeIndexSequenceMemorySafe() { const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); const size_t vertex_count = 1001; std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); // check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access for (size_t i = 0; i <= buffer.size(); ++i) { std::vector shortbuffer(i); size_t result = meshopt_encodeIndexSequence(i == 0 ? NULL : &shortbuffer[0], i, kIndexSequence, index_count); if (i == buffer.size()) assert(result == buffer.size()); else assert(result == 0); } } static void decodeIndexSequenceMemorySafe() { const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); const size_t vertex_count = 1001; std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); // check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access unsigned int decoded[index_count]; for (size_t i = 0; i <= buffer.size(); ++i) { std::vector shortbuffer(buffer.begin(), buffer.begin() + i); int result = meshopt_decodeIndexSequence(decoded, index_count, i == 0 ? NULL : &shortbuffer[0], i); if (i == buffer.size()) assert(result == 0); else assert(result < 0); } } static void decodeIndexSequenceRejectExtraBytes() { const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); const size_t vertex_count = 1001; std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); // check that decoder doesn't accept extra bytes after a valid stream std::vector largebuffer(buffer); largebuffer.push_back(0); unsigned int decoded[index_count]; assert(meshopt_decodeIndexSequence(decoded, index_count, &largebuffer[0], largebuffer.size()) < 0); } static void decodeIndexSequenceRejectMalformedHeaders() { const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); const size_t vertex_count = 1001; std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); // check that decoder doesn't accept malformed headers std::vector brokenbuffer(buffer); brokenbuffer[0] = 0; unsigned int decoded[index_count]; assert(meshopt_decodeIndexSequence(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0); } static void decodeIndexSequenceRejectInvalidVersion() { const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); const size_t vertex_count = 1001; std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); // check that decoder doesn't accept invalid version std::vector brokenbuffer(buffer); brokenbuffer[0] |= 0x0f; unsigned int decoded[index_count]; assert(meshopt_decodeIndexSequence(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0); } static void encodeIndexSequenceEmpty() { std::vector buffer(meshopt_encodeIndexSequenceBound(0, 0)); buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), NULL, 0)); assert(meshopt_decodeIndexSequence(static_cast(NULL), 0, &buffer[0], buffer.size()) == 0); } static void decodeVertexV0() { const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); std::vector buffer(kVertexDataV0, kVertexDataV0 + sizeof(kVertexDataV0)); PV decoded[vertex_count]; assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, kVertexBuffer, sizeof(kVertexBuffer)) == 0); } static void decodeVertexV0More() { const unsigned char expected[] = { 0, 0, 0, 0, 0, 1, 2, 8, 0, 2, 4, 16, 0, 3, 6, 24, 0, 4, 8, 32, 0, 5, 10, 40, 0, 6, 12, 48, 0, 7, 14, 56, 0, 8, 16, 64, 0, 9, 18, 72, 0, 10, 20, 80, 0, 11, 22, 88, 0, 12, 24, 96, 0, 13, 26, 104, 0, 14, 28, 112, 0, 15, 30, 120, // clang-format :-/ }; const unsigned char input[] = { 0xa0, 0x00, 0x01, 0x2a, 0xaa, 0xaa, 0xaa, 0x02, 0x04, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x03, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // clang-format :-/ }; unsigned char decoded[sizeof(expected)]; assert(meshopt_decodeVertexBuffer(decoded, 16, 4, input, sizeof(input)) == 0); assert(memcmp(decoded, expected, sizeof(expected)) == 0); } static void decodeVertexV0Mode2() { const unsigned char expected[] = { 0, 0, 0, 0, 4, 5, 6, 7, 8, 10, 12, 14, 12, 15, 18, 21, 16, 20, 24, 28, 20, 25, 30, 35, 24, 30, 36, 42, 28, 35, 42, 49, 32, 40, 48, 56, 36, 45, 54, 63, 40, 50, 60, 70, 44, 55, 66, 77, 48, 60, 72, 84, 52, 65, 78, 91, 56, 70, 84, 98, 60, 75, 90, 105, // clang-format :-/ }; const unsigned char input[] = { 0xa0, 0x02, 0x08, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x02, 0x0a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x02, 0x0c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x02, 0x0e, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // clang-format :-/ }; unsigned char decoded[sizeof(expected)]; assert(meshopt_decodeVertexBuffer(decoded, 16, 4, input, sizeof(input)) == 0); assert(memcmp(decoded, expected, sizeof(expected)) == 0); } static void decodeVertexV1() { const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); std::vector buffer(kVertexDataV1, kVertexDataV1 + sizeof(kVertexDataV1)); PV decoded[vertex_count]; assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, kVertexBuffer, sizeof(kVertexBuffer)) == 0); } static void decodeVertexV1Custom() { const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); std::vector buffer(kVertexDataV1Custom, kVertexDataV1Custom + sizeof(kVertexDataV1Custom)); PV decoded[vertex_count]; assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, kVertexBuffer, sizeof(kVertexBuffer)) == 0); } static void decodeVertexV1Deltas() { const unsigned short expected[] = { 248, 248, 240, 240, 249, 250, 243, 244, 250, 252, 246, 248, 251, 254, 249, 252, 252, 256, 252, 256, 253, 258, 255, 260, 254, 260, 258, 264, 255, 262, 261, 268, 256, 264, 264, 272, 257, 262, 267, 268, 258, 260, 270, 264, 259, 258, 273, 260, 260, 256, 276, 256, 261, 254, 279, 252, 262, 252, 282, 248, 263, 250, 285, 244, // clang-format :-/ }; const unsigned char input[] = { 0xa1, 0x99, 0x99, 0x01, 0x2a, 0xaa, 0xaa, 0xaa, 0x02, 0x04, 0x44, 0x44, 0x44, 0x43, 0x33, 0x33, 0x33, 0x02, 0x06, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x02, 0x08, 0x88, 0x88, 0x88, 0x87, 0x77, 0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x01, 0x01, // clang-format :-/ }; unsigned short decoded[sizeof(expected) / sizeof(expected[0])]; assert(meshopt_decodeVertexBuffer(decoded, 16, 8, input, sizeof(input)) == 0); assert(memcmp(decoded, expected, sizeof(expected)) == 0); } static void encodeVertexMemorySafe() { const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); std::vector buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV))); buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV))); // check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access for (size_t i = 0; i <= buffer.size(); ++i) { std::vector shortbuffer(i); size_t result = meshopt_encodeVertexBuffer(i == 0 ? NULL : &shortbuffer[0], i, kVertexBuffer, vertex_count, sizeof(PV)); if (i == buffer.size()) assert(result == buffer.size()); else assert(result == 0); } } static void decodeVertexMemorySafe() { const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); std::vector buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV))); buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV))); // check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access PV decoded[vertex_count]; for (size_t i = 0; i <= buffer.size(); ++i) { std::vector shortbuffer(buffer.begin(), buffer.begin() + i); int result = meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), i == 0 ? NULL : &shortbuffer[0], i); (void)result; if (i == buffer.size()) assert(result == 0); else assert(result < 0); } } static void decodeVertexRejectExtraBytes() { const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); std::vector buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV))); buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV))); // check that decoder doesn't accept extra bytes after a valid stream std::vector largebuffer(buffer); largebuffer.push_back(0); PV decoded[vertex_count]; assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &largebuffer[0], largebuffer.size()) < 0); } static void decodeVertexRejectMalformedHeaders() { const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); std::vector buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV))); buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV))); // check that decoder doesn't accept malformed headers std::vector brokenbuffer(buffer); brokenbuffer[0] = 0; PV decoded[vertex_count]; assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &brokenbuffer[0], brokenbuffer.size()) < 0); } static void decodeVertexBitGroups() { unsigned char data[16 * 4]; // this tests 0/2/4/8 bit groups in one stream for (size_t i = 0; i < 16; ++i) { data[i * 4 + 0] = 0; data[i * 4 + 1] = (unsigned char)(i * 1); data[i * 4 + 2] = (unsigned char)(i * 2); data[i * 4 + 3] = (unsigned char)(i * 8); } std::vector buffer(meshopt_encodeVertexBufferBound(16, 4)); buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 16, 4)); unsigned char decoded[16 * 4]; assert(meshopt_decodeVertexBuffer(decoded, 16, 4, &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, data, sizeof(data)) == 0); } static void decodeVertexBitGroupSentinels() { unsigned char data[16 * 4]; // this tests 0/2/4/8 bit groups and sentinels in one stream for (size_t i = 0; i < 16; ++i) { if (i == 7 || i == 13) { data[i * 4 + 0] = 42; data[i * 4 + 1] = 42; data[i * 4 + 2] = 42; data[i * 4 + 3] = 42; } else { data[i * 4 + 0] = 0; data[i * 4 + 1] = (unsigned char)(i * 1); data[i * 4 + 2] = (unsigned char)(i * 2); data[i * 4 + 3] = (unsigned char)(i * 8); } } std::vector buffer(meshopt_encodeVertexBufferBound(16, 4)); buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 16, 4)); unsigned char decoded[16 * 4]; assert(meshopt_decodeVertexBuffer(decoded, 16, 4, &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, data, sizeof(data)) == 0); } static void decodeVertexDeltas() { unsigned short data[16 * 4]; // this forces wider deltas by using values that cross byte boundary for (size_t i = 0; i < 16; ++i) { data[i * 4 + 0] = (unsigned short)(0xf8 + i * 1); data[i * 4 + 1] = (unsigned short)(0xf8 + (i < 8 ? i : 16 - i) * 2); data[i * 4 + 2] = (unsigned short)(0xf0 + i * 3); data[i * 4 + 3] = (unsigned short)(0xf0 + (i < 8 ? i : 16 - i) * 4); } std::vector buffer(meshopt_encodeVertexBufferBound(16, 8)); buffer.resize(meshopt_encodeVertexBufferLevel(&buffer[0], buffer.size(), data, 16, 8, 2, -1)); unsigned short decoded[16 * 4]; assert(meshopt_decodeVertexBuffer(decoded, 16, 8, &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, data, sizeof(data)) == 0); } static void decodeVertexBitXor() { unsigned int data[16 * 4]; // this forces xors by using bit values at an offset for (size_t i = 0; i < 16; ++i) { data[i * 4 + 0] = unsigned(i << 0); data[i * 4 + 1] = unsigned(i << 2); data[i * 4 + 2] = unsigned(i << 15); data[i * 4 + 3] = unsigned(i << 28); } std::vector buffer(meshopt_encodeVertexBufferBound(16, 16)); buffer.resize(meshopt_encodeVertexBufferLevel(&buffer[0], buffer.size(), data, 16, 16, 3, -1)); unsigned int decoded[16 * 4]; assert(meshopt_decodeVertexBuffer(decoded, 16, 16, &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, data, sizeof(data)) == 0); } static void decodeVertexLarge() { unsigned char data[128 * 4]; // this tests 0/2/4/8 bit groups in one stream for (size_t i = 0; i < 128; ++i) { data[i * 4 + 0] = 0; data[i * 4 + 1] = (unsigned char)(i * 1); data[i * 4 + 2] = (unsigned char)(i * 2); data[i * 4 + 3] = (unsigned char)(i * 8); } std::vector buffer(meshopt_encodeVertexBufferBound(128, 4)); buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 128, 4)); unsigned char decoded[128 * 4]; assert(meshopt_decodeVertexBuffer(decoded, 128, 4, &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, data, sizeof(data)) == 0); } static void decodeVertexSmall() { unsigned char data[13 * 4]; // this tests 0/2/4/8 bit groups in one stream for (size_t i = 0; i < 13; ++i) { data[i * 4 + 0] = 0; data[i * 4 + 1] = (unsigned char)(i * 1); data[i * 4 + 2] = (unsigned char)(i * 2); data[i * 4 + 3] = (unsigned char)(i * 8); } std::vector buffer(meshopt_encodeVertexBufferBound(13, 4)); buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 13, 4)); unsigned char decoded[13 * 4]; assert(meshopt_decodeVertexBuffer(decoded, 13, 4, &buffer[0], buffer.size()) == 0); assert(memcmp(decoded, data, sizeof(data)) == 0); } static void encodeVertexEmpty() { std::vector buffer(meshopt_encodeVertexBufferBound(0, 16)); buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), NULL, 0, 16)); assert(meshopt_decodeVertexBuffer(NULL, 0, 16, &buffer[0], buffer.size()) == 0); } static void decodeVersion() { assert(meshopt_decodeVertexVersion(reinterpret_cast("\xa0"), 1) == 0); assert(meshopt_decodeVertexVersion(reinterpret_cast("\xa1"), 1) == 1); assert(meshopt_decodeVertexVersion(reinterpret_cast("\xa1hello"), 6) == 1); assert(meshopt_decodeVertexVersion(NULL, 0) == -1); assert(meshopt_decodeVertexVersion(reinterpret_cast("\xa7"), 1) == -1); assert(meshopt_decodeVertexVersion(reinterpret_cast("\xb1"), 1) == -1); assert(meshopt_decodeIndexVersion(reinterpret_cast("\xe0"), 1) == 0); assert(meshopt_decodeIndexVersion(reinterpret_cast("\xd1"), 1) == 1); assert(meshopt_decodeIndexVersion(reinterpret_cast("\xe1hello"), 6) == 1); assert(meshopt_decodeIndexVersion(NULL, 0) == -1); assert(meshopt_decodeIndexVersion(reinterpret_cast("\xa7"), 1) == -1); assert(meshopt_decodeIndexVersion(reinterpret_cast("\xa1"), 1) == -1); } static void decodeFilterOct8() { const unsigned char data[4 * 4] = { 0, 1, 127, 0, 0, 187, 127, 1, 255, 1, 127, 0, 14, 130, 127, 1, // clang-format :-/ }; const unsigned char expected[4 * 4] = { 0, 1, 127, 0, 0, 159, 82, 1, 255, 1, 127, 0, 1, 130, 241, 1, // clang-format :-/ }; // Aligned by 4 unsigned char full[4 * 4]; memcpy(full, data, sizeof(full)); meshopt_decodeFilterOct(full, 4, 4); assert(memcmp(full, expected, sizeof(full)) == 0); // Tail processing for unaligned data unsigned char tail[3 * 4]; memcpy(tail, data, sizeof(tail)); meshopt_decodeFilterOct(tail, 3, 4); assert(memcmp(tail, expected, sizeof(tail)) == 0); } static void decodeFilterOct12() { const unsigned short data[4 * 4] = { 0, 1, 2047, 0, 0, 1870, 2047, 1, 2017, 1, 2047, 0, 14, 1300, 2047, 1, // clang-format :-/ }; const unsigned short expected[4 * 4] = { 0, 16, 32767, 0, 0, 32621, 3088, 1, 32764, 16, 471, 0, 307, 28541, 16093, 1, // clang-format :-/ }; // Aligned by 4 unsigned short full[4 * 4]; memcpy(full, data, sizeof(full)); meshopt_decodeFilterOct(full, 4, 8); assert(memcmp(full, expected, sizeof(full)) == 0); // Tail processing for unaligned data unsigned short tail[3 * 4]; memcpy(tail, data, sizeof(tail)); meshopt_decodeFilterOct(tail, 3, 8); assert(memcmp(tail, expected, sizeof(tail)) == 0); } static void decodeFilterQuat12() { const unsigned short data[4 * 4] = { 0, 1, 0, 0x7fc, 0, 1870, 0, 0x7fd, 2017, 1, 0, 0x7fe, 14, 1300, 0, 0x7ff, // clang-format :-/ }; const unsigned short expected[4 * 4] = { 32767, 0, 11, 0, 0, 25013, 0, 21166, 11, 0, 23504, 22830, 158, 14715, 0, 29277, // clang-format :-/ }; // Aligned by 4 unsigned short full[4 * 4]; memcpy(full, data, sizeof(full)); meshopt_decodeFilterQuat(full, 4, 8); assert(memcmp(full, expected, sizeof(full)) == 0); // Tail processing for unaligned data unsigned short tail[3 * 4]; memcpy(tail, data, sizeof(tail)); meshopt_decodeFilterQuat(tail, 3, 8); assert(memcmp(tail, expected, sizeof(tail)) == 0); } static void decodeFilterExp() { const unsigned int data[4] = { 0, 0xff000003, 0x02fffff7, 0xfe7fffff, // clang-format :-/ }; const unsigned int expected[4] = { 0, 0x3fc00000, 0xc2100000, 0x49fffffe, // clang-format :-/ }; // Aligned by 4 unsigned int full[4]; memcpy(full, data, sizeof(full)); meshopt_decodeFilterExp(full, 4, 4); assert(memcmp(full, expected, sizeof(full)) == 0); // Tail processing for unaligned data unsigned int tail[3]; memcpy(tail, data, sizeof(tail)); meshopt_decodeFilterExp(tail, 3, 4); assert(memcmp(tail, expected, sizeof(tail)) == 0); } static void encodeFilterOct8() { const float data[4 * 4] = { 1, 0, 0, 0, 0, -1, 0, 0, 0.7071068f, 0, 0.707168f, 1, -0.7071068f, 0, -0.707168f, 1, // clang-format :-/ }; const unsigned char expected[4 * 4] = { 0x7f, 0, 0x7f, 0, 0, 0x81, 0x7f, 0, 0x3f, 0, 0x7f, 0x7f, 0x81, 0x40, 0x7f, 0x7f, // clang-format :-/ }; unsigned char encoded[4 * 4]; meshopt_encodeFilterOct(encoded, 4, 4, 8, data); assert(memcmp(encoded, expected, sizeof(expected)) == 0); signed char decoded[4 * 4]; memcpy(decoded, encoded, sizeof(decoded)); meshopt_decodeFilterOct(decoded, 4, 4); for (size_t i = 0; i < 4 * 4; ++i) assert(fabsf(decoded[i] / 127.f - data[i]) < 1e-2f); } static void encodeFilterOct12() { const float data[4 * 4] = { 1, 0, 0, 0, 0, -1, 0, 0, 0.7071068f, 0, 0.707168f, 1, -0.7071068f, 0, -0.707168f, 1, // clang-format :-/ }; const unsigned short expected[4 * 4] = { 0x7ff, 0, 0x7ff, 0, 0x0, 0xf801, 0x7ff, 0, 0x3ff, 0, 0x7ff, 0x7fff, 0xf801, 0x400, 0x7ff, 0x7fff, // clang-format :-/ }; unsigned short encoded[4 * 4]; meshopt_encodeFilterOct(encoded, 4, 8, 12, data); assert(memcmp(encoded, expected, sizeof(expected)) == 0); short decoded[4 * 4]; memcpy(decoded, encoded, sizeof(decoded)); meshopt_decodeFilterOct(decoded, 4, 8); for (size_t i = 0; i < 4 * 4; ++i) assert(fabsf(decoded[i] / 32767.f - data[i]) < 1e-3f); } static void encodeFilterQuat12() { const float data[4 * 4] = { 1, 0, 0, 0, 0, -1, 0, 0, 0.7071068f, 0, 0, 0.707168f, -0.7071068f, 0, 0, -0.707168f, // clang-format :-/ }; const unsigned short expected[4 * 4] = { 0, 0, 0, 0x7fc, 0, 0, 0, 0x7fd, 0x7ff, 0, 0, 0x7ff, 0x7ff, 0, 0, 0x7ff, // clang-format :-/ }; unsigned short encoded[4 * 4]; meshopt_encodeFilterQuat(encoded, 4, 8, 12, data); assert(memcmp(encoded, expected, sizeof(expected)) == 0); short decoded[4 * 4]; memcpy(decoded, encoded, sizeof(decoded)); meshopt_decodeFilterQuat(decoded, 4, 8); for (size_t i = 0; i < 4; ++i) { float dx = decoded[i * 4 + 0] / 32767.f; float dy = decoded[i * 4 + 1] / 32767.f; float dz = decoded[i * 4 + 2] / 32767.f; float dw = decoded[i * 4 + 3] / 32767.f; float dp = data[i * 4 + 0] * dx + data[i * 4 + 1] * dy + data[i * 4 + 2] * dz + data[i * 4 + 3] * dw; assert(fabsf(fabsf(dp) - 1.f) < 1e-4f); } } static void encodeFilterExp() { const float data[4] = { 1, -23.4f, -0.1f, 11.0f, }; // separate exponents: each component gets its own value const unsigned int expected1[4] = { 0xf3002000, 0xf7ffd133, 0xefffcccd, 0xf6002c00, }; // shared exponents (vector): all components of each vector get the same value const unsigned int expected2[4] = { 0xf7000200, 0xf7ffd133, 0xf6ffff9a, 0xf6002c00, }; // shared exponents (component): each component gets the same value across all vectors const unsigned int expected3[4] = { 0xf3002000, 0xf7ffd133, 0xf3fffccd, 0xf7001600, }; unsigned int encoded1[4]; meshopt_encodeFilterExp(encoded1, 2, 8, 15, data, meshopt_EncodeExpSeparate); unsigned int encoded2[4]; meshopt_encodeFilterExp(encoded2, 2, 8, 15, data, meshopt_EncodeExpSharedVector); unsigned int encoded3[4]; meshopt_encodeFilterExp(encoded3, 2, 8, 15, data, meshopt_EncodeExpSharedComponent); assert(memcmp(encoded1, expected1, sizeof(expected1)) == 0); assert(memcmp(encoded2, expected2, sizeof(expected2)) == 0); assert(memcmp(encoded3, expected3, sizeof(expected3)) == 0); float decoded1[4]; memcpy(decoded1, encoded1, sizeof(decoded1)); meshopt_decodeFilterExp(decoded1, 2, 8); float decoded2[4]; memcpy(decoded2, encoded2, sizeof(decoded2)); meshopt_decodeFilterExp(decoded2, 2, 8); float decoded3[4]; memcpy(decoded3, encoded3, sizeof(decoded3)); meshopt_decodeFilterExp(decoded3, 2, 8); for (size_t i = 0; i < 4; ++i) { assert(fabsf(decoded1[i] - data[i]) < 1e-3f); assert(fabsf(decoded2[i] - data[i]) < 1e-3f); assert(fabsf(decoded3[i] - data[i]) < 1e-3f); } } static void encodeFilterExpZero() { const float data[4] = { 0.f, -0.f, 1.1754944e-38f, -1.1754944e-38f, }; const unsigned int expected[4] = { 0xf2000000, 0xf2000000, 0x8e000000, 0x8e000000, }; unsigned int encoded[4]; meshopt_encodeFilterExp(encoded, 4, 4, 15, data, meshopt_EncodeExpSeparate); assert(memcmp(encoded, expected, sizeof(expected)) == 0); float decoded[4]; memcpy(decoded, encoded, sizeof(decoded)); meshopt_decodeFilterExp(&decoded, 4, 4); for (size_t i = 0; i < 4; ++i) assert(decoded[i] == 0); } static void encodeFilterExpAlias() { const float data[4] = { 1, -23.4f, -0.1f, 11.0f, }; // separate exponents: each component gets its own value const unsigned int expected1[4] = { 0xf3002000, 0xf7ffd133, 0xefffcccd, 0xf6002c00, }; // shared exponents (vector): all components of each vector get the same value const unsigned int expected2[4] = { 0xf7000200, 0xf7ffd133, 0xf6ffff9a, 0xf6002c00, }; // shared exponents (component): each component gets the same value across all vectors const unsigned int expected3[4] = { 0xf3002000, 0xf7ffd133, 0xf3fffccd, 0xf7001600, }; unsigned int encoded1[4]; memcpy(encoded1, data, sizeof(data)); meshopt_encodeFilterExp(encoded1, 2, 8, 15, reinterpret_cast(encoded1), meshopt_EncodeExpSeparate); unsigned int encoded2[4]; memcpy(encoded2, data, sizeof(data)); meshopt_encodeFilterExp(encoded2, 2, 8, 15, reinterpret_cast(encoded2), meshopt_EncodeExpSharedVector); unsigned int encoded3[4]; memcpy(encoded3, data, sizeof(data)); meshopt_encodeFilterExp(encoded3, 2, 8, 15, reinterpret_cast(encoded3), meshopt_EncodeExpSharedComponent); assert(memcmp(encoded1, expected1, sizeof(expected1)) == 0); assert(memcmp(encoded2, expected2, sizeof(expected2)) == 0); assert(memcmp(encoded3, expected3, sizeof(expected3)) == 0); } static void encodeFilterExpClamp() { const float data[4] = { 1, -23.4f, -0.1f, 11.0f, }; // separate exponents: each component gets its own value // note: third value is exponent clamped const unsigned int expected[4] = { 0xf3002000, 0xf7ffd133, 0xf2fff99a, 0xf6002c00, }; unsigned int encoded[4]; meshopt_encodeFilterExp(encoded, 2, 8, 15, data, meshopt_EncodeExpClamped); assert(memcmp(encoded, expected, sizeof(expected)) == 0); float decoded[4]; memcpy(decoded, encoded, sizeof(decoded)); meshopt_decodeFilterExp(decoded, 2, 8); for (size_t i = 0; i < 4; ++i) assert(fabsf(decoded[i] - data[i]) < 1e-3f); } static void encodeFilterColor8() { const float data[4 * 4] = { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.25f, 0.4f, 0.4f, 0.4f, 0.75f, // clang-format :-/ }; const unsigned char expected[4 * 4] = { 0x40, 0x7f, 0xc1, 0xff, 0x7f, 0x00, 0x7f, 0xc0, 0x40, 0x81, 0xc0, 0xa0, 0x66, 0x00, 0x00, 0xdf, // clang-format :-/ }; unsigned char encoded[4 * 4]; meshopt_encodeFilterColor(encoded, 4, 4, 8, data); assert(memcmp(encoded, expected, sizeof(expected)) == 0); unsigned char decoded[4 * 4]; memcpy(decoded, encoded, sizeof(decoded)); meshopt_decodeFilterColor(decoded, 4, 4); for (size_t i = 0; i < 4 * 4; ++i) assert(fabsf(decoded[i] / 255.f - data[i]) < 1e-2f); // ensure grayscale is preserved assert(decoded[12] == decoded[13] && decoded[12] == decoded[14]); } static void encodeFilterColor12() { const float data[4 * 4] = { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.25f, 0.4f, 0.4f, 0.4f, 0.75f, // clang-format :-/ }; const unsigned short expected[4 * 4] = { 0x0400, 0x07ff, 0xfc01, 0x0fff, 0x07ff, 0x0000, 0x07ff, 0x0c00, 0x0400, 0xf801, 0xfc00, 0x0a00, 0x0666, 0x0000, 0x0000, 0x0dff, // clang-format :-/ }; unsigned short encoded[4 * 4]; meshopt_encodeFilterColor(encoded, 4, 8, 12, data); assert(memcmp(encoded, expected, sizeof(expected)) == 0); unsigned short decoded[4 * 4]; memcpy(decoded, encoded, sizeof(decoded)); meshopt_decodeFilterColor(decoded, 4, 8); for (size_t i = 0; i < 4 * 4; ++i) assert(fabsf(decoded[i] / 65535.f - data[i]) < 1e-3f); // ensure grayscale is preserved assert(decoded[12] == decoded[13] && decoded[12] == decoded[14]); } static void clusterBoundsDegenerate() { const float vbd[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; const unsigned int ibd[] = {0, 0, 0}; const unsigned int ib1[] = {0, 1, 2}; // all of the bounds below are degenerate as they use 0 triangles, one topology-degenerate triangle and one position-degenerate triangle respectively meshopt_Bounds bounds0 = meshopt_computeClusterBounds(NULL, 0, NULL, 0, 12); meshopt_Bounds boundsd = meshopt_computeClusterBounds(ibd, 3, vbd, 3, 12); meshopt_Bounds bounds1 = meshopt_computeClusterBounds(ib1, 3, vbd, 3, 12); assert(bounds0.center[0] == 0 && bounds0.center[1] == 0 && bounds0.center[2] == 0 && bounds0.radius == 0); assert(boundsd.center[0] == 0 && boundsd.center[1] == 0 && boundsd.center[2] == 0 && boundsd.radius == 0); assert(bounds1.center[0] == 0 && bounds1.center[1] == 0 && bounds1.center[2] == 0 && bounds1.radius == 0); const float vb1[] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; const unsigned int ib2[] = {0, 1, 2, 0, 2, 1}; // these bounds have a degenerate cone since the cluster has two triangles with opposite normals meshopt_Bounds bounds2 = meshopt_computeClusterBounds(ib2, 6, vb1, 3, 12); assert(bounds2.cone_apex[0] == 0 && bounds2.cone_apex[1] == 0 && bounds2.cone_apex[2] == 0); assert(bounds2.cone_axis[0] == 0 && bounds2.cone_axis[1] == 0 && bounds2.cone_axis[2] == 0); assert(bounds2.cone_cutoff == 1); assert(bounds2.cone_axis_s8[0] == 0 && bounds2.cone_axis_s8[1] == 0 && bounds2.cone_axis_s8[2] == 0); assert(bounds2.cone_cutoff_s8 == 127); // however, the bounding sphere needs to be in tact (here we only check bbox for simplicity) assert(bounds2.center[0] - bounds2.radius <= 0 && bounds2.center[0] + bounds2.radius >= 1); assert(bounds2.center[1] - bounds2.radius <= 0 && bounds2.center[1] + bounds2.radius >= 1); assert(bounds2.center[2] - bounds2.radius <= 0 && bounds2.center[2] + bounds2.radius >= 1); } static void sphereBounds() { const float vbr[] = { 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 2, 1, 0, 1, 3, // clang-format }; // without the radius, the center is inside the tetrahedron meshopt_Bounds bounds = meshopt_computeSphereBounds(vbr, 4, sizeof(float) * 4, NULL, 0); assert(fabsf(bounds.center[0] - 0.5f) < 1e-2f); assert(fabsf(bounds.center[1] - 0.5f) < 1e-2f); assert(fabsf(bounds.center[2] - 0.5f) < 1e-2f); assert(bounds.radius < 0.87f); // when using the radius, the last sphere envelops the entire set meshopt_Bounds boundsr = meshopt_computeSphereBounds(vbr, 4, sizeof(float) * 4, vbr + 3, sizeof(float) * 4); assert(fabsf(boundsr.center[0] - 1.f) < 1e-2f); assert(fabsf(boundsr.center[1] - 0.f) < 1e-2f); assert(fabsf(boundsr.center[2] - 1.f) < 1e-2f); assert(fabsf(boundsr.radius - 3.f) < 1e-2f); } static void meshletsEmpty() { const float vbd[4 * 3] = {}; meshopt_Meshlet ml[1]; unsigned int mv[4]; unsigned char mt[8]; size_t mc = meshopt_buildMeshlets(ml, mv, mt, NULL, 0, vbd, 4, sizeof(float) * 3, 64, 64, 0.f); assert(mc == 0); } static void meshletsDense() { const float vbd[4 * 3] = {}; const unsigned int ibd[6] = {0, 2, 1, 1, 2, 3}; meshopt_Meshlet ml[1]; unsigned int mv[4]; unsigned char mt[8]; size_t mc = meshopt_buildMeshlets(ml, mv, mt, ibd, 6, vbd, 4, sizeof(float) * 3, 64, 64, 0.f); assert(mc == 1); assert(ml[0].triangle_count == 2); assert(ml[0].vertex_count == 4); unsigned int tri0[3] = {mv[mt[0]], mv[mt[1]], mv[mt[2]]}; unsigned int tri1[3] = {mv[mt[3]], mv[mt[4]], mv[mt[5]]}; // technically triangles could also be flipped in the meshlet but for now just assume they aren't assert(memcmp(tri0, ibd + 0, 3 * sizeof(unsigned int)) == 0); assert(memcmp(tri1, ibd + 3, 3 * sizeof(unsigned int)) == 0); } static void meshletsSparse() { const float vbd[16 * 3] = {}; const unsigned int ibd[6] = {0, 7, 15, 15, 7, 3}; meshopt_Meshlet ml[1]; unsigned int mv[4]; unsigned char mt[8]; size_t mc = meshopt_buildMeshlets(ml, mv, mt, ibd, 6, vbd, 16, sizeof(float) * 3, 64, 64, 0.f); assert(mc == 1); assert(ml[0].triangle_count == 2); assert(ml[0].vertex_count == 4); unsigned int tri0[3] = {mv[mt[0]], mv[mt[1]], mv[mt[2]]}; unsigned int tri1[3] = {mv[mt[3]], mv[mt[4]], mv[mt[5]]}; // technically triangles could also be flipped in the meshlet but for now just assume they aren't assert(memcmp(tri0, ibd + 0, 3 * sizeof(unsigned int)) == 0); assert(memcmp(tri1, ibd + 3, 3 * sizeof(unsigned int)) == 0); } static void meshletsFlex() { // two tetrahedrons far apart float vb[2 * 4 * 3] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 10, 0, 0, 11, 0, 0, 10, 1, 0, 10, 0, 1, // clang-format :-/ }; unsigned int ib[2 * 4 * 3] = { 0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 3, 2, 4, 5, 6, 4, 6, 7, 4, 7, 5, 5, 7, 6, // clang-format :-/ }; // up to 2 meshlets with min_triangles=4 assert(meshopt_buildMeshletsBound(2 * 4 * 3, 16, 4) == 2); meshopt_Meshlet ml[2]; unsigned int mv[2 * 16]; unsigned char mt[2 * 8 * 3]; // 2 meshlets with up to 8 triangles // with regular function, we should get one meshlet (maxt=8) or two (maxt=4) assert(meshopt_buildMeshlets(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 8, 0.f) == 1); assert(ml[0].triangle_count == 8); assert(ml[0].vertex_count == 8); assert(meshopt_buildMeshlets(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 4, 0.f) == 2); assert(ml[0].triangle_count == 4); assert(ml[0].vertex_count == 4); assert(ml[1].triangle_count == 4); assert(ml[1].vertex_count == 4); // with flex function and mint=4 maxt=8 we should get one meshlet if split_factor is zero, or large enough to accomodate both assert(meshopt_buildMeshletsFlex(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 4, 8, 0.f, 0.f) == 1); assert(ml[0].triangle_count == 8); assert(ml[0].vertex_count == 8); assert(meshopt_buildMeshletsFlex(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 4, 8, 0.f, 10.f) == 1); assert(ml[0].triangle_count == 8); assert(ml[0].vertex_count == 8); // however, with a smaller split factor we should get two meshlets assert(meshopt_buildMeshletsFlex(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 4, 8, 0.f, 1.f) == 2); assert(ml[0].triangle_count == 4); assert(ml[0].vertex_count == 4); assert(ml[1].triangle_count == 4); assert(ml[1].vertex_count == 4); } static void meshletsMax() { float vb[16 * 16 * 3]; unsigned int ib[15 * 15 * 2 * 3]; // 16x16 grid of vertices, 15x15 grid of triangles for (int y = 0; y < 16; ++y) for (int x = 0; x < 16; ++x) { vb[(y * 16 + x) * 3 + 0] = float(x); vb[(y * 16 + x) * 3 + 1] = float(y); vb[(y * 16 + x) * 3 + 2] = 0; } for (int y = 0; y < 15; ++y) for (int x = 0; x < 15; ++x) { ib[(y * 15 + x) * 2 * 3 + 0] = (y + 0) * 16 + (x + 0); ib[(y * 15 + x) * 2 * 3 + 1] = (y + 0) * 16 + (x + 1); ib[(y * 15 + x) * 2 * 3 + 2] = (y + 1) * 16 + (x + 0); ib[(y * 15 + x) * 2 * 3 + 3] = (y + 1) * 16 + (x + 0); ib[(y * 15 + x) * 2 * 3 + 4] = (y + 0) * 16 + (x + 1); ib[(y * 15 + x) * 2 * 3 + 5] = (y + 1) * 16 + (x + 1); } meshopt_Meshlet ml[1]; unsigned int mv[16 * 16]; unsigned char mt[15 * 15 * 2 * 3 + 3]; size_t mc = meshopt_buildMeshlets(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 16 * 16, sizeof(float) * 3, 256, 512, 0.f); assert(mc == 1); assert(ml[0].triangle_count == 450); assert(ml[0].vertex_count == 256); meshopt_optimizeMeshlet(mv, mt, ml[0].triangle_count, ml[0].vertex_count); // check sequential ordering of remapped indices int vmax = -1; for (size_t i = 0; i < 450 * 3; ++i) { assert(mt[i] <= vmax + 1); vmax = vmax < mt[i] ? mt[i] : vmax; } } static void extractMeshlet() { // 15 and 1039 collide in low 10 bits const unsigned int indices[] = {0, 7, 15, 1039, 7, 15}; unsigned int vertices[4]; unsigned char triangles[6]; size_t unique = meshopt_extractMeshletIndices(vertices, triangles, indices, 6); assert(unique == 4); // verify the invariant: vertices[triangles[i]] == indices[i] for (size_t i = 0; i < 6; ++i) assert(vertices[triangles[i]] == indices[i]); assert(vertices[0] == 0 && vertices[1] == 7 && vertices[2] == 15 && vertices[3] == 1039); assert(triangles[0] == 0 && triangles[1] == 1 && triangles[2] == 2 && triangles[3] == 3 && triangles[4] == 1 && triangles[5] == 2); } static void meshletsSpatial() { // two tetrahedrons far apart float vb[2 * 4 * 3] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 10, 0, 0, 11, 0, 0, 10, 1, 0, 10, 0, 1, // clang-format :-/ }; unsigned int ib[2 * 4 * 3] = { 0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 3, 2, 4, 5, 6, 4, 6, 7, 4, 7, 5, 5, 7, 6, // clang-format :-/ }; // up to 2 meshlets with min_triangles=4 assert(meshopt_buildMeshletsBound(2 * 4 * 3, 16, 4) == 2); meshopt_Meshlet ml[2]; unsigned int mv[2 * 16]; unsigned char mt[2 * 8 * 3]; // 2 meshlets with up to 8 triangles // with strict limits, we should get one meshlet (maxt=8) or two (maxt=4) assert(meshopt_buildMeshletsSpatial(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 8, 8, 0.f) == 1); assert(ml[0].triangle_count == 8); assert(ml[0].vertex_count == 8); assert(meshopt_buildMeshletsSpatial(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 4, 4, 0.f) == 2); assert(ml[0].triangle_count == 4); assert(ml[0].vertex_count == 4); assert(ml[1].triangle_count == 4); assert(ml[1].vertex_count == 4); // with maxv=4 we should get two meshlets since we can't accomodate both assert(meshopt_buildMeshletsSpatial(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 4, 4, 8, 0.f) == 2); assert(ml[0].triangle_count == 4); assert(ml[0].vertex_count == 4); assert(ml[1].triangle_count == 4); assert(ml[1].vertex_count == 4); } static void meshletsSpatialDeep() { const int N = 400; const size_t max_vertices = 4; const size_t max_triangles = 4; float vb[(N + 1) * 3]; unsigned int ib[N * 3]; vb[0] = vb[1] = vb[2] = 0; for (size_t i = 0; i < N; ++i) { vb[(i + 1) * 3 + 0] = vb[(i + 1) * 3 + 1] = vb[(i + 1) * 3 + 2] = powf(1.2f, float(i)); ib[i * 3 + 0] = 0; ib[i * 3 + 1] = ib[i * 3 + 2] = unsigned(i + 1); } size_t max_meshlets = meshopt_buildMeshletsBound(N * 3, max_vertices, max_triangles); std::vector meshlets(max_meshlets); std::vector meshlet_vertices(N * 3); std::vector meshlet_triangles(N * 3); size_t result = meshopt_buildMeshletsSpatial(&meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &ib[0], N * 3, &vb[0], N + 1, sizeof(float) * 3, max_vertices, max_triangles, max_triangles, 0.f); assert(result == N); } static void partitionBasic() { // 0 1 2 // 3 // 4 5 6 7 8 // 9 // 10 11 12 const unsigned int ci[] = { 0, 1, 3, 4, 5, 6, 1, 2, 3, 6, 7, 8, 4, 5, 6, 9, 10, 11, 6, 7, 8, 9, 11, 12, // clang-format :-/ }; const unsigned int cc[4] = {6, 6, 6, 6}; unsigned int part[4]; assert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 4, NULL, 13, 0, 1) == 4); assert(part[0] == 0 && part[1] == 1 && part[2] == 2 && part[3] == 3); assert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 4, NULL, 13, 0, 2) == 2); assert(part[0] == 0 && part[1] == 0 && part[2] == 1 && part[3] == 1); assert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 4, NULL, 13, 0, 4) == 1); assert(part[0] == 0 && part[1] == 0 && part[2] == 0 && part[3] == 0); } static void partitionSpatial() { const unsigned int ci[] = { 0, 1, 2, 0, 3, 4, 0, 5, 6, // clang-format :-/ }; const float vb[] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 2, 0, 0, -1, 0, 0, 0, -1, 0, // clang-format :-/ }; const unsigned int cc[3] = {3, 3, 3}; unsigned int part[3]; assert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 3, NULL, 7, 0, 2) == 2); assert(part[0] == 0 && part[1] == 0 && part[2] == 1); assert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 3, vb, 7, sizeof(float) * 3, 2) == 2); assert(part[0] == 0 && part[1] == 1 && part[2] == 0); } static void partitionSpatialMerge() { const unsigned int ci[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, // clang-format :-/ }; const float vb[] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 10, 0, 0, 10, 1, 0, 10, 2, 0, // clang-format :-/ }; const unsigned int cc[3] = {3, 3, 3}; unsigned int part[3]; assert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 3, NULL, 9, 0, 2) == 3); assert(part[0] == 0 && part[1] == 1 && part[2] == 2); assert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 3, vb, 9, sizeof(float) * 3, 2) == 2); assert(part[0] == 0 && part[1] == 0 && part[2] == 1); } static int remapCustomFalse(void*, unsigned int, unsigned int) { return 0; } static int remapCustomTrue(void*, unsigned int, unsigned int) { return 1; } static void remapCustom() { const float vb[] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, -0.f, 1, // clang-format }; unsigned int remap[6]; size_t res; res = meshopt_generateVertexRemapCustom(remap, NULL, 6, vb, 6, sizeof(float) * 3, NULL, NULL); assert(res == 4); for (int i = 0; i < 4; ++i) assert(remap[i] == unsigned(i)); assert(remap[4] == 1); assert(remap[5] == 3); res = meshopt_generateVertexRemapCustom(remap, NULL, 6, vb, 6, sizeof(float) * 3, remapCustomTrue, NULL); assert(res == 4); for (int i = 0; i < 4; ++i) assert(remap[i] == unsigned(i)); assert(remap[4] == 1); assert(remap[5] == 3); res = meshopt_generateVertexRemapCustom(remap, NULL, 6, vb, 6, sizeof(float) * 3, remapCustomFalse, NULL); assert(res == 6); for (int i = 0; i < 6; ++i) assert(remap[i] == unsigned(i)); } static size_t allocCount; static size_t freeCount; static void* customAlloc(size_t size) { allocCount++; return malloc(size); } static void customFree(void* ptr) { freeCount++; free(ptr); } static void customAllocator() { meshopt_setAllocator(customAlloc, customFree); assert(allocCount == 0 && freeCount == 0); float vb[] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; unsigned int ib[] = {0, 1, 2}; unsigned short ibs[] = {0, 1, 2}; // meshopt_computeClusterBounds doesn't allocate meshopt_computeClusterBounds(ib, 3, vb, 3, 12); assert(allocCount == 0 && freeCount == 0); // ... unless IndexAdapter is used meshopt_computeClusterBounds(ibs, 3, vb, 3, 12); assert(allocCount == 1 && freeCount == 1); // meshopt_optimizeVertexFetch allocates internal remap table and temporary storage for in-place remaps meshopt_optimizeVertexFetch(vb, ib, 3, vb, 3, 12); assert(allocCount == 3 && freeCount == 3); // ... plus one for IndexAdapter meshopt_optimizeVertexFetch(vb, ibs, 3, vb, 3, 12); assert(allocCount == 6 && freeCount == 6); meshopt_setAllocator(operator new, operator delete); // customAlloc & customFree should not get called anymore meshopt_optimizeVertexFetch(vb, ib, 3, vb, 3, 12); assert(allocCount == 6 && freeCount == 6); allocCount = freeCount = 0; } static void emptyMesh() { meshopt_optimizeVertexCache(NULL, NULL, 0, 0); meshopt_optimizeVertexCacheFifo(NULL, NULL, 0, 0, 16); meshopt_optimizeOverdraw(NULL, NULL, 0, NULL, 0, 12, 1.f); } static void simplify() { // 0 // 1 2 // 3 4 5 unsigned int ib[] = { 0, 2, 1, 1, 2, 3, 3, 2, 4, 2, 5, 4, // clang-format :-/ }; float vb[] = { 0, 4, 0, 0, 1, 0, 2, 2, 0, 0, 0, 0, 1, 0, 0, 4, 0, 0, // clang-format :-/ }; unsigned int expected[] = { 0, 5, 3, }; float error; assert(meshopt_simplify(ib, ib, 12, vb, 6, 12, 3, 1e-2f, 0, &error) == 3); assert(error < 1e-4f); assert(memcmp(ib, expected, sizeof(expected)) == 0); } static void simplifyStuck() { // tetrahedron can't be simplified due to collapse error restrictions float vb1[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1}; unsigned int ib1[] = {0, 1, 2, 0, 2, 3, 0, 3, 1, 2, 1, 3}; assert(meshopt_simplify(ib1, ib1, 12, vb1, 4, 12, 6, 1e-3f) == 12); // 5-vertex strip can't be simplified due to topology restriction since middle triangle has flipped winding float vb2[] = {0, 0, 0, 1, 0, 0, 2, 0, 0, 0.5f, 1, 0, 1.5f, 1, 0}; unsigned int ib2[] = {0, 1, 3, 3, 1, 4, 1, 2, 4}; // ok unsigned int ib3[] = {0, 1, 3, 1, 3, 4, 1, 2, 4}; // flipped assert(meshopt_simplify(ib2, ib2, 9, vb2, 5, 12, 6, 1e-3f) == 6); assert(meshopt_simplify(ib3, ib3, 9, vb2, 5, 12, 6, 1e-3f) == 9); // 4-vertex quad with a locked corner can't be simplified due to border error-induced restriction float vb4[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0}; unsigned int ib4[] = {0, 1, 3, 0, 3, 2}; assert(meshopt_simplify(ib4, ib4, 6, vb4, 4, 12, 3, 1e-3f) == 6); // 4-vertex quad with a locked corner can't be simplified due to border error-induced restriction float vb5[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0}; unsigned int ib5[] = {0, 1, 4, 0, 3, 2}; assert(meshopt_simplify(ib5, ib5, 6, vb5, 5, 12, 3, 1e-3f) == 6); } static void simplifySloppyStuck() { const float vb[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; const unsigned int ib[] = {0, 1, 2, 0, 1, 2}; unsigned int* target = NULL; // simplifying down to 0 triangles results in 0 immediately assert(meshopt_simplifySloppy(target, ib, 3, vb, 3, 12, 0, 0.f) == 0); // simplifying down to 2 triangles given that all triangles are degenerate results in 0 as well assert(meshopt_simplifySloppy(target, ib, 6, vb, 3, 12, 6, 0.f) == 0); } static void simplifySloppyLocks() { // 0 // 1 2 // 3 4 5 unsigned int ib[] = { 0, 2, 1, 1, 2, 3, 3, 2, 4, 2, 5, 4, // clang-format :-/ }; float vb[] = { 0, 4, 0, 0, 1, 0, 2, 2, 0, 0, 0, 0, 1, 0, 0, 4, 0, 0, // clang-format :-/ }; // lock spine unsigned char locks[] = {1, 0, 1, 0, 0, 1}; unsigned int expected[] = { 0, 2, 1, 1, 2, 5, }; float error; assert(meshopt_simplifySloppy(ib, ib, 12, vb, 6, 12, locks, 3, 1.f, &error) == 6); assert(error == 0.f); assert(memcmp(ib, expected, sizeof(expected)) == 0); } static void simplifyPointsStuck() { const float vb[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; // simplifying down to 0 points results in 0 immediately assert(meshopt_simplifyPoints(NULL, vb, 3, 12, NULL, 0, 0, 0) == 0); } static void simplifyFlip() { // this mesh has been constructed by taking a tessellated irregular grid with a square cutout // and progressively collapsing edges until the only ones left violate border or flip constraints. // there is only one valid non-flip collapse, so we validate that we take it; when flips are allowed, // the wrong collapse is picked instead. float vb[] = { 1.000000f, 1.000000f, -1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, -1.000000f, 1.000000f, 1.000000f, -0.200000f, -0.200000f, 1.000000f, 0.200000f, -0.200000f, 1.000000f, -0.200000f, 0.200000f, 1.000000f, 0.200000f, 0.200000f, 1.000000f, 0.500000f, -0.500000f, 1.000000f, -1.000000f, 0.000000f, // clang-format :-/ }; // the collapse we expect is 7 -> 0 unsigned int ib[] = { 7, 4, 3, 1, 2, 5, 7, 1, 6, 7, 8, 0, // gets removed 7, 6, 4, 8, 5, 2, 8, 7, 3, 8, 3, 5, 5, 6, 1, 7, 0, 1, // gets removed }; unsigned int expected[] = { 0, 4, 3, 1, 2, 5, 0, 1, 6, 0, 6, 4, 8, 5, 2, 8, 0, 3, 8, 3, 5, 5, 6, 1, // clang-format :-/ }; assert(meshopt_simplify(ib, ib, 30, vb, 9, 12, 3, 1e-3f) == 24); assert(memcmp(ib, expected, sizeof(expected)) == 0); } static void simplifyScale() { const float vb[] = {0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3}; assert(meshopt_simplifyScale(vb, 4, 12) == 3.f); } static void simplifyDegenerate() { float vb[] = { 0.000000f, 0.000000f, 0.000000f, 0.000000f, 1.000000f, 0.000000f, 0.000000f, 2.000000f, 0.000000f, 1.000000f, 0.000000f, 0.000000f, 2.000000f, 0.000000f, 0.000000f, 1.000000f, 1.000000f, 0.000000f, // clang-format :-/ }; // 0 1 2 // 3 5 // 4 unsigned int ib[] = { 0, 1, 3, 3, 1, 5, 1, 2, 5, 3, 5, 4, 1, 0, 1, // these two degenerate triangles create a fake reverse edge 0, 3, 0, // which breaks border classification }; unsigned int expected[] = { 0, 1, 4, 4, 1, 2, // clang-format :-/ }; assert(meshopt_simplify(ib, ib, 18, vb, 6, 12, 3, 1e-3f) == 6); assert(memcmp(ib, expected, sizeof(expected)) == 0); } static void simplifyLockBorder() { float vb[] = { 0.000000f, 0.000000f, 0.000000f, 0.000000f, 1.000000f, 0.000000f, 0.000000f, 2.000000f, 0.000000f, 1.000000f, 0.000000f, 0.000000f, 1.000000f, 1.000000f, 0.000000f, 1.000000f, 2.000000f, 0.000000f, 2.000000f, 0.000000f, 0.000000f, 2.000000f, 1.000000f, 0.000000f, 2.000000f, 2.000000f, 0.000000f, // clang-format :-/ }; // 0 1 2 // 3 4 5 // 6 7 8 unsigned int ib[] = { 0, 1, 3, 3, 1, 4, 1, 2, 4, 4, 2, 5, 3, 4, 6, 6, 4, 7, 4, 5, 7, 7, 5, 8, // clang-format :-/ }; unsigned int expected[] = { 0, 1, 3, 1, 2, 3, 3, 2, 5, 6, 3, 7, 3, 5, 7, 7, 5, 8, // clang-format :-/ }; assert(meshopt_simplify(ib, ib, 24, vb, 9, 12, 3, 1e-3f, meshopt_SimplifyLockBorder) == 18); assert(memcmp(ib, expected, sizeof(expected)) == 0); } static void simplifyAttr(bool skip_g) { float vb[8 * 3][6]; for (int y = 0; y < 8; ++y) { // first four rows are a blue gradient, next four rows are a yellow gradient float r = (y < 4) ? 0.8f + y * 0.05f : 0.f; float g = (y < 4) ? 0.8f + y * 0.05f : 0.f; float b = (y < 4) ? 0.f : 0.8f + (7 - y) * 0.05f; for (int x = 0; x < 3; ++x) { vb[y * 3 + x][0] = float(x); vb[y * 3 + x][1] = float(y); vb[y * 3 + x][2] = 0.03f * x + 0.03f * (y % 2) + (x == 2 && y == 7) * 0.03f; vb[y * 3 + x][3] = r; vb[y * 3 + x][4] = g; vb[y * 3 + x][5] = b; } } unsigned int ib[7 * 2][6]; for (int y = 0; y < 7; ++y) { for (int x = 0; x < 2; ++x) { ib[y * 2 + x][0] = (y + 0) * 3 + (x + 0); ib[y * 2 + x][1] = (y + 0) * 3 + (x + 1); ib[y * 2 + x][2] = (y + 1) * 3 + (x + 0); ib[y * 2 + x][3] = (y + 1) * 3 + (x + 0); ib[y * 2 + x][4] = (y + 0) * 3 + (x + 1); ib[y * 2 + x][5] = (y + 1) * 3 + (x + 1); } } float attr_weights[3] = {0.5f, skip_g ? 0.f : 0.5f, 0.5f}; // *0 1 *2 // 3 4 5 // 6 7 8 // *9 10 *11 // *12 13 *14 // 15 16 17 // 18 19 20 // *21 22 *23 unsigned int expected[3][6] = { {0, 2, 11, 0, 11, 9}, {9, 11, 12, 12, 11, 14}, {12, 14, 23, 12, 23, 21}, }; assert(meshopt_simplifyWithAttributes(ib[0], ib[0], 7 * 2 * 6, vb[0], 8 * 3, 6 * sizeof(float), vb[0] + 3, 6 * sizeof(float), attr_weights, 3, NULL, 6 * 3, 1e-2f) == 18); assert(memcmp(ib, expected, sizeof(expected)) == 0); } static void simplifyLockFlags() { float vb[] = { 0, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 1, 1, 0, 1, 2, 0, 2, 0, 0, 2, 1, 0, 2, 2, 0, // clang-format :-/ }; unsigned char lock[9] = { 1, 1, 1, 1, 0, 1, 1, 1, 1, // clang-format :-/ }; // 0 1 2 // 3 4 5 // 6 7 8 unsigned int ib[] = { 0, 1, 3, 3, 1, 4, 1, 2, 4, 4, 2, 5, 3, 4, 6, 6, 4, 7, 4, 5, 7, 7, 5, 8, // clang-format :-/ }; unsigned int expected[] = { 0, 1, 3, 1, 2, 3, 3, 2, 5, 6, 3, 7, 3, 5, 7, 7, 5, 8, // clang-format :-/ }; assert(meshopt_simplifyWithAttributes(ib, ib, 24, vb, 9, 12, NULL, 0, NULL, 0, lock, 3, 1e-3f, 0) == 18); assert(memcmp(ib, expected, sizeof(expected)) == 0); } static void simplifyLockFlagsSeam() { float vb[] = { 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 2, 0, 2, 0, 0, 2, 1, 0, 2, 1, 0, 2, 2, 0, // clang-format :-/ }; unsigned char lock0[12] = { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, // clang-format :-/ }; unsigned char lock1[12] = { 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, // clang-format :-/ }; unsigned char lock2[12] = { 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, // clang-format :-/ }; unsigned char lock3[12] = { 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, // clang-format :-/ }; // 0 1-2 3 // 4 5-6 7 // 8 9-10 11 unsigned int ib[] = { 0, 1, 4, 4, 1, 5, 4, 5, 8, 8, 5, 9, 2, 3, 6, 6, 3, 7, 6, 7, 10, 10, 7, 11, // clang-format :-/ }; unsigned int res[24]; // with no locks, we should be able to collapse the entire mesh (vertices 1-2 and 9-10 are locked but others can move towards them) assert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, NULL, 0, 1.f, 0) == 0); // with corners locked, we should get two quads assert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, lock0, 0, 1.f, 0) == 12); // with both sides locked, we can only collapse the seam spine assert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, lock1, 0, 1.f, 0) == 18); // with seam spine locked, we can collapse nothing; note that we intentionally test two different lock configurations // they each lock only one side of the seam spine, which should be equivalent assert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, lock2, 0, 1.f, 0) == 24); assert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, lock3, 0, 1.f, 0) == 24); } static void simplifySparse() { float vb[] = { 0, 0, 100, 0, 1, 0, 0, 2, 100, 1, 0, 0.1f, 1, 1, 0.1f, 1, 2, 0.1f, 2, 0, 100, 2, 1, 0, 2, 2, 100, // clang-format :-/ }; float vba[] = { 100, 0.5f, 100, 0.5f, 0.5f, 0, 100, 0.5f, 100, // clang-format :-/ }; float aw[] = { 0.5f}; unsigned char lock[9] = { 8, 1, 8, 1, 0, 1, 8, 1, 8, // clang-format :-/ }; // 1 // 3 4 5 // 7 unsigned int ib[] = { 3, 1, 4, 1, 5, 4, 3, 4, 7, 4, 5, 7, // clang-format :-/ }; unsigned int res[12]; // vertices 3-4-5 are slightly elevated along Z which guides the collapses when only using geometry unsigned int expected[] = { 1, 5, 3, 3, 5, 7, // clang-format :-/ }; assert(meshopt_simplify(res, ib, 12, vb, 9, 12, 6, 1e-3f, meshopt_SimplifySparse) == 6); assert(memcmp(res, expected, sizeof(expected)) == 0); // vertices 1-4-7 have a crease in the attribute value which guides the collapses the opposite way when weighing attributes sufficiently unsigned int expecteda[] = { 3, 1, 7, 1, 5, 7, // clang-format :-/ }; assert(meshopt_simplifyWithAttributes(res, ib, 12, vb, 9, 12, vba, sizeof(float), aw, 1, lock, 6, 1e-1f, meshopt_SimplifySparse) == 6); assert(memcmp(res, expecteda, sizeof(expecteda)) == 0); // a final test validates that destination can alias when using sparsity assert(meshopt_simplify(ib, ib, 12, vb, 9, 12, 6, 1e-3f, meshopt_SimplifySparse) == 6); assert(memcmp(ib, expected, sizeof(expected)) == 0); } static void simplifyErrorAbsolute() { float vb[] = { 0, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 1, 1, 1, 1, 2, 0, 2, 0, 0, 2, 1, 0, 2, 2, 0, // clang-format :-/ }; // 0 1 2 // 3 4 5 // 6 7 8 unsigned int ib[] = { 0, 1, 3, 3, 1, 4, 1, 2, 4, 4, 2, 5, 3, 4, 6, 6, 4, 7, 4, 5, 7, 7, 5, 8, // clang-format :-/ }; float error = 0.f; assert(meshopt_simplify(ib, ib, 24, vb, 9, 12, 18, 2.f, meshopt_SimplifyLockBorder | meshopt_SimplifyErrorAbsolute, &error) == 18); assert(fabsf(error - 0.85f) < 0.01f); } static void simplifySeam() { // xyz+attr float vb[] = { 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 2, 0, 1, 1, 0, 0, 0, 1, 1, 0.3f, 0, 1, 1, 0.3f, 1, 1, 2, 0, 1, 2, 0, 0, 0, 2, 1, 0.1f, 0, 2, 1, 0.1f, 1, 2, 2, 0, 1, 3, 0, 0, 0, 3, 1, 0, 0, 3, 1, 0, 1, 3, 2, 0, 1, // clang-format :-/ }; // 0 1-2 3 // 4 5-6 7 // 8 9-10 11 // 12 13-14 15 unsigned int ib[] = { 0, 1, 4, 4, 1, 5, 2, 3, 6, 6, 3, 7, 4, 5, 8, 8, 5, 9, 6, 7, 10, 10, 7, 11, 8, 9, 12, 12, 9, 13, 10, 11, 14, 14, 11, 15, // clang-format :-/ }; // note: vertices 1-2 and 13-14 are classified as locked, because they are on a seam & a border // 0 1-2 3 // 5-6 // 9-10 // 12 13-14 15 unsigned int expected[] = { 0, 1, 13, 2, 3, 14, 0, 13, 12, 14, 3, 15, // clang-format :-/ }; unsigned int res[36]; float error = 0.f; assert(meshopt_simplify(res, ib, 36, vb, 16, 16, 12, 1.f, 0, &error) == 12); assert(memcmp(res, expected, sizeof(expected)) == 0); assert(fabsf(error - 0.1f) < 0.01f); // note: the error is not zero because there is a difference in height between the seam vertices float aw = 1; assert(meshopt_simplifyWithAttributes(res, ib, 36, vb, 16, 16, vb + 3, 16, &aw, 1, NULL, 12, 2.f, 0, &error) == 12); assert(memcmp(res, expected, sizeof(expected)) == 0); assert(fabsf(error - 0.1f) < 0.01f); // note: this is the same error as above because the attribute is constant on either side of the seam } static void simplifySeamFake() { // xyz+attr float vb[] = { 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 2, 0, 0, 0, 3, // clang-format :-/ }; unsigned int ib[] = { 0, 1, 2, 2, 1, 3, // clang-format :-/ }; assert(meshopt_simplify(ib, ib, 6, vb, 4, 16, 0, 1.f, 0, NULL) == 6); } static void simplifySeamAttr() { // xyz+attr float vb[] = { 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 2, 0, 1, 4, 0, 0, 2, 4, 1, 0, 2, 4, 1, 0, 2, 4, 2, 0, 2, // clang-format :-/ }; // 0 1-2 3 // 4 5-6 7 // 8 9-10 11 unsigned int ib[] = { 0, 1, 4, 4, 1, 5, 2, 3, 6, 6, 3, 7, 4, 5, 8, 8, 5, 9, 6, 7, 10, 10, 7, 11, // clang-format :-/ }; // note: vertices 1-2 and 9-10 are classified as locked, because they are on a seam & a border // 0 1-2 3 // 4 7 // 8 9-10 11 unsigned int expected[] = { 0, 1, 4, 2, 3, 7, 4, 1, 8, 8, 1, 9, 2, 7, 10, 10, 7, 11, // clang-format :-/ }; unsigned int res[24]; float error = 0.f; float aw = 1; assert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 16, vb + 3, 16, &aw, 1, NULL, 12, 2.f, meshopt_SimplifyLockBorder, &error) == 18); assert(memcmp(res, expected, sizeof(expected)) == 0); assert(fabsf(error - 0.35f) < 0.01f); } static void simplifyDebug() { // 0 // 1 2 // 3 4 5 unsigned int ib[] = { 0, 2, 1, 1, 2, 3, 3, 2, 4, 2, 5, 4, // clang-format :-/ }; float vb[] = { 0, 4, 0, 0, 1, 0, 2, 2, 0, 0, 0, 0, 1, 0, 0, 4, 0, 0, // clang-format :-/ }; unsigned int expected[] = { 0 | (9u << 28), 5 | (9u << 28), 3 | (9u << 28), }; const unsigned int meshopt_SimplifyInternalDebug = 1 << 30; float error; assert(meshopt_simplify(ib, ib, 12, vb, 6, 12, 3, 1e-2f, meshopt_SimplifyInternalDebug, &error) == 3); assert(error < 1e-4f); assert(memcmp(ib, expected, sizeof(expected)) == 0); } static void simplifyPrune() { // 0 // 1 2 // 3 4 5 // + // 6 7 8 (same position) unsigned int ib[] = { 3, 2, 4, 0, 2, 1, 1, 2, 3, 2, 5, 4, 6, 7, 8, // clang-format :-/ }; float vb[] = { 0, 4, 0, 0, 1, 0, 2, 2, 0, 0, 0, 0, 1, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // clang-format :-/ }; unsigned int expected[] = { 0, 5, 3, }; float error; assert(meshopt_simplify(ib, ib, 15, vb, 9, 12, 3, 1e-2f, meshopt_SimplifyPrune, &error) == 3); assert(error < 1e-4f); assert(memcmp(ib, expected, sizeof(expected)) == 0); // re-run prune with and without sparsity on a small subset to make sure the component code correctly handles sparse subsets assert(meshopt_simplify(ib, ib, 3, vb, 9, 12, 3, 1e-2f, meshopt_SimplifyPrune, &error) == 3); assert(meshopt_simplify(ib, ib, 3, vb, 9, 12, 3, 1e-2f, meshopt_SimplifyPrune | meshopt_SimplifySparse, &error) == 3); assert(memcmp(ib, expected, sizeof(expected)) == 0); } static void simplifyPruneCleanup() { unsigned int ib[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, // clang-format :-/ }; float vb[] = { 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 2, 1, 2, 0, 1, 0, 0, 2, 0, 4, 2, 4, 0, 2, // clang-format :-/ }; unsigned int expected[] = { 6, 7, 8, }; float error; assert(meshopt_simplify(ib, ib, 9, vb, 9, 12, 3, 1.f, meshopt_SimplifyLockBorder | meshopt_SimplifyPrune, &error) == 3); assert(fabsf(error - 0.37f) < 0.01f); assert(memcmp(ib, expected, sizeof(expected)) == 0); } static void simplifyPruneFunc() { unsigned int ib[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, // clang-format :-/ }; float vb[] = { 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 2, 1, 2, 0, 1, 0, 0, 2, 0, 4, 2, 4, 0, 2, // clang-format :-/ }; unsigned int expected[] = { 6, 7, 8, }; assert(meshopt_simplifyPrune(ib, ib, 9, vb, 9, 12, 0.5f) == 3); assert(memcmp(ib, expected, sizeof(expected)) == 0); } static void simplifyUpdate() { float vb[5][4] = { {0, 0, 0, 0}, {1, 1, 0, 0}, {2, 0, 0, 0}, {0.9f, 0.2f, 0.1f, 0.2f}, {1.1f, 0.2f, 0.1f, 0.1f}, }; // 1 // 3 4 // 0 2 unsigned int ib[15] = { 0, 1, 3, 3, 1, 4, 4, 1, 2, 0, 3, 2, 3, 4, 2, // }; float attr_weight = 1.f; assert(meshopt_simplifyWithUpdate(ib, 15, vb[0], 5, 4 * sizeof(float), vb[0] + 3, 4 * sizeof(float), &attr_weight, 1, NULL, 9, 1.f) == 9); unsigned int expected[] = { 0, 1, 3, 3, 1, 2, 0, 3, 2, // }; assert(memcmp(ib, expected, sizeof(expected)) == 0); // border vertices haven't moved but may have small floating point drift for (int i = 0; i < 3; ++i) assert(fabsf(vb[i][3]) < 1e-6f); // center vertex got updated assert(fabsf(vb[3][0] - 0.88f) < 1e-2f); assert(fabsf(vb[3][1] - 0.19f) < 1e-2f); assert(fabsf(vb[3][2] - 0.11f) < 1e-2f); assert(fabsf(vb[3][3] - 0.18f) < 1e-2f); } static void simplifyUpdateLocked(unsigned int options) { float vb[5][4] = { {0, 0, 0, 0}, {1, 1, 0, 0}, {2, 0, 0, 0}, {0.9f, 0.2f, 0.1f, 0.2f}, {1.1f, 0.2f, 0.1f, 0.1f}, }; // 1 // 3 4 // 0 2 unsigned int ib[15] = { 0, 1, 3, 3, 1, 4, 4, 1, 2, 0, 3, 2, 3, 4, 2, // }; float attr_weight = 1.f; unsigned char vertex_lock[5] = {0, 0, 0, 1, 0}; assert(meshopt_simplifyWithUpdate(ib, 15, vb[0], 5, 4 * sizeof(float), vb[0] + 3, 4 * sizeof(float), &attr_weight, 1, vertex_lock, 9, 1.f, options) == 9); unsigned int expected[] = { 0, 1, 3, 3, 1, 2, 0, 3, 2, // }; assert(memcmp(ib, expected, sizeof(expected)) == 0); for (int i = 0; i < 3; ++i) assert(fabsf(vb[i][3]) < 1e-6f); // locking guarantees exact result assert(vb[3][0] == 0.9f); assert(vb[3][1] == 0.2f); assert(vb[3][2] == 0.1f); assert(vb[3][3] == 0.2f); } static void adjacency() { // 0 1/4 // 2/5 3 const float vb[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0}; const unsigned int ib[] = {0, 1, 2, 5, 4, 3}; unsigned int adjib[12]; meshopt_generateAdjacencyIndexBuffer(adjib, ib, 6, vb, 6, 12); unsigned int expected[] = { // patch 0 0, 0, 1, 3, 2, 2, // patch 1 5, 0, 4, 4, 3, 3, // clang-format :-/ }; assert(memcmp(adjib, expected, sizeof(expected)) == 0); } static void tessellation() { // 0 1/4 // 2/5 3 const float vb[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0}; const unsigned int ib[] = {0, 1, 2, 5, 4, 3}; unsigned int tessib[24]; meshopt_generateTessellationIndexBuffer(tessib, ib, 6, vb, 6, 12); unsigned int expected[] = { // patch 0 0, 1, 2, 0, 1, 4, 5, 2, 0, 0, 1, 2, // patch 1 5, 4, 3, 2, 1, 4, 3, 3, 5, 2, 1, 3, // clang-format :-/ }; assert(memcmp(tessib, expected, sizeof(expected)) == 0); } static void provoking() { // 0 1 2 // 3 4 5 const unsigned int ib[] = { 0, 1, 3, 3, 1, 4, 1, 2, 4, 4, 2, 5, 0, 2, 4, // clang-format :-/ }; unsigned int pib[15]; unsigned int pre[6 + 5]; // limit is vertex count + triangle count size_t res = meshopt_generateProvokingIndexBuffer(pib, pre, ib, 15, 6); unsigned int expectedib[] = { 0, 5, 1, 1, 4, 0, 2, 4, 1, 3, 4, 2, 4, 5, 2, // clang-format :-/ }; unsigned int expectedre[] = { 3, 1, 2, 5, 4, 0, // clang-format :-/ }; assert(res == 6); assert(memcmp(pib, expectedib, sizeof(expectedib)) == 0); assert(memcmp(pre, expectedre, sizeof(expectedre)) == 0); } static void quantizeFloat() { volatile float zero = 0.f; // avoids div-by-zero warnings assert(meshopt_quantizeFloat(1.2345f, 23) == 1.2345f); assert(meshopt_quantizeFloat(1.2345f, 16) == 1.2344971f); assert(meshopt_quantizeFloat(1.2345f, 8) == 1.2343750f); assert(meshopt_quantizeFloat(1.2345f, 4) == 1.25f); assert(meshopt_quantizeFloat(1.2345f, 1) == 1.0); assert(meshopt_quantizeFloat(1.f, 0) == 1.0f); assert(meshopt_quantizeFloat(1.f / zero, 0) == 1.f / zero); assert(meshopt_quantizeFloat(-1.f / zero, 0) == -1.f / zero); float nanf = meshopt_quantizeFloat(zero / zero, 8); assert(nanf != nanf); } static void quantizeHalf() { volatile float zero = 0.f; // avoids div-by-zero warnings // normal assert(meshopt_quantizeHalf(1.2345f) == 0x3cf0); // overflow assert(meshopt_quantizeHalf(65535.f) == 0x7c00); assert(meshopt_quantizeHalf(-65535.f) == 0xfc00); // large assert(meshopt_quantizeHalf(65000.f) == 0x7bef); assert(meshopt_quantizeHalf(-65000.f) == 0xfbef); // small assert(meshopt_quantizeHalf(0.125f) == 0x3000); assert(meshopt_quantizeHalf(-0.125f) == 0xb000); // very small assert(meshopt_quantizeHalf(1e-4f) == 0x068e); assert(meshopt_quantizeHalf(-1e-4f) == 0x868e); // underflow assert(meshopt_quantizeHalf(1e-5f) == 0x0000); assert(meshopt_quantizeHalf(-1e-5f) == 0x8000); // exponent underflow assert(meshopt_quantizeHalf(1e-20f) == 0x0000); assert(meshopt_quantizeHalf(-1e-20f) == 0x8000); // exponent overflow assert(meshopt_quantizeHalf(1e20f) == 0x7c00); assert(meshopt_quantizeHalf(-1e20f) == 0xfc00); // inf assert(meshopt_quantizeHalf(1.f / zero) == 0x7c00); assert(meshopt_quantizeHalf(-1.f / zero) == 0xfc00); // nan unsigned short nanh = meshopt_quantizeHalf(zero / zero); assert(nanh == 0x7e00 || nanh == 0xfe00); } static void dequantizeHalf() { volatile float zero = 0.f; // avoids div-by-zero warnings // normal assert(meshopt_dequantizeHalf(0x3cf0) == 1.234375f); // large assert(meshopt_dequantizeHalf(0x7bef) == 64992.f); assert(meshopt_dequantizeHalf(0xfbef) == -64992.f); // small assert(meshopt_dequantizeHalf(0x3000) == 0.125f); assert(meshopt_dequantizeHalf(0xb000) == -0.125f); // very small assert(meshopt_dequantizeHalf(0x068e) == 1.00016594e-4f); assert(meshopt_dequantizeHalf(0x868e) == -1.00016594e-4f); // denormal assert(meshopt_dequantizeHalf(0x00ff) == 0.f); assert(meshopt_dequantizeHalf(0x80ff) == 0.f); // actually this is -0.f assert(1.f / meshopt_dequantizeHalf(0x80ff) == -1.f / zero); // inf assert(meshopt_dequantizeHalf(0x7c00) == 1.f / zero); assert(meshopt_dequantizeHalf(0xfc00) == -1.f / zero); // nan float nanf = meshopt_dequantizeHalf(0x7e00); assert(nanf != nanf); } static void encodeMeshletBound() { const unsigned char triangles[5 * 3] = { 0, 1, 2, 2, 1, 3, 3, 5, 4, 2, 0, 6, 7, 7, 7, // clang-format :-/ }; const unsigned int vertices[7] = { 5, 12, 140, 0, 12389, 123456789, 7, }; size_t bound = meshopt_encodeMeshletBound(7, 5); unsigned char enc[256]; size_t size = meshopt_encodeMeshlet(enc, sizeof(enc), vertices, 7, triangles, 5); assert(size > 0 && size <= bound); assert(meshopt_encodeMeshlet(enc, size - 1, vertices, 7, triangles, 5) == 0); } template static void validateDecodeMeshlet(const unsigned char* data, size_t size, const unsigned int* vertices, size_t vertex_count, const unsigned char* triangles, size_t triangle_count) { V rv[VS]; T rt[TS]; int rc = meshopt_decodeMeshlet(rv, vertex_count, rt, triangle_count, data, size); assert(rc == 0); for (size_t j = 0; j < vertex_count; ++j) assert(rv[j] == V(vertices[j])); for (size_t j = 0; j < triangle_count; ++j) { unsigned int a = triangles[j * 3 + 0]; unsigned int b = triangles[j * 3 + 1]; unsigned int c = triangles[j * 3 + 2]; unsigned int tri = sizeof(T) == 1 ? rt[j * 3] | (rt[j * 3 + 1] << 8) | (rt[j * 3 + 2] << 16) : rt[j]; unsigned int abc = (a << 0) | (b << 8) | (c << 16); unsigned int bca = (b << 0) | (c << 8) | (a << 16); unsigned int cba = (c << 0) | (a << 8) | (b << 16); assert(tri == abc || tri == bca || tri == cba); } } static void decodeMeshletSafety() { const unsigned char triangles[5 * 3] = { 0, 1, 2, 2, 1, 3, 3, 5, 4, 2, 0, 6, 6, 6, 6, // clang-format :-/ }; const unsigned int vertices[7] = { 5, 12, 140, 0, 12389, 123456789, 7, }; unsigned char encb[256]; size_t size = meshopt_encodeMeshlet(encb, sizeof(encb), vertices, 7, triangles, 5); assert(size > 0); // move encoded buffer to the end to make sure any over-reads trigger sanitizers unsigned char* enc = encb + sizeof(encb) - size; memmove(enc, encb, size); validateDecodeMeshlet(enc, size, vertices, 7, triangles, 5); // decodeMeshlet uses aligned 32-bit writes => must round vertex/triangle storage up when using short/char decoding // note the +1 in triangle storage is because align(5*3, 4) = 16; it's up to +3 in general case validateDecodeMeshlet(enc, size, vertices, 7, triangles, 5); validateDecodeMeshlet(enc, size, vertices, 7, triangles, 5); validateDecodeMeshlet(enc, size, vertices, 7, triangles, 5); // any truncated input should not be decodable; we check both prefix and suffix truncation unsigned int rv[7], rt[5]; for (size_t i = 1; i < size; ++i) assert(meshopt_decodeMeshlet(rv, 7, rt, 5, enc, i) < 0); for (size_t i = 1; i < size; ++i) assert(meshopt_decodeMeshlet(rv, 7, rt, 5, enc + i, size - i) < 0); // because SIMD implementation is specialized by size, we need to test truncated inputs for short representations unsigned short rvs[7 + 1]; // 32b alignment unsigned char rts[5 * 3 + 1]; // 32b alignment for (size_t i = 1; i < size; ++i) assert(meshopt_decodeMeshlet(rvs, 7, rts, 5, enc, i) < 0); for (size_t i = 1; i < size; ++i) assert(meshopt_decodeMeshlet(rvs, 7, rts, 5, enc + i, size - i) < 0); // when using decodeMeshletRaw, the output buffer sizes must be 16b aligned unsigned int rvr[8], rtr[8]; for (size_t i = 1; i < size; ++i) assert(meshopt_decodeMeshletRaw(rvr, 7, rtr, 5, enc, i) < 0); for (size_t i = 1; i < size; ++i) assert(meshopt_decodeMeshletRaw(rvr, 7, rtr, 5, enc + i, size - i) < 0); // otherwise, decodeMeshlet and decodeMeshletRaw should agree int rc = meshopt_decodeMeshlet(rv, 7, rt, 5, enc, size); int rcr = meshopt_decodeMeshletRaw(rvr, 7, rtr, 5, enc, size); assert(rc == 0 && rcr == 0); assert(memcmp(rv, rvr, 7 * sizeof(unsigned int)) == 0); assert(memcmp(rt, rtr, 5 * sizeof(unsigned int)) == 0); } static void decodeMeshletBasic() { const unsigned char triangles[5 * 3] = { 0, 1, 2, 2, 1, 3, 4, 3, 5, 2, 0, 6, 6, 6, 6, // clang-format :-/ }; const unsigned int vertices[7] = { 5, 12, 140, 0, 12389, 123456789, 7, }; const unsigned char encoded[46] = { 0x0a, 0x0c, 0xfe, 0x19, 0x01, 0xc8, 0x60, 0x00, 0x00, 0x5e, 0x39, 0xb7, 0x0e, 0x1d, 0x9a, 0xb7, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x05, 0x02, 0x00, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0xff, 0x2c, 0xff, 0x0f, // clang-format :-/ }; unsigned int rv[7]; unsigned char rt[5 * 3 + 1]; int rc = meshopt_decodeMeshlet(rv, 7, rt, 5, encoded, sizeof(encoded)); assert(rc == 0); assert(memcmp(rv, vertices, sizeof(vertices)) == 0); assert(memcmp(rt, triangles, sizeof(triangles)) == 0); } static void decodeMeshletTypical() { const unsigned char triangles[44 * 3] = { 0, 1, 2, 0, 2, 3, 3, 2, 4, 3, 4, 5, 0, 3, 6, 6, 3, 5, 0, 6, 7, 7, 6, 8, 8, 6, 5, 7, 8, 9, 8, 5, 10, 10, 5, 4, 10, 4, 11, 11, 4, 12, 11, 12, 13, 10, 11, 14, 10, 14, 8, 14, 11, 15, 15, 11, 13, 15, 13, 16, 15, 16, 14, 16, 13, 17, 14, 16, 18, 14, 18, 8, 18, 16, 19, 19, 16, 20, 20, 16, 17, 20, 17, 21, 20, 21, 22, 20, 22, 19, 22, 21, 23, 19, 22, 24, 19, 24, 25, 19, 25, 18, 18, 25, 26, 18, 26, 8, 8, 26, 9, 22, 23, 27, 22, 27, 24, 27, 23, 28, 27, 28, 29, 27, 29, 24, 29, 28, 30, 24, 29, 31, // clang-format :-/ }; const unsigned int vertices[32] = { 10, 11, 9, 12, 8, 13, 14, 15, 16, 17, 18, 19, 0, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // clang-format :-/ }; const unsigned char encoded[53] = { 0x14, 0x05, 0x04, 0x09, 0x08, 0x27, 0x26, 0x05, 0x05, 0x04, 0x08, 0x0d, 0x0e, 0x08, 0x11, 0x13, 0x12, 0x08, 0x09, 0x16, 0x17, 0x18, 0x18, 0x0d, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x02, 0x38, 0x24, 0x43, 0x34, 0x20, 0x80, 0x61, 0x03, 0x61, 0x16, 0x26, 0x03, 0x10, 0x66, 0x10, 0x12, 0xe3, 0x61, 0x10, 0x66, // clang-format :-/ }; unsigned int rv[32]; unsigned char rt[44 * 3]; int rc = meshopt_decodeMeshlet(rv, 32, rt, 44, encoded, sizeof(encoded)); assert(rc == 0); assert(memcmp(rv, vertices, sizeof(vertices)) == 0); assert(memcmp(rt, triangles, sizeof(triangles)) == 0); } static void opacityMap() { const size_t triangle_count = 6; const size_t vertex_count = 8; const unsigned int indices[triangle_count * 3] = { // 4 corner triangles 0, 4, 7, 1, 5, 4, 2, 6, 5, 3, 7, 6, // 2 center triangles (2x larger) 4, 6, 5, // note: this triangle is flipped from its correct orientation to produce the same OMM to test compaction 4, 6, 7, // clang-format :-/ }; const float uvs[vertex_count * 2] = { 0.f, 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f, 0.5f, 0.f, 1.f, 0.5f, 0.5f, 1.f, 0.f, 0.5f, // clang-format :-/ }; const unsigned int texture_size = 32; unsigned char texture[texture_size * texture_size]; float center = float(texture_size) * 0.5f; float radius = 10.f; for (unsigned int y = 0; y < texture_size; ++y) for (unsigned int x = 0; x < texture_size; ++x) { float dx = float(x) + 0.5f - center; float dy = float(y) + 0.5f - center; float dc = radius - sqrtf(dx * dx + dy * dy); texture[y * texture_size + x] = (unsigned char)meshopt_quantizeUnorm(dc + 0.5f, 8); } // subdivision parameterrs const float target_edge = 2.5f; const int max_level = 4; // state histogram for testing int histogram[2][4] = {}; for (int k = 0; k < 2; ++k) { int states = 2 << k; unsigned char levels[triangle_count]; unsigned int sources[triangle_count]; int omm_indices[triangle_count]; // compute level and source triangle per OMM; note that this can also deduplicate OMMs based on UVs but in our case the UVs are unique size_t omm_count = meshopt_opacityMapMeasure(levels, sources, omm_indices, indices, triangle_count * 3, uvs, vertex_count, sizeof(float) * 2, texture_size, texture_size, max_level, target_edge); assert(omm_count <= triangle_count); // validate expected levels/special indices based on underlying test data; depends on implementation specifics, might change assert(levels[0] == 2 && levels[1] == 2 && levels[2] == 2 && levels[3] == 2); assert(levels[4] == 3 && levels[5] == 3); // layout OMM data std::vector offsets(omm_count); size_t data_size = 0; for (size_t i = 0; i < omm_count; ++i) { offsets[i] = unsigned(data_size); data_size += meshopt_opacityMapEntrySize(levels[i], states); } std::vector data(data_size); for (size_t i = 0; i < omm_count; ++i) { unsigned int tri = sources[i]; assert(tri < triangle_count); const float* uv0 = &uvs[indices[tri * 3 + 0] * 2]; const float* uv1 = &uvs[indices[tri * 3 + 1] * 2]; const float* uv2 = &uvs[indices[tri * 3 + 2] * 2]; meshopt_opacityMapRasterize(&data[offsets[i]], levels[i], states, uv0, uv1, uv2, texture, 1, texture_size, texture_size, texture_size); size_t micro_triangle_count = size_t(1) << (levels[i] * 2); for (size_t j = 0; j < micro_triangle_count; ++j) { unsigned char byte = data[offsets[i] + (j >> (states == 2 ? 3 : 2))]; int state = (byte >> (states == 2 ? j & 7 : (j & 3) * 2)) & (states - 1); histogram[k][state]++; } } // compact OMMs; note, this can be done per mesh but for optimal reuse this should be done for all meshes in model/scene at once size_t compact_count = meshopt_opacityMapCompact(&data[0], data.size(), levels, &offsets[0], omm_count, omm_indices, triangle_count, states); assert(compact_count <= omm_count); data.resize(compact_count == 0 ? 0 : offsets[compact_count - 1] + meshopt_opacityMapEntrySize(levels[compact_count - 1], states)); // after compaction, some OMM indices may be replaced with a special index (-4..-1) for (size_t i = 0; i < triangle_count; ++i) assert(omm_indices[i] < 0 || size_t(omm_indices[i]) < compact_count); // validate expected levels/special indices based on underlying test data; depends on implementation specifics, might change assert(levels[0] == 3 && levels[1] == 3); // note: OMM data got compacted so we only have 3-level OMMs left assert(omm_indices[0] == -1 && omm_indices[1] == -1 && omm_indices[2] == -1 && omm_indices[3] == -1); assert(compact_count == 1 && omm_indices[4] == 0 && omm_indices[5] == 0); // we force the two center triangles to use opposite orientations to make sure their data matches } // validate expected histogram based on underlying test data; depends on rasterization specifics, might change assert(histogram[0][0] == histogram[1][0] + histogram[1][2]); assert(histogram[0][1] == histogram[1][1] + histogram[1][3]); float opaque = float(histogram[0][1]) / float(histogram[0][0] + histogram[0][1]); float known = float(histogram[1][0] + histogram[1][1]) / float(histogram[1][0] + histogram[1][1] + histogram[1][2] + histogram[1][3]); assert(fabsf(opaque - 0.36f) < 1e-2f); assert(fabsf(known - 0.76f) < 1e-2f); } void runTests() { decodeIndexV0(); decodeIndexV1(); decodeIndexV1More(); decodeIndexV1ThreeEdges(); decodeIndex16(); encodeIndexMemorySafe(); decodeIndexMemorySafe(); decodeIndexRejectExtraBytes(); decodeIndexRejectMalformedHeaders(); decodeIndexRejectInvalidVersion(); decodeIndexMalformedVByte(); roundtripIndexTricky(); encodeIndexEmpty(); decodeIndexSequence(); decodeIndexSequence16(); encodeIndexSequenceMemorySafe(); decodeIndexSequenceMemorySafe(); decodeIndexSequenceRejectExtraBytes(); decodeIndexSequenceRejectMalformedHeaders(); decodeIndexSequenceRejectInvalidVersion(); encodeIndexSequenceEmpty(); decodeVertexV0(); decodeVertexV0More(); decodeVertexV0Mode2(); decodeVertexV1(); decodeVertexV1Custom(); decodeVertexV1Deltas(); for (int version = 0; version <= 1; ++version) { meshopt_encodeVertexVersion(version); decodeVertexMemorySafe(); decodeVertexRejectExtraBytes(); decodeVertexRejectMalformedHeaders(); decodeVertexBitGroups(); decodeVertexBitGroupSentinels(); decodeVertexDeltas(); decodeVertexBitXor(); decodeVertexLarge(); decodeVertexSmall(); encodeVertexEmpty(); encodeVertexMemorySafe(); } decodeVersion(); decodeFilterOct8(); decodeFilterOct12(); decodeFilterQuat12(); decodeFilterExp(); encodeFilterOct8(); encodeFilterOct12(); encodeFilterQuat12(); encodeFilterExp(); encodeFilterExpZero(); encodeFilterExpAlias(); encodeFilterExpClamp(); encodeFilterColor8(); encodeFilterColor12(); clusterBoundsDegenerate(); sphereBounds(); extractMeshlet(); meshletsEmpty(); meshletsDense(); meshletsSparse(); meshletsFlex(); meshletsMax(); meshletsSpatial(); meshletsSpatialDeep(); partitionBasic(); partitionSpatial(); partitionSpatialMerge(); remapCustom(); customAllocator(); emptyMesh(); simplify(); simplifyStuck(); simplifySloppyStuck(); simplifySloppyLocks(); simplifyPointsStuck(); simplifyFlip(); simplifyScale(); simplifyDegenerate(); simplifyLockBorder(); simplifyAttr(/* skip_g= */ false); simplifyAttr(/* skip_g= */ true); simplifyLockFlags(); simplifyLockFlagsSeam(); simplifySparse(); simplifyErrorAbsolute(); simplifySeam(); simplifySeamFake(); simplifySeamAttr(); simplifyDebug(); simplifyPrune(); simplifyPruneCleanup(); simplifyPruneFunc(); simplifyUpdate(); simplifyUpdateLocked(0); simplifyUpdateLocked(meshopt_SimplifySparse); adjacency(); tessellation(); provoking(); quantizeFloat(); quantizeHalf(); dequantizeHalf(); encodeMeshletBound(); decodeMeshletSafety(); decodeMeshletBasic(); decodeMeshletTypical(); opacityMap(); } ================================================ FILE: extern/.clang-format ================================================ DisableFormat: true ================================================ FILE: extern/cgltf.h ================================================ /** * cgltf - a single-file glTF 2.0 parser written in C99. * * Version: 1.15 * * Website: https://github.com/jkuhlmann/cgltf * * Distributed under the MIT License, see notice at the end of this file. * * Building: * Include this file where you need the struct and function * declarations. Have exactly one source file where you define * `CGLTF_IMPLEMENTATION` before including this file to get the * function definitions. * * Reference: * `cgltf_result cgltf_parse(const cgltf_options*, const void*, * cgltf_size, cgltf_data**)` parses both glTF and GLB data. If * this function returns `cgltf_result_success`, you have to call * `cgltf_free()` on the created `cgltf_data*` variable. * Note that contents of external files for buffers and images are not * automatically loaded. You'll need to read these files yourself using * URIs in the `cgltf_data` structure. * * `cgltf_options` is the struct passed to `cgltf_parse()` to control * parts of the parsing process. You can use it to force the file type * and provide memory allocation as well as file operation callbacks. * Should be zero-initialized to trigger default behavior. * * `cgltf_data` is the struct allocated and filled by `cgltf_parse()`. * It generally mirrors the glTF format as described by the spec (see * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0). * * `void cgltf_free(cgltf_data*)` frees the allocated `cgltf_data` * variable. * * `cgltf_result cgltf_load_buffers(const cgltf_options*, cgltf_data*, * const char* gltf_path)` can be optionally called to open and read buffer * files using the `FILE*` APIs. The `gltf_path` argument is the path to * the original glTF file, which allows the parser to resolve the path to * buffer files. * * `cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, * cgltf_size size, const char* base64, void** out_data)` decodes * base64-encoded data content. Used internally by `cgltf_load_buffers()`. * This is useful when decoding data URIs in images. * * `cgltf_result cgltf_parse_file(const cgltf_options* options, const * char* path, cgltf_data** out_data)` can be used to open the given * file using `FILE*` APIs and parse the data using `cgltf_parse()`. * * `cgltf_result cgltf_validate(cgltf_data*)` can be used to do additional * checks to make sure the parsed glTF data is valid. * * `cgltf_node_transform_local` converts the translation / rotation / scale properties of a node * into a mat4. * * `cgltf_node_transform_world` calls `cgltf_node_transform_local` on every ancestor in order * to compute the root-to-node transformation. * * `cgltf_accessor_unpack_floats` reads in the data from an accessor, applies sparse data (if any), * and converts them to floating point. Assumes that `cgltf_load_buffers` has already been called. * By passing null for the output pointer, users can find out how many floats are required in the * output buffer. * * `cgltf_accessor_unpack_indices` reads in the index data from an accessor. Assumes that * `cgltf_load_buffers` has already been called. By passing null for the output pointer, users can * find out how many indices are required in the output buffer. Returns 0 if the accessor is * sparse or if the output component size is less than the accessor's component size. * * `cgltf_num_components` is a tiny utility that tells you the dimensionality of * a certain accessor type. This can be used before `cgltf_accessor_unpack_floats` to help allocate * the necessary amount of memory. `cgltf_component_size` and `cgltf_calc_size` exist for * similar purposes. * * `cgltf_accessor_read_float` reads a certain element from a non-sparse accessor and converts it to * floating point, assuming that `cgltf_load_buffers` has already been called. The passed-in element * size is the number of floats in the output buffer, which should be in the range [1, 16]. Returns * false if the passed-in element_size is too small, or if the accessor is sparse. * * `cgltf_accessor_read_uint` is similar to its floating-point counterpart, but limited to reading * vector types and does not support matrix types. The passed-in element size is the number of uints * in the output buffer, which should be in the range [1, 4]. Returns false if the passed-in * element_size is too small, or if the accessor is sparse. * * `cgltf_accessor_read_index` is similar to its floating-point counterpart, but it returns size_t * and only works with single-component data types. * * `cgltf_copy_extras_json` allows users to retrieve the "extras" data that can be attached to many * glTF objects (which can be arbitrary JSON data). This is a legacy function, consider using * cgltf_extras::data directly instead. You can parse this data using your own JSON parser * or, if you've included the cgltf implementation using the integrated JSMN JSON parser. */ #ifndef CGLTF_H_INCLUDED__ #define CGLTF_H_INCLUDED__ #include #include /* For uint8_t, uint32_t */ #ifdef __cplusplus extern "C" { #endif typedef size_t cgltf_size; typedef long long int cgltf_ssize; typedef float cgltf_float; typedef int cgltf_int; typedef unsigned int cgltf_uint; typedef int cgltf_bool; typedef enum cgltf_file_type { cgltf_file_type_invalid, cgltf_file_type_gltf, cgltf_file_type_glb, cgltf_file_type_max_enum } cgltf_file_type; typedef enum cgltf_result { cgltf_result_success, cgltf_result_data_too_short, cgltf_result_unknown_format, cgltf_result_invalid_json, cgltf_result_invalid_gltf, cgltf_result_invalid_options, cgltf_result_file_not_found, cgltf_result_io_error, cgltf_result_out_of_memory, cgltf_result_legacy_gltf, cgltf_result_max_enum } cgltf_result; typedef struct cgltf_memory_options { void* (*alloc_func)(void* user, cgltf_size size); void (*free_func) (void* user, void* ptr); void* user_data; } cgltf_memory_options; typedef struct cgltf_file_options { cgltf_result(*read)(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data); void (*release)(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data, cgltf_size size); void* user_data; } cgltf_file_options; typedef struct cgltf_options { cgltf_file_type type; /* invalid == auto detect */ cgltf_size json_token_count; /* 0 == auto */ cgltf_memory_options memory; cgltf_file_options file; } cgltf_options; typedef enum cgltf_buffer_view_type { cgltf_buffer_view_type_invalid, cgltf_buffer_view_type_indices, cgltf_buffer_view_type_vertices, cgltf_buffer_view_type_max_enum } cgltf_buffer_view_type; typedef enum cgltf_attribute_type { cgltf_attribute_type_invalid, cgltf_attribute_type_position, cgltf_attribute_type_normal, cgltf_attribute_type_tangent, cgltf_attribute_type_texcoord, cgltf_attribute_type_color, cgltf_attribute_type_joints, cgltf_attribute_type_weights, cgltf_attribute_type_custom, cgltf_attribute_type_max_enum } cgltf_attribute_type; typedef enum cgltf_component_type { cgltf_component_type_invalid, cgltf_component_type_r_8, /* BYTE */ cgltf_component_type_r_8u, /* UNSIGNED_BYTE */ cgltf_component_type_r_16, /* SHORT */ cgltf_component_type_r_16u, /* UNSIGNED_SHORT */ cgltf_component_type_r_32u, /* UNSIGNED_INT */ cgltf_component_type_r_32f, /* FLOAT */ cgltf_component_type_max_enum } cgltf_component_type; typedef enum cgltf_type { cgltf_type_invalid, cgltf_type_scalar, cgltf_type_vec2, cgltf_type_vec3, cgltf_type_vec4, cgltf_type_mat2, cgltf_type_mat3, cgltf_type_mat4, cgltf_type_max_enum } cgltf_type; typedef enum cgltf_primitive_type { cgltf_primitive_type_invalid, cgltf_primitive_type_points, cgltf_primitive_type_lines, cgltf_primitive_type_line_loop, cgltf_primitive_type_line_strip, cgltf_primitive_type_triangles, cgltf_primitive_type_triangle_strip, cgltf_primitive_type_triangle_fan, cgltf_primitive_type_max_enum } cgltf_primitive_type; typedef enum cgltf_alpha_mode { cgltf_alpha_mode_opaque, cgltf_alpha_mode_mask, cgltf_alpha_mode_blend, cgltf_alpha_mode_max_enum } cgltf_alpha_mode; typedef enum cgltf_animation_path_type { cgltf_animation_path_type_invalid, cgltf_animation_path_type_translation, cgltf_animation_path_type_rotation, cgltf_animation_path_type_scale, cgltf_animation_path_type_weights, cgltf_animation_path_type_max_enum } cgltf_animation_path_type; typedef enum cgltf_interpolation_type { cgltf_interpolation_type_linear, cgltf_interpolation_type_step, cgltf_interpolation_type_cubic_spline, cgltf_interpolation_type_max_enum } cgltf_interpolation_type; typedef enum cgltf_camera_type { cgltf_camera_type_invalid, cgltf_camera_type_perspective, cgltf_camera_type_orthographic, cgltf_camera_type_max_enum } cgltf_camera_type; typedef enum cgltf_light_type { cgltf_light_type_invalid, cgltf_light_type_directional, cgltf_light_type_point, cgltf_light_type_spot, cgltf_light_type_max_enum } cgltf_light_type; typedef enum cgltf_data_free_method { cgltf_data_free_method_none, cgltf_data_free_method_file_release, cgltf_data_free_method_memory_free, cgltf_data_free_method_max_enum } cgltf_data_free_method; typedef struct cgltf_extras { cgltf_size start_offset; /* this field is deprecated and will be removed in the future; use data instead */ cgltf_size end_offset; /* this field is deprecated and will be removed in the future; use data instead */ char* data; } cgltf_extras; typedef struct cgltf_extension { char* name; char* data; } cgltf_extension; typedef struct cgltf_buffer { char* name; cgltf_size size; char* uri; void* data; /* loaded by cgltf_load_buffers */ cgltf_data_free_method data_free_method; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_buffer; typedef enum cgltf_meshopt_compression_mode { cgltf_meshopt_compression_mode_invalid, cgltf_meshopt_compression_mode_attributes, cgltf_meshopt_compression_mode_triangles, cgltf_meshopt_compression_mode_indices, cgltf_meshopt_compression_mode_max_enum } cgltf_meshopt_compression_mode; typedef enum cgltf_meshopt_compression_filter { cgltf_meshopt_compression_filter_none, cgltf_meshopt_compression_filter_octahedral, cgltf_meshopt_compression_filter_quaternion, cgltf_meshopt_compression_filter_exponential, cgltf_meshopt_compression_filter_color, cgltf_meshopt_compression_filter_max_enum } cgltf_meshopt_compression_filter; typedef struct cgltf_meshopt_compression { cgltf_buffer* buffer; cgltf_size offset; cgltf_size size; cgltf_size stride; cgltf_size count; cgltf_meshopt_compression_mode mode; cgltf_meshopt_compression_filter filter; cgltf_bool is_khr; } cgltf_meshopt_compression; typedef struct cgltf_buffer_view { char *name; cgltf_buffer* buffer; cgltf_size offset; cgltf_size size; cgltf_size stride; /* 0 == automatically determined by accessor */ cgltf_buffer_view_type type; void* data; /* overrides buffer->data if present, filled by extensions */ cgltf_bool has_meshopt_compression; cgltf_meshopt_compression meshopt_compression; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_buffer_view; typedef struct cgltf_accessor_sparse { cgltf_size count; cgltf_buffer_view* indices_buffer_view; cgltf_size indices_byte_offset; cgltf_component_type indices_component_type; cgltf_buffer_view* values_buffer_view; cgltf_size values_byte_offset; } cgltf_accessor_sparse; typedef struct cgltf_accessor { char* name; cgltf_component_type component_type; cgltf_bool normalized; cgltf_type type; cgltf_size offset; cgltf_size count; cgltf_size stride; cgltf_buffer_view* buffer_view; cgltf_bool has_min; cgltf_float min[16]; cgltf_bool has_max; cgltf_float max[16]; cgltf_bool is_sparse; cgltf_accessor_sparse sparse; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_accessor; typedef struct cgltf_attribute { char* name; cgltf_attribute_type type; cgltf_int index; cgltf_accessor* data; } cgltf_attribute; typedef struct cgltf_image { char* name; char* uri; cgltf_buffer_view* buffer_view; char* mime_type; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_image; typedef enum cgltf_filter_type { cgltf_filter_type_undefined = 0, cgltf_filter_type_nearest = 9728, cgltf_filter_type_linear = 9729, cgltf_filter_type_nearest_mipmap_nearest = 9984, cgltf_filter_type_linear_mipmap_nearest = 9985, cgltf_filter_type_nearest_mipmap_linear = 9986, cgltf_filter_type_linear_mipmap_linear = 9987 } cgltf_filter_type; typedef enum cgltf_wrap_mode { cgltf_wrap_mode_clamp_to_edge = 33071, cgltf_wrap_mode_mirrored_repeat = 33648, cgltf_wrap_mode_repeat = 10497 } cgltf_wrap_mode; typedef struct cgltf_sampler { char* name; cgltf_filter_type mag_filter; cgltf_filter_type min_filter; cgltf_wrap_mode wrap_s; cgltf_wrap_mode wrap_t; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_sampler; typedef struct cgltf_texture { char* name; cgltf_image* image; cgltf_sampler* sampler; cgltf_bool has_basisu; cgltf_image* basisu_image; cgltf_bool has_webp; cgltf_image* webp_image; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_texture; typedef struct cgltf_texture_transform { cgltf_float offset[2]; cgltf_float rotation; cgltf_float scale[2]; cgltf_bool has_texcoord; cgltf_int texcoord; } cgltf_texture_transform; typedef struct cgltf_texture_view { cgltf_texture* texture; cgltf_int texcoord; cgltf_float scale; /* equivalent to strength for occlusion_texture */ cgltf_bool has_transform; cgltf_texture_transform transform; } cgltf_texture_view; typedef struct cgltf_pbr_metallic_roughness { cgltf_texture_view base_color_texture; cgltf_texture_view metallic_roughness_texture; cgltf_float base_color_factor[4]; cgltf_float metallic_factor; cgltf_float roughness_factor; } cgltf_pbr_metallic_roughness; typedef struct cgltf_pbr_specular_glossiness { cgltf_texture_view diffuse_texture; cgltf_texture_view specular_glossiness_texture; cgltf_float diffuse_factor[4]; cgltf_float specular_factor[3]; cgltf_float glossiness_factor; } cgltf_pbr_specular_glossiness; typedef struct cgltf_clearcoat { cgltf_texture_view clearcoat_texture; cgltf_texture_view clearcoat_roughness_texture; cgltf_texture_view clearcoat_normal_texture; cgltf_float clearcoat_factor; cgltf_float clearcoat_roughness_factor; } cgltf_clearcoat; typedef struct cgltf_transmission { cgltf_texture_view transmission_texture; cgltf_float transmission_factor; } cgltf_transmission; typedef struct cgltf_ior { cgltf_float ior; } cgltf_ior; typedef struct cgltf_specular { cgltf_texture_view specular_texture; cgltf_texture_view specular_color_texture; cgltf_float specular_color_factor[3]; cgltf_float specular_factor; } cgltf_specular; typedef struct cgltf_volume { cgltf_texture_view thickness_texture; cgltf_float thickness_factor; cgltf_float attenuation_color[3]; cgltf_float attenuation_distance; } cgltf_volume; typedef struct cgltf_sheen { cgltf_texture_view sheen_color_texture; cgltf_float sheen_color_factor[3]; cgltf_texture_view sheen_roughness_texture; cgltf_float sheen_roughness_factor; } cgltf_sheen; typedef struct cgltf_emissive_strength { cgltf_float emissive_strength; } cgltf_emissive_strength; typedef struct cgltf_iridescence { cgltf_float iridescence_factor; cgltf_texture_view iridescence_texture; cgltf_float iridescence_ior; cgltf_float iridescence_thickness_min; cgltf_float iridescence_thickness_max; cgltf_texture_view iridescence_thickness_texture; } cgltf_iridescence; typedef struct cgltf_diffuse_transmission { cgltf_texture_view diffuse_transmission_texture; cgltf_float diffuse_transmission_factor; cgltf_float diffuse_transmission_color_factor[3]; cgltf_texture_view diffuse_transmission_color_texture; } cgltf_diffuse_transmission; typedef struct cgltf_anisotropy { cgltf_float anisotropy_strength; cgltf_float anisotropy_rotation; cgltf_texture_view anisotropy_texture; } cgltf_anisotropy; typedef struct cgltf_dispersion { cgltf_float dispersion; } cgltf_dispersion; typedef struct cgltf_material { char* name; cgltf_bool has_pbr_metallic_roughness; cgltf_bool has_pbr_specular_glossiness; cgltf_bool has_clearcoat; cgltf_bool has_transmission; cgltf_bool has_volume; cgltf_bool has_ior; cgltf_bool has_specular; cgltf_bool has_sheen; cgltf_bool has_emissive_strength; cgltf_bool has_iridescence; cgltf_bool has_diffuse_transmission; cgltf_bool has_anisotropy; cgltf_bool has_dispersion; cgltf_pbr_metallic_roughness pbr_metallic_roughness; cgltf_pbr_specular_glossiness pbr_specular_glossiness; cgltf_clearcoat clearcoat; cgltf_ior ior; cgltf_specular specular; cgltf_sheen sheen; cgltf_transmission transmission; cgltf_volume volume; cgltf_emissive_strength emissive_strength; cgltf_iridescence iridescence; cgltf_diffuse_transmission diffuse_transmission; cgltf_anisotropy anisotropy; cgltf_dispersion dispersion; cgltf_texture_view normal_texture; cgltf_texture_view occlusion_texture; cgltf_texture_view emissive_texture; cgltf_float emissive_factor[3]; cgltf_alpha_mode alpha_mode; cgltf_float alpha_cutoff; cgltf_bool double_sided; cgltf_bool unlit; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_material; typedef struct cgltf_material_mapping { cgltf_size variant; cgltf_material* material; cgltf_extras extras; } cgltf_material_mapping; typedef struct cgltf_morph_target { cgltf_attribute* attributes; cgltf_size attributes_count; } cgltf_morph_target; typedef struct cgltf_draco_mesh_compression { cgltf_buffer_view* buffer_view; cgltf_attribute* attributes; cgltf_size attributes_count; } cgltf_draco_mesh_compression; typedef struct cgltf_mesh_gpu_instancing { cgltf_attribute* attributes; cgltf_size attributes_count; } cgltf_mesh_gpu_instancing; typedef struct cgltf_primitive { cgltf_primitive_type type; cgltf_accessor* indices; cgltf_material* material; cgltf_attribute* attributes; cgltf_size attributes_count; cgltf_morph_target* targets; cgltf_size targets_count; cgltf_extras extras; cgltf_bool has_draco_mesh_compression; cgltf_draco_mesh_compression draco_mesh_compression; cgltf_material_mapping* mappings; cgltf_size mappings_count; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_primitive; typedef struct cgltf_mesh { char* name; cgltf_primitive* primitives; cgltf_size primitives_count; cgltf_float* weights; cgltf_size weights_count; char** target_names; cgltf_size target_names_count; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_mesh; typedef struct cgltf_node cgltf_node; typedef struct cgltf_skin { char* name; cgltf_node** joints; cgltf_size joints_count; cgltf_node* skeleton; cgltf_accessor* inverse_bind_matrices; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_skin; typedef struct cgltf_camera_perspective { cgltf_bool has_aspect_ratio; cgltf_float aspect_ratio; cgltf_float yfov; cgltf_bool has_zfar; cgltf_float zfar; cgltf_float znear; cgltf_extras extras; } cgltf_camera_perspective; typedef struct cgltf_camera_orthographic { cgltf_float xmag; cgltf_float ymag; cgltf_float zfar; cgltf_float znear; cgltf_extras extras; } cgltf_camera_orthographic; typedef struct cgltf_camera { char* name; cgltf_camera_type type; union { cgltf_camera_perspective perspective; cgltf_camera_orthographic orthographic; } data; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_camera; typedef struct cgltf_light { char* name; cgltf_float color[3]; cgltf_float intensity; cgltf_light_type type; cgltf_float range; cgltf_float spot_inner_cone_angle; cgltf_float spot_outer_cone_angle; cgltf_extras extras; } cgltf_light; struct cgltf_node { char* name; cgltf_node* parent; cgltf_node** children; cgltf_size children_count; cgltf_skin* skin; cgltf_mesh* mesh; cgltf_camera* camera; cgltf_light* light; cgltf_float* weights; cgltf_size weights_count; cgltf_bool has_translation; cgltf_bool has_rotation; cgltf_bool has_scale; cgltf_bool has_matrix; cgltf_float translation[3]; cgltf_float rotation[4]; cgltf_float scale[3]; cgltf_float matrix[16]; cgltf_extras extras; cgltf_bool has_mesh_gpu_instancing; cgltf_mesh_gpu_instancing mesh_gpu_instancing; cgltf_size extensions_count; cgltf_extension* extensions; }; typedef struct cgltf_scene { char* name; cgltf_node** nodes; cgltf_size nodes_count; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_scene; typedef struct cgltf_animation_sampler { cgltf_accessor* input; cgltf_accessor* output; cgltf_interpolation_type interpolation; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_animation_sampler; typedef struct cgltf_animation_channel { cgltf_animation_sampler* sampler; cgltf_node* target_node; cgltf_animation_path_type target_path; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_animation_channel; typedef struct cgltf_animation { char* name; cgltf_animation_sampler* samplers; cgltf_size samplers_count; cgltf_animation_channel* channels; cgltf_size channels_count; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_animation; typedef struct cgltf_material_variant { char* name; cgltf_extras extras; } cgltf_material_variant; typedef struct cgltf_asset { char* copyright; char* generator; char* version; char* min_version; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; } cgltf_asset; typedef struct cgltf_data { cgltf_file_type file_type; void* file_data; cgltf_size file_size; cgltf_asset asset; cgltf_mesh* meshes; cgltf_size meshes_count; cgltf_material* materials; cgltf_size materials_count; cgltf_accessor* accessors; cgltf_size accessors_count; cgltf_buffer_view* buffer_views; cgltf_size buffer_views_count; cgltf_buffer* buffers; cgltf_size buffers_count; cgltf_image* images; cgltf_size images_count; cgltf_texture* textures; cgltf_size textures_count; cgltf_sampler* samplers; cgltf_size samplers_count; cgltf_skin* skins; cgltf_size skins_count; cgltf_camera* cameras; cgltf_size cameras_count; cgltf_light* lights; cgltf_size lights_count; cgltf_node* nodes; cgltf_size nodes_count; cgltf_scene* scenes; cgltf_size scenes_count; cgltf_scene* scene; cgltf_animation* animations; cgltf_size animations_count; cgltf_material_variant* variants; cgltf_size variants_count; cgltf_extras extras; cgltf_size data_extensions_count; cgltf_extension* data_extensions; char** extensions_used; cgltf_size extensions_used_count; char** extensions_required; cgltf_size extensions_required_count; const char* json; cgltf_size json_size; const void* bin; cgltf_size bin_size; cgltf_memory_options memory; cgltf_file_options file; } cgltf_data; cgltf_result cgltf_parse( const cgltf_options* options, const void* data, cgltf_size size, cgltf_data** out_data); cgltf_result cgltf_parse_file( const cgltf_options* options, const char* path, cgltf_data** out_data); cgltf_result cgltf_load_buffers( const cgltf_options* options, cgltf_data* data, const char* gltf_path); cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data); cgltf_size cgltf_decode_string(char* string); cgltf_size cgltf_decode_uri(char* uri); cgltf_result cgltf_validate(cgltf_data* data); void cgltf_free(cgltf_data* data); void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix); void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix); const uint8_t* cgltf_buffer_view_data(const cgltf_buffer_view* view); const cgltf_accessor* cgltf_find_accessor(const cgltf_primitive* prim, cgltf_attribute_type type, cgltf_int index); cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size); cgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size); cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index); cgltf_size cgltf_num_components(cgltf_type type); cgltf_size cgltf_component_size(cgltf_component_type component_type); cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type); cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count); cgltf_size cgltf_accessor_unpack_indices(const cgltf_accessor* accessor, void* out, cgltf_size out_component_size, cgltf_size index_count); /* this function is deprecated and will be removed in the future; use cgltf_extras::data instead */ cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size); cgltf_size cgltf_mesh_index(const cgltf_data* data, const cgltf_mesh* object); cgltf_size cgltf_material_index(const cgltf_data* data, const cgltf_material* object); cgltf_size cgltf_accessor_index(const cgltf_data* data, const cgltf_accessor* object); cgltf_size cgltf_buffer_view_index(const cgltf_data* data, const cgltf_buffer_view* object); cgltf_size cgltf_buffer_index(const cgltf_data* data, const cgltf_buffer* object); cgltf_size cgltf_image_index(const cgltf_data* data, const cgltf_image* object); cgltf_size cgltf_texture_index(const cgltf_data* data, const cgltf_texture* object); cgltf_size cgltf_sampler_index(const cgltf_data* data, const cgltf_sampler* object); cgltf_size cgltf_skin_index(const cgltf_data* data, const cgltf_skin* object); cgltf_size cgltf_camera_index(const cgltf_data* data, const cgltf_camera* object); cgltf_size cgltf_light_index(const cgltf_data* data, const cgltf_light* object); cgltf_size cgltf_node_index(const cgltf_data* data, const cgltf_node* object); cgltf_size cgltf_scene_index(const cgltf_data* data, const cgltf_scene* object); cgltf_size cgltf_animation_index(const cgltf_data* data, const cgltf_animation* object); cgltf_size cgltf_animation_sampler_index(const cgltf_animation* animation, const cgltf_animation_sampler* object); cgltf_size cgltf_animation_channel_index(const cgltf_animation* animation, const cgltf_animation_channel* object); #ifdef __cplusplus } #endif #endif /* #ifndef CGLTF_H_INCLUDED__ */ /* * * Stop now, if you are only interested in the API. * Below, you find the implementation. * */ #if defined(__INTELLISENSE__) || defined(__JETBRAINS_IDE__) /* This makes MSVC/CLion intellisense work. */ #define CGLTF_IMPLEMENTATION #endif #ifdef CGLTF_IMPLEMENTATION #include /* For assert */ #include /* For strncpy */ #include /* For fopen */ #include /* For UINT_MAX etc */ #include /* For FLT_MAX */ #if !defined(CGLTF_MALLOC) || !defined(CGLTF_FREE) || !defined(CGLTF_ATOI) || !defined(CGLTF_ATOF) || !defined(CGLTF_ATOLL) #include /* For malloc, free, atoi, atof */ #endif /* JSMN_PARENT_LINKS is necessary to make parsing large structures linear in input size */ #define JSMN_PARENT_LINKS /* JSMN_STRICT is necessary to reject invalid JSON documents */ #define JSMN_STRICT /* * -- jsmn.h start -- * Source: https://github.com/zserge/jsmn * License: MIT */ typedef enum { JSMN_UNDEFINED = 0, JSMN_OBJECT = 1, JSMN_ARRAY = 2, JSMN_STRING = 3, JSMN_PRIMITIVE = 4 } jsmntype_t; enum jsmnerr { /* Not enough tokens were provided */ JSMN_ERROR_NOMEM = -1, /* Invalid character inside JSON string */ JSMN_ERROR_INVAL = -2, /* The string is not a full JSON packet, more bytes expected */ JSMN_ERROR_PART = -3 }; typedef struct { jsmntype_t type; ptrdiff_t start; ptrdiff_t end; int size; #ifdef JSMN_PARENT_LINKS int parent; #endif } jsmntok_t; typedef struct { size_t pos; /* offset in the JSON string */ unsigned int toknext; /* next token to allocate */ int toksuper; /* superior token node, e.g parent object or array */ } jsmn_parser; static void jsmn_init(jsmn_parser *parser); static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens); /* * -- jsmn.h end -- */ #ifndef CGLTF_CONSTS #define GlbHeaderSize 12 #define GlbChunkHeaderSize 8 static const uint32_t GlbVersion = 2; static const uint32_t GlbMagic = 0x46546C67; static const uint32_t GlbMagicJsonChunk = 0x4E4F534A; static const uint32_t GlbMagicBinChunk = 0x004E4942; #define CGLTF_CONSTS #endif #ifndef CGLTF_MALLOC #define CGLTF_MALLOC(size) malloc(size) #endif #ifndef CGLTF_FREE #define CGLTF_FREE(ptr) free(ptr) #endif #ifndef CGLTF_ATOI #define CGLTF_ATOI(str) atoi(str) #endif #ifndef CGLTF_ATOF #define CGLTF_ATOF(str) atof(str) #endif #ifndef CGLTF_ATOLL #define CGLTF_ATOLL(str) atoll(str) #endif #ifndef CGLTF_VALIDATE_ENABLE_ASSERTS #define CGLTF_VALIDATE_ENABLE_ASSERTS 0 #endif static void* cgltf_default_alloc(void* user, cgltf_size size) { (void)user; return CGLTF_MALLOC(size); } static void cgltf_default_free(void* user, void* ptr) { (void)user; CGLTF_FREE(ptr); } static void* cgltf_calloc(cgltf_options* options, size_t element_size, cgltf_size count) { if (SIZE_MAX / element_size < count) { return NULL; } void* result = options->memory.alloc_func(options->memory.user_data, element_size * count); if (!result) { return NULL; } memset(result, 0, element_size * count); return result; } static cgltf_result cgltf_default_file_read(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data) { (void)file_options; void* (*memory_alloc)(void*, cgltf_size) = memory_options->alloc_func ? memory_options->alloc_func : &cgltf_default_alloc; void (*memory_free)(void*, void*) = memory_options->free_func ? memory_options->free_func : &cgltf_default_free; FILE* file = fopen(path, "rb"); if (!file) { return cgltf_result_file_not_found; } cgltf_size file_size = size ? *size : 0; if (file_size == 0) { fseek(file, 0, SEEK_END); #ifdef _MSC_VER __int64 length = _ftelli64(file); #else long length = ftell(file); #endif if (length < 0) { fclose(file); return cgltf_result_io_error; } fseek(file, 0, SEEK_SET); file_size = (cgltf_size)length; } char* file_data = (char*)memory_alloc(memory_options->user_data, file_size); if (!file_data) { fclose(file); return cgltf_result_out_of_memory; } cgltf_size read_size = fread(file_data, 1, file_size, file); fclose(file); if (read_size != file_size) { memory_free(memory_options->user_data, file_data); return cgltf_result_io_error; } if (size) { *size = file_size; } if (data) { *data = file_data; } return cgltf_result_success; } static void cgltf_default_file_release(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data, cgltf_size size) { (void)file_options; (void)size; void (*memfree)(void*, void*) = memory_options->free_func ? memory_options->free_func : &cgltf_default_free; memfree(memory_options->user_data, data); } static cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data); cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_size size, cgltf_data** out_data) { if (size < GlbHeaderSize) { return cgltf_result_data_too_short; } if (options == NULL) { return cgltf_result_invalid_options; } cgltf_options fixed_options = *options; if (fixed_options.memory.alloc_func == NULL) { fixed_options.memory.alloc_func = &cgltf_default_alloc; } if (fixed_options.memory.free_func == NULL) { fixed_options.memory.free_func = &cgltf_default_free; } uint32_t tmp; // Magic memcpy(&tmp, data, 4); if (tmp != GlbMagic) { if (fixed_options.type == cgltf_file_type_invalid) { fixed_options.type = cgltf_file_type_gltf; } else if (fixed_options.type == cgltf_file_type_glb) { return cgltf_result_unknown_format; } } if (fixed_options.type == cgltf_file_type_gltf) { cgltf_result json_result = cgltf_parse_json(&fixed_options, (const uint8_t*)data, size, out_data); if (json_result != cgltf_result_success) { return json_result; } (*out_data)->file_type = cgltf_file_type_gltf; return cgltf_result_success; } const uint8_t* ptr = (const uint8_t*)data; // Version memcpy(&tmp, ptr + 4, 4); uint32_t version = tmp; if (version != GlbVersion) { return version < GlbVersion ? cgltf_result_legacy_gltf : cgltf_result_unknown_format; } // Total length memcpy(&tmp, ptr + 8, 4); if (tmp > size) { return cgltf_result_data_too_short; } const uint8_t* json_chunk = ptr + GlbHeaderSize; if (GlbHeaderSize + GlbChunkHeaderSize > size) { return cgltf_result_data_too_short; } // JSON chunk: length uint32_t json_length; memcpy(&json_length, json_chunk, 4); if (json_length > size - GlbHeaderSize - GlbChunkHeaderSize) { return cgltf_result_data_too_short; } // JSON chunk: magic memcpy(&tmp, json_chunk + 4, 4); if (tmp != GlbMagicJsonChunk) { return cgltf_result_unknown_format; } json_chunk += GlbChunkHeaderSize; const void* bin = NULL; cgltf_size bin_size = 0; if (GlbChunkHeaderSize <= size - GlbHeaderSize - GlbChunkHeaderSize - json_length) { // We can read another chunk const uint8_t* bin_chunk = json_chunk + json_length; // Bin chunk: length uint32_t bin_length; memcpy(&bin_length, bin_chunk, 4); if (bin_length > size - GlbHeaderSize - GlbChunkHeaderSize - json_length - GlbChunkHeaderSize) { return cgltf_result_data_too_short; } // Bin chunk: magic memcpy(&tmp, bin_chunk + 4, 4); if (tmp != GlbMagicBinChunk) { return cgltf_result_unknown_format; } bin_chunk += GlbChunkHeaderSize; bin = bin_chunk; bin_size = bin_length; } cgltf_result json_result = cgltf_parse_json(&fixed_options, json_chunk, json_length, out_data); if (json_result != cgltf_result_success) { return json_result; } (*out_data)->file_type = cgltf_file_type_glb; (*out_data)->bin = bin; (*out_data)->bin_size = bin_size; return cgltf_result_success; } cgltf_result cgltf_parse_file(const cgltf_options* options, const char* path, cgltf_data** out_data) { if (options == NULL) { return cgltf_result_invalid_options; } cgltf_result (*file_read)(const struct cgltf_memory_options*, const struct cgltf_file_options*, const char*, cgltf_size*, void**) = options->file.read ? options->file.read : &cgltf_default_file_read; void (*file_release)(const struct cgltf_memory_options*, const struct cgltf_file_options*, void* data, cgltf_size size) = options->file.release ? options->file.release : cgltf_default_file_release; void* file_data = NULL; cgltf_size file_size = 0; cgltf_result result = file_read(&options->memory, &options->file, path, &file_size, &file_data); if (result != cgltf_result_success) { return result; } result = cgltf_parse(options, file_data, file_size, out_data); if (result != cgltf_result_success) { file_release(&options->memory, &options->file, file_data, file_size); return result; } (*out_data)->file_data = file_data; (*out_data)->file_size = file_size; return cgltf_result_success; } static void cgltf_combine_paths(char* path, const char* base, const char* uri) { const char* s0 = strrchr(base, '/'); const char* s1 = strrchr(base, '\\'); const char* slash = s0 ? (s1 && s1 > s0 ? s1 : s0) : s1; if (slash) { size_t prefix = slash - base + 1; strncpy(path, base, prefix); strcpy(path + prefix, uri); } else { strcpy(path, uri); } } static cgltf_result cgltf_load_buffer_file(const cgltf_options* options, cgltf_size size, const char* uri, const char* gltf_path, void** out_data) { void* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc_func ? options->memory.alloc_func : &cgltf_default_alloc; void (*memory_free)(void*, void*) = options->memory.free_func ? options->memory.free_func : &cgltf_default_free; cgltf_result (*file_read)(const struct cgltf_memory_options*, const struct cgltf_file_options*, const char*, cgltf_size*, void**) = options->file.read ? options->file.read : &cgltf_default_file_read; char* path = (char*)memory_alloc(options->memory.user_data, strlen(uri) + strlen(gltf_path) + 1); if (!path) { return cgltf_result_out_of_memory; } cgltf_combine_paths(path, gltf_path, uri); // after combining, the tail of the resulting path is a uri; decode_uri converts it into path cgltf_decode_uri(path + strlen(path) - strlen(uri)); void* file_data = NULL; cgltf_result result = file_read(&options->memory, &options->file, path, &size, &file_data); memory_free(options->memory.user_data, path); *out_data = (result == cgltf_result_success) ? file_data : NULL; return result; } cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data) { void* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc_func ? options->memory.alloc_func : &cgltf_default_alloc; void (*memory_free)(void*, void*) = options->memory.free_func ? options->memory.free_func : &cgltf_default_free; unsigned char* data = (unsigned char*)memory_alloc(options->memory.user_data, size); if (!data) { return cgltf_result_out_of_memory; } unsigned int buffer = 0; unsigned int buffer_bits = 0; for (cgltf_size i = 0; i < size; ++i) { while (buffer_bits < 8) { char ch = *base64++; int index = (unsigned)(ch - 'A') < 26 ? (ch - 'A') : (unsigned)(ch - 'a') < 26 ? (ch - 'a') + 26 : (unsigned)(ch - '0') < 10 ? (ch - '0') + 52 : ch == '+' ? 62 : ch == '/' ? 63 : -1; if (index < 0) { memory_free(options->memory.user_data, data); return cgltf_result_io_error; } buffer = (buffer << 6) | index; buffer_bits += 6; } data[i] = (unsigned char)(buffer >> (buffer_bits - 8)); buffer_bits -= 8; } *out_data = data; return cgltf_result_success; } static int cgltf_unhex(char ch) { return (unsigned)(ch - '0') < 10 ? (ch - '0') : (unsigned)(ch - 'A') < 6 ? (ch - 'A') + 10 : (unsigned)(ch - 'a') < 6 ? (ch - 'a') + 10 : -1; } cgltf_size cgltf_decode_string(char* string) { char* read = string + strcspn(string, "\\"); if (*read == 0) { return read - string; } char* write = string; char* last = string; for (;;) { // Copy characters since last escaped sequence cgltf_size written = read - last; memmove(write, last, written); write += written; if (*read++ == 0) { break; } // jsmn already checked that all escape sequences are valid switch (*read++) { case '\"': *write++ = '\"'; break; case '/': *write++ = '/'; break; case '\\': *write++ = '\\'; break; case 'b': *write++ = '\b'; break; case 'f': *write++ = '\f'; break; case 'r': *write++ = '\r'; break; case 'n': *write++ = '\n'; break; case 't': *write++ = '\t'; break; case 'u': { // UCS-2 codepoint \uXXXX to UTF-8 int character = 0; for (cgltf_size i = 0; i < 4; ++i) { character = (character << 4) + cgltf_unhex(*read++); } if (character <= 0x7F) { *write++ = character & 0xFF; } else if (character <= 0x7FF) { *write++ = 0xC0 | ((character >> 6) & 0xFF); *write++ = 0x80 | (character & 0x3F); } else { *write++ = 0xE0 | ((character >> 12) & 0xFF); *write++ = 0x80 | ((character >> 6) & 0x3F); *write++ = 0x80 | (character & 0x3F); } break; } default: break; } last = read; read += strcspn(read, "\\"); } *write = 0; return write - string; } cgltf_size cgltf_decode_uri(char* uri) { char* write = uri; char* i = uri; while (*i) { if (*i == '%') { int ch1 = cgltf_unhex(i[1]); if (ch1 >= 0) { int ch2 = cgltf_unhex(i[2]); if (ch2 >= 0) { *write++ = (char)(ch1 * 16 + ch2); i += 3; continue; } } } *write++ = *i++; } *write = 0; return write - uri; } cgltf_result cgltf_load_buffers(const cgltf_options* options, cgltf_data* data, const char* gltf_path) { if (options == NULL) { return cgltf_result_invalid_options; } if (data->buffers_count && data->buffers[0].data == NULL && data->buffers[0].uri == NULL && data->bin) { if (data->bin_size < data->buffers[0].size) { return cgltf_result_data_too_short; } data->buffers[0].data = (void*)data->bin; data->buffers[0].data_free_method = cgltf_data_free_method_none; } for (cgltf_size i = 0; i < data->buffers_count; ++i) { if (data->buffers[i].data) { continue; } const char* uri = data->buffers[i].uri; if (uri == NULL) { continue; } if (strncmp(uri, "data:", 5) == 0) { const char* comma = strchr(uri, ','); if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0) { cgltf_result res = cgltf_load_buffer_base64(options, data->buffers[i].size, comma + 1, &data->buffers[i].data); data->buffers[i].data_free_method = cgltf_data_free_method_memory_free; if (res != cgltf_result_success) { return res; } } else { return cgltf_result_unknown_format; } } else if (strstr(uri, "://") == NULL && gltf_path) { cgltf_result res = cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data); data->buffers[i].data_free_method = cgltf_data_free_method_file_release; if (res != cgltf_result_success) { return res; } } else { return cgltf_result_unknown_format; } } return cgltf_result_success; } static cgltf_size cgltf_calc_index_bound(cgltf_buffer_view* buffer_view, cgltf_size offset, cgltf_component_type component_type, cgltf_size count) { char* data = (char*)buffer_view->buffer->data + offset + buffer_view->offset; cgltf_size bound = 0; switch (component_type) { case cgltf_component_type_r_8u: for (size_t i = 0; i < count; ++i) { cgltf_size v = ((unsigned char*)data)[i]; bound = bound > v ? bound : v; } break; case cgltf_component_type_r_16u: for (size_t i = 0; i < count; ++i) { cgltf_size v = ((unsigned short*)data)[i]; bound = bound > v ? bound : v; } break; case cgltf_component_type_r_32u: for (size_t i = 0; i < count; ++i) { cgltf_size v = ((unsigned int*)data)[i]; bound = bound > v ? bound : v; } break; default: ; } return bound; } #if CGLTF_VALIDATE_ENABLE_ASSERTS #define CGLTF_ASSERT_IF(cond, result) assert(!(cond)); if (cond) return result; #else #define CGLTF_ASSERT_IF(cond, result) if (cond) return result; #endif cgltf_result cgltf_validate(cgltf_data* data) { for (cgltf_size i = 0; i < data->accessors_count; ++i) { cgltf_accessor* accessor = &data->accessors[i]; CGLTF_ASSERT_IF(data->accessors[i].component_type == cgltf_component_type_invalid, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(data->accessors[i].type == cgltf_type_invalid, cgltf_result_invalid_gltf); cgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type); if (accessor->buffer_view) { cgltf_size req_size = accessor->offset + accessor->stride * (accessor->count - 1) + element_size; CGLTF_ASSERT_IF(accessor->buffer_view->size < req_size, cgltf_result_data_too_short); } if (accessor->is_sparse) { cgltf_accessor_sparse* sparse = &accessor->sparse; cgltf_size indices_component_size = cgltf_component_size(sparse->indices_component_type); cgltf_size indices_req_size = sparse->indices_byte_offset + indices_component_size * sparse->count; cgltf_size values_req_size = sparse->values_byte_offset + element_size * sparse->count; CGLTF_ASSERT_IF(sparse->indices_buffer_view->size < indices_req_size || sparse->values_buffer_view->size < values_req_size, cgltf_result_data_too_short); CGLTF_ASSERT_IF(sparse->indices_component_type != cgltf_component_type_r_8u && sparse->indices_component_type != cgltf_component_type_r_16u && sparse->indices_component_type != cgltf_component_type_r_32u, cgltf_result_invalid_gltf); if (sparse->indices_buffer_view->buffer->data) { cgltf_size index_bound = cgltf_calc_index_bound(sparse->indices_buffer_view, sparse->indices_byte_offset, sparse->indices_component_type, sparse->count); CGLTF_ASSERT_IF(index_bound >= accessor->count, cgltf_result_data_too_short); } } } for (cgltf_size i = 0; i < data->buffer_views_count; ++i) { cgltf_size req_size = data->buffer_views[i].offset + data->buffer_views[i].size; CGLTF_ASSERT_IF(data->buffer_views[i].buffer && data->buffer_views[i].buffer->size < req_size, cgltf_result_data_too_short); if (data->buffer_views[i].has_meshopt_compression) { cgltf_meshopt_compression* mc = &data->buffer_views[i].meshopt_compression; CGLTF_ASSERT_IF(mc->buffer == NULL || mc->buffer->size < mc->offset + mc->size, cgltf_result_data_too_short); CGLTF_ASSERT_IF(data->buffer_views[i].stride && mc->stride != data->buffer_views[i].stride, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(data->buffer_views[i].size != mc->stride * mc->count, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_invalid, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_attributes && !(mc->stride % 4 == 0 && mc->stride <= 256), cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_triangles && mc->count % 3 != 0, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF((mc->mode == cgltf_meshopt_compression_mode_triangles || mc->mode == cgltf_meshopt_compression_mode_indices) && mc->stride != 2 && mc->stride != 4, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF((mc->mode == cgltf_meshopt_compression_mode_triangles || mc->mode == cgltf_meshopt_compression_mode_indices) && mc->filter != cgltf_meshopt_compression_filter_none, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_octahedral && mc->stride != 4 && mc->stride != 8, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_quaternion && mc->stride != 8, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_color && mc->stride != 4 && mc->stride != 8, cgltf_result_invalid_gltf); } } for (cgltf_size i = 0; i < data->meshes_count; ++i) { if (data->meshes[i].weights) { CGLTF_ASSERT_IF(data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].weights_count, cgltf_result_invalid_gltf); } if (data->meshes[i].target_names) { CGLTF_ASSERT_IF(data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].target_names_count, cgltf_result_invalid_gltf); } for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) { CGLTF_ASSERT_IF(data->meshes[i].primitives[j].type == cgltf_primitive_type_invalid, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(data->meshes[i].primitives[j].targets_count != data->meshes[i].primitives[0].targets_count, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(data->meshes[i].primitives[j].attributes_count == 0, cgltf_result_invalid_gltf); cgltf_accessor* first = data->meshes[i].primitives[j].attributes[0].data; CGLTF_ASSERT_IF(first->count == 0, cgltf_result_invalid_gltf); for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) { CGLTF_ASSERT_IF(data->meshes[i].primitives[j].attributes[k].data->count != first->count, cgltf_result_invalid_gltf); } for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) { for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) { CGLTF_ASSERT_IF(data->meshes[i].primitives[j].targets[k].attributes[m].data->count != first->count, cgltf_result_invalid_gltf); } } cgltf_accessor* indices = data->meshes[i].primitives[j].indices; CGLTF_ASSERT_IF(indices && indices->component_type != cgltf_component_type_r_8u && indices->component_type != cgltf_component_type_r_16u && indices->component_type != cgltf_component_type_r_32u, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(indices && indices->type != cgltf_type_scalar, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(indices && indices->stride != cgltf_component_size(indices->component_type), cgltf_result_invalid_gltf); if (indices && indices->buffer_view && indices->buffer_view->buffer->data) { cgltf_size index_bound = cgltf_calc_index_bound(indices->buffer_view, indices->offset, indices->component_type, indices->count); CGLTF_ASSERT_IF(index_bound >= first->count, cgltf_result_data_too_short); } for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k) { CGLTF_ASSERT_IF(data->meshes[i].primitives[j].mappings[k].variant >= data->variants_count, cgltf_result_invalid_gltf); } } } for (cgltf_size i = 0; i < data->nodes_count; ++i) { if (data->nodes[i].weights && data->nodes[i].mesh) { CGLTF_ASSERT_IF(data->nodes[i].mesh->primitives_count && data->nodes[i].mesh->primitives[0].targets_count != data->nodes[i].weights_count, cgltf_result_invalid_gltf); } if (data->nodes[i].has_mesh_gpu_instancing) { CGLTF_ASSERT_IF(data->nodes[i].mesh == NULL, cgltf_result_invalid_gltf); CGLTF_ASSERT_IF(data->nodes[i].mesh_gpu_instancing.attributes_count == 0, cgltf_result_invalid_gltf); cgltf_accessor* first = data->nodes[i].mesh_gpu_instancing.attributes[0].data; for (cgltf_size k = 0; k < data->nodes[i].mesh_gpu_instancing.attributes_count; ++k) { CGLTF_ASSERT_IF(data->nodes[i].mesh_gpu_instancing.attributes[k].data->count != first->count, cgltf_result_invalid_gltf); } } } for (cgltf_size i = 0; i < data->nodes_count; ++i) { cgltf_node* p1 = data->nodes[i].parent; cgltf_node* p2 = p1 ? p1->parent : NULL; while (p1 && p2) { CGLTF_ASSERT_IF(p1 == p2, cgltf_result_invalid_gltf); p1 = p1->parent; p2 = p2->parent ? p2->parent->parent : NULL; } } for (cgltf_size i = 0; i < data->scenes_count; ++i) { for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j) { CGLTF_ASSERT_IF(data->scenes[i].nodes[j]->parent, cgltf_result_invalid_gltf); } } for (cgltf_size i = 0; i < data->animations_count; ++i) { for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) { cgltf_animation_channel* channel = &data->animations[i].channels[j]; if (!channel->target_node) { continue; } cgltf_size components = 1; if (channel->target_path == cgltf_animation_path_type_weights) { CGLTF_ASSERT_IF(!channel->target_node->mesh || !channel->target_node->mesh->primitives_count, cgltf_result_invalid_gltf); components = channel->target_node->mesh->primitives[0].targets_count; } cgltf_size values = channel->sampler->interpolation == cgltf_interpolation_type_cubic_spline ? 3 : 1; CGLTF_ASSERT_IF(channel->sampler->input->count * components * values != channel->sampler->output->count, cgltf_result_invalid_gltf); } } for (cgltf_size i = 0; i < data->variants_count; ++i) { CGLTF_ASSERT_IF(!data->variants[i].name, cgltf_result_invalid_gltf); } return cgltf_result_success; } cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size) { cgltf_size json_size = extras->end_offset - extras->start_offset; if (!dest) { if (dest_size) { *dest_size = json_size + 1; return cgltf_result_success; } return cgltf_result_invalid_options; } if (*dest_size + 1 < json_size) { strncpy(dest, data->json + extras->start_offset, *dest_size - 1); dest[*dest_size - 1] = 0; } else { strncpy(dest, data->json + extras->start_offset, json_size); dest[json_size] = 0; } return cgltf_result_success; } static void cgltf_free_extras(cgltf_data* data, cgltf_extras* extras) { data->memory.free_func(data->memory.user_data, extras->data); } static void cgltf_free_extensions(cgltf_data* data, cgltf_extension* extensions, cgltf_size extensions_count) { for (cgltf_size i = 0; i < extensions_count; ++i) { data->memory.free_func(data->memory.user_data, extensions[i].name); data->memory.free_func(data->memory.user_data, extensions[i].data); } data->memory.free_func(data->memory.user_data, extensions); } void cgltf_free(cgltf_data* data) { if (!data) { return; } void (*file_release)(const struct cgltf_memory_options*, const struct cgltf_file_options*, void* data, cgltf_size size) = data->file.release ? data->file.release : cgltf_default_file_release; data->memory.free_func(data->memory.user_data, data->asset.copyright); data->memory.free_func(data->memory.user_data, data->asset.generator); data->memory.free_func(data->memory.user_data, data->asset.version); data->memory.free_func(data->memory.user_data, data->asset.min_version); cgltf_free_extensions(data, data->asset.extensions, data->asset.extensions_count); cgltf_free_extras(data, &data->asset.extras); for (cgltf_size i = 0; i < data->accessors_count; ++i) { data->memory.free_func(data->memory.user_data, data->accessors[i].name); cgltf_free_extensions(data, data->accessors[i].extensions, data->accessors[i].extensions_count); cgltf_free_extras(data, &data->accessors[i].extras); } data->memory.free_func(data->memory.user_data, data->accessors); for (cgltf_size i = 0; i < data->buffer_views_count; ++i) { data->memory.free_func(data->memory.user_data, data->buffer_views[i].name); data->memory.free_func(data->memory.user_data, data->buffer_views[i].data); cgltf_free_extensions(data, data->buffer_views[i].extensions, data->buffer_views[i].extensions_count); cgltf_free_extras(data, &data->buffer_views[i].extras); } data->memory.free_func(data->memory.user_data, data->buffer_views); for (cgltf_size i = 0; i < data->buffers_count; ++i) { data->memory.free_func(data->memory.user_data, data->buffers[i].name); if (data->buffers[i].data_free_method == cgltf_data_free_method_file_release) { file_release(&data->memory, &data->file, data->buffers[i].data, data->buffers[i].size); } else if (data->buffers[i].data_free_method == cgltf_data_free_method_memory_free) { data->memory.free_func(data->memory.user_data, data->buffers[i].data); } data->memory.free_func(data->memory.user_data, data->buffers[i].uri); cgltf_free_extensions(data, data->buffers[i].extensions, data->buffers[i].extensions_count); cgltf_free_extras(data, &data->buffers[i].extras); } data->memory.free_func(data->memory.user_data, data->buffers); for (cgltf_size i = 0; i < data->meshes_count; ++i) { data->memory.free_func(data->memory.user_data, data->meshes[i].name); for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) { for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) { data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].attributes[k].name); } data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].attributes); for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) { for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) { data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].targets[k].attributes[m].name); } data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].targets[k].attributes); } data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].targets); if (data->meshes[i].primitives[j].has_draco_mesh_compression) { for (cgltf_size k = 0; k < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++k) { data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes[k].name); } data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes); } for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k) { cgltf_free_extras(data, &data->meshes[i].primitives[j].mappings[k].extras); } data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].mappings); cgltf_free_extensions(data, data->meshes[i].primitives[j].extensions, data->meshes[i].primitives[j].extensions_count); cgltf_free_extras(data, &data->meshes[i].primitives[j].extras); } data->memory.free_func(data->memory.user_data, data->meshes[i].primitives); data->memory.free_func(data->memory.user_data, data->meshes[i].weights); for (cgltf_size j = 0; j < data->meshes[i].target_names_count; ++j) { data->memory.free_func(data->memory.user_data, data->meshes[i].target_names[j]); } cgltf_free_extensions(data, data->meshes[i].extensions, data->meshes[i].extensions_count); cgltf_free_extras(data, &data->meshes[i].extras); data->memory.free_func(data->memory.user_data, data->meshes[i].target_names); } data->memory.free_func(data->memory.user_data, data->meshes); for (cgltf_size i = 0; i < data->materials_count; ++i) { data->memory.free_func(data->memory.user_data, data->materials[i].name); cgltf_free_extensions(data, data->materials[i].extensions, data->materials[i].extensions_count); cgltf_free_extras(data, &data->materials[i].extras); } data->memory.free_func(data->memory.user_data, data->materials); for (cgltf_size i = 0; i < data->images_count; ++i) { data->memory.free_func(data->memory.user_data, data->images[i].name); data->memory.free_func(data->memory.user_data, data->images[i].uri); data->memory.free_func(data->memory.user_data, data->images[i].mime_type); cgltf_free_extensions(data, data->images[i].extensions, data->images[i].extensions_count); cgltf_free_extras(data, &data->images[i].extras); } data->memory.free_func(data->memory.user_data, data->images); for (cgltf_size i = 0; i < data->textures_count; ++i) { data->memory.free_func(data->memory.user_data, data->textures[i].name); cgltf_free_extensions(data, data->textures[i].extensions, data->textures[i].extensions_count); cgltf_free_extras(data, &data->textures[i].extras); } data->memory.free_func(data->memory.user_data, data->textures); for (cgltf_size i = 0; i < data->samplers_count; ++i) { data->memory.free_func(data->memory.user_data, data->samplers[i].name); cgltf_free_extensions(data, data->samplers[i].extensions, data->samplers[i].extensions_count); cgltf_free_extras(data, &data->samplers[i].extras); } data->memory.free_func(data->memory.user_data, data->samplers); for (cgltf_size i = 0; i < data->skins_count; ++i) { data->memory.free_func(data->memory.user_data, data->skins[i].name); data->memory.free_func(data->memory.user_data, data->skins[i].joints); cgltf_free_extensions(data, data->skins[i].extensions, data->skins[i].extensions_count); cgltf_free_extras(data, &data->skins[i].extras); } data->memory.free_func(data->memory.user_data, data->skins); for (cgltf_size i = 0; i < data->cameras_count; ++i) { data->memory.free_func(data->memory.user_data, data->cameras[i].name); if (data->cameras[i].type == cgltf_camera_type_perspective) { cgltf_free_extras(data, &data->cameras[i].data.perspective.extras); } else if (data->cameras[i].type == cgltf_camera_type_orthographic) { cgltf_free_extras(data, &data->cameras[i].data.orthographic.extras); } cgltf_free_extensions(data, data->cameras[i].extensions, data->cameras[i].extensions_count); cgltf_free_extras(data, &data->cameras[i].extras); } data->memory.free_func(data->memory.user_data, data->cameras); for (cgltf_size i = 0; i < data->lights_count; ++i) { data->memory.free_func(data->memory.user_data, data->lights[i].name); cgltf_free_extras(data, &data->lights[i].extras); } data->memory.free_func(data->memory.user_data, data->lights); for (cgltf_size i = 0; i < data->nodes_count; ++i) { data->memory.free_func(data->memory.user_data, data->nodes[i].name); data->memory.free_func(data->memory.user_data, data->nodes[i].children); data->memory.free_func(data->memory.user_data, data->nodes[i].weights); if (data->nodes[i].has_mesh_gpu_instancing) { for (cgltf_size j = 0; j < data->nodes[i].mesh_gpu_instancing.attributes_count; ++j) { data->memory.free_func(data->memory.user_data, data->nodes[i].mesh_gpu_instancing.attributes[j].name); } data->memory.free_func(data->memory.user_data, data->nodes[i].mesh_gpu_instancing.attributes); } cgltf_free_extensions(data, data->nodes[i].extensions, data->nodes[i].extensions_count); cgltf_free_extras(data, &data->nodes[i].extras); } data->memory.free_func(data->memory.user_data, data->nodes); for (cgltf_size i = 0; i < data->scenes_count; ++i) { data->memory.free_func(data->memory.user_data, data->scenes[i].name); data->memory.free_func(data->memory.user_data, data->scenes[i].nodes); cgltf_free_extensions(data, data->scenes[i].extensions, data->scenes[i].extensions_count); cgltf_free_extras(data, &data->scenes[i].extras); } data->memory.free_func(data->memory.user_data, data->scenes); for (cgltf_size i = 0; i < data->animations_count; ++i) { data->memory.free_func(data->memory.user_data, data->animations[i].name); for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j) { cgltf_free_extensions(data, data->animations[i].samplers[j].extensions, data->animations[i].samplers[j].extensions_count); cgltf_free_extras(data, &data->animations[i].samplers[j].extras); } data->memory.free_func(data->memory.user_data, data->animations[i].samplers); for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) { cgltf_free_extensions(data, data->animations[i].channels[j].extensions, data->animations[i].channels[j].extensions_count); cgltf_free_extras(data, &data->animations[i].channels[j].extras); } data->memory.free_func(data->memory.user_data, data->animations[i].channels); cgltf_free_extensions(data, data->animations[i].extensions, data->animations[i].extensions_count); cgltf_free_extras(data, &data->animations[i].extras); } data->memory.free_func(data->memory.user_data, data->animations); for (cgltf_size i = 0; i < data->variants_count; ++i) { data->memory.free_func(data->memory.user_data, data->variants[i].name); cgltf_free_extras(data, &data->variants[i].extras); } data->memory.free_func(data->memory.user_data, data->variants); cgltf_free_extensions(data, data->data_extensions, data->data_extensions_count); cgltf_free_extras(data, &data->extras); for (cgltf_size i = 0; i < data->extensions_used_count; ++i) { data->memory.free_func(data->memory.user_data, data->extensions_used[i]); } data->memory.free_func(data->memory.user_data, data->extensions_used); for (cgltf_size i = 0; i < data->extensions_required_count; ++i) { data->memory.free_func(data->memory.user_data, data->extensions_required[i]); } data->memory.free_func(data->memory.user_data, data->extensions_required); file_release(&data->memory, &data->file, data->file_data, data->file_size); data->memory.free_func(data->memory.user_data, data); } void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix) { cgltf_float* lm = out_matrix; if (node->has_matrix) { memcpy(lm, node->matrix, sizeof(float) * 16); } else { float tx = node->translation[0]; float ty = node->translation[1]; float tz = node->translation[2]; float qx = node->rotation[0]; float qy = node->rotation[1]; float qz = node->rotation[2]; float qw = node->rotation[3]; float sx = node->scale[0]; float sy = node->scale[1]; float sz = node->scale[2]; lm[0] = (1 - 2 * qy*qy - 2 * qz*qz) * sx; lm[1] = (2 * qx*qy + 2 * qz*qw) * sx; lm[2] = (2 * qx*qz - 2 * qy*qw) * sx; lm[3] = 0.f; lm[4] = (2 * qx*qy - 2 * qz*qw) * sy; lm[5] = (1 - 2 * qx*qx - 2 * qz*qz) * sy; lm[6] = (2 * qy*qz + 2 * qx*qw) * sy; lm[7] = 0.f; lm[8] = (2 * qx*qz + 2 * qy*qw) * sz; lm[9] = (2 * qy*qz - 2 * qx*qw) * sz; lm[10] = (1 - 2 * qx*qx - 2 * qy*qy) * sz; lm[11] = 0.f; lm[12] = tx; lm[13] = ty; lm[14] = tz; lm[15] = 1.f; } } void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix) { cgltf_float* lm = out_matrix; cgltf_node_transform_local(node, lm); const cgltf_node* parent = node->parent; while (parent) { float pm[16]; cgltf_node_transform_local(parent, pm); for (int i = 0; i < 4; ++i) { float l0 = lm[i * 4 + 0]; float l1 = lm[i * 4 + 1]; float l2 = lm[i * 4 + 2]; float r0 = l0 * pm[0] + l1 * pm[4] + l2 * pm[8]; float r1 = l0 * pm[1] + l1 * pm[5] + l2 * pm[9]; float r2 = l0 * pm[2] + l1 * pm[6] + l2 * pm[10]; lm[i * 4 + 0] = r0; lm[i * 4 + 1] = r1; lm[i * 4 + 2] = r2; } lm[12] += pm[12]; lm[13] += pm[13]; lm[14] += pm[14]; parent = parent->parent; } } static cgltf_ssize cgltf_component_read_integer(const void* in, cgltf_component_type component_type) { switch (component_type) { case cgltf_component_type_r_16: return *((const int16_t*) in); case cgltf_component_type_r_16u: return *((const uint16_t*) in); case cgltf_component_type_r_32u: return *((const uint32_t*) in); case cgltf_component_type_r_8: return *((const int8_t*) in); case cgltf_component_type_r_8u: return *((const uint8_t*) in); default: return 0; } } static cgltf_size cgltf_component_read_index(const void* in, cgltf_component_type component_type) { switch (component_type) { case cgltf_component_type_r_16u: return *((const uint16_t*) in); case cgltf_component_type_r_32u: return *((const uint32_t*) in); case cgltf_component_type_r_8u: return *((const uint8_t*) in); default: return 0; } } static cgltf_float cgltf_component_read_float(const void* in, cgltf_component_type component_type, cgltf_bool normalized) { if (component_type == cgltf_component_type_r_32f) { return *((const float*) in); } if (normalized) { switch (component_type) { // note: glTF spec doesn't currently define normalized conversions for 32-bit integers case cgltf_component_type_r_16: return *((const int16_t*) in) / (cgltf_float)32767; case cgltf_component_type_r_16u: return *((const uint16_t*) in) / (cgltf_float)65535; case cgltf_component_type_r_8: return *((const int8_t*) in) / (cgltf_float)127; case cgltf_component_type_r_8u: return *((const uint8_t*) in) / (cgltf_float)255; default: return 0; } } return (cgltf_float)cgltf_component_read_integer(in, component_type); } static cgltf_bool cgltf_element_read_float(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_bool normalized, cgltf_float* out, cgltf_size element_size) { cgltf_size num_components = cgltf_num_components(type); if (element_size < num_components) { return 0; } // There are three special cases for component extraction, see #data-alignment in the 2.0 spec. cgltf_size component_size = cgltf_component_size(component_type); if (type == cgltf_type_mat2 && component_size == 1) { out[0] = cgltf_component_read_float(element, component_type, normalized); out[1] = cgltf_component_read_float(element + 1, component_type, normalized); out[2] = cgltf_component_read_float(element + 4, component_type, normalized); out[3] = cgltf_component_read_float(element + 5, component_type, normalized); return 1; } if (type == cgltf_type_mat3 && component_size == 1) { out[0] = cgltf_component_read_float(element, component_type, normalized); out[1] = cgltf_component_read_float(element + 1, component_type, normalized); out[2] = cgltf_component_read_float(element + 2, component_type, normalized); out[3] = cgltf_component_read_float(element + 4, component_type, normalized); out[4] = cgltf_component_read_float(element + 5, component_type, normalized); out[5] = cgltf_component_read_float(element + 6, component_type, normalized); out[6] = cgltf_component_read_float(element + 8, component_type, normalized); out[7] = cgltf_component_read_float(element + 9, component_type, normalized); out[8] = cgltf_component_read_float(element + 10, component_type, normalized); return 1; } if (type == cgltf_type_mat3 && component_size == 2) { out[0] = cgltf_component_read_float(element, component_type, normalized); out[1] = cgltf_component_read_float(element + 2, component_type, normalized); out[2] = cgltf_component_read_float(element + 4, component_type, normalized); out[3] = cgltf_component_read_float(element + 8, component_type, normalized); out[4] = cgltf_component_read_float(element + 10, component_type, normalized); out[5] = cgltf_component_read_float(element + 12, component_type, normalized); out[6] = cgltf_component_read_float(element + 16, component_type, normalized); out[7] = cgltf_component_read_float(element + 18, component_type, normalized); out[8] = cgltf_component_read_float(element + 20, component_type, normalized); return 1; } for (cgltf_size i = 0; i < num_components; ++i) { out[i] = cgltf_component_read_float(element + component_size * i, component_type, normalized); } return 1; } const uint8_t* cgltf_buffer_view_data(const cgltf_buffer_view* view) { if (view->data) return (const uint8_t*)view->data; if (!view->buffer->data) return NULL; const uint8_t* result = (const uint8_t*)view->buffer->data; result += view->offset; return result; } const cgltf_accessor* cgltf_find_accessor(const cgltf_primitive* prim, cgltf_attribute_type type, cgltf_int index) { for (cgltf_size i = 0; i < prim->attributes_count; ++i) { const cgltf_attribute* attr = &prim->attributes[i]; if (attr->type == type && attr->index == index) return attr->data; } return NULL; } static const uint8_t* cgltf_find_sparse_index(const cgltf_accessor* accessor, cgltf_size needle) { const cgltf_accessor_sparse* sparse = &accessor->sparse; const uint8_t* index_data = cgltf_buffer_view_data(sparse->indices_buffer_view); const uint8_t* value_data = cgltf_buffer_view_data(sparse->values_buffer_view); if (index_data == NULL || value_data == NULL) return NULL; index_data += sparse->indices_byte_offset; value_data += sparse->values_byte_offset; cgltf_size index_stride = cgltf_component_size(sparse->indices_component_type); cgltf_size offset = 0; cgltf_size length = sparse->count; while (length) { cgltf_size rem = length % 2; length /= 2; cgltf_size index = cgltf_component_read_index(index_data + (offset + length) * index_stride, sparse->indices_component_type); offset += index < needle ? length + rem : 0; } if (offset == sparse->count) return NULL; cgltf_size index = cgltf_component_read_index(index_data + offset * index_stride, sparse->indices_component_type); return index == needle ? value_data + offset * accessor->stride : NULL; } cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size) { if (accessor->is_sparse) { const uint8_t* element = cgltf_find_sparse_index(accessor, index); if (element) return cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, out, element_size); } if (accessor->buffer_view == NULL) { memset(out, 0, element_size * sizeof(cgltf_float)); return 1; } const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); if (element == NULL) { return 0; } element += accessor->offset + accessor->stride * index; return cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, out, element_size); } cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count) { cgltf_size floats_per_element = cgltf_num_components(accessor->type); cgltf_size available_floats = accessor->count * floats_per_element; if (out == NULL) { return available_floats; } float_count = available_floats < float_count ? available_floats : float_count; cgltf_size element_count = float_count / floats_per_element; // First pass: convert each element in the base accessor. if (accessor->buffer_view == NULL) { memset(out, 0, element_count * floats_per_element * sizeof(cgltf_float)); } else { const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); if (element == NULL) { return 0; } element += accessor->offset; if (accessor->component_type == cgltf_component_type_r_32f && accessor->stride == floats_per_element * sizeof(cgltf_float)) { memcpy(out, element, element_count * floats_per_element * sizeof(cgltf_float)); } else { cgltf_float* dest = out; for (cgltf_size index = 0; index < element_count; index++, dest += floats_per_element, element += accessor->stride) { if (!cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, dest, floats_per_element)) { return 0; } } } } // Second pass: write out each element in the sparse accessor. if (accessor->is_sparse) { const cgltf_accessor_sparse* sparse = &accessor->sparse; const uint8_t* index_data = cgltf_buffer_view_data(sparse->indices_buffer_view); const uint8_t* reader_head = cgltf_buffer_view_data(sparse->values_buffer_view); if (index_data == NULL || reader_head == NULL) { return 0; } index_data += sparse->indices_byte_offset; reader_head += sparse->values_byte_offset; cgltf_size index_stride = cgltf_component_size(sparse->indices_component_type); for (cgltf_size reader_index = 0; reader_index < sparse->count; reader_index++, index_data += index_stride, reader_head += accessor->stride) { size_t writer_index = cgltf_component_read_index(index_data, sparse->indices_component_type); float* writer_head = out + writer_index * floats_per_element; if (!cgltf_element_read_float(reader_head, accessor->type, accessor->component_type, accessor->normalized, writer_head, floats_per_element)) { return 0; } } } return element_count * floats_per_element; } static cgltf_uint cgltf_component_read_uint(const void* in, cgltf_component_type component_type) { switch (component_type) { case cgltf_component_type_r_8: return *((const int8_t*) in); case cgltf_component_type_r_8u: return *((const uint8_t*) in); case cgltf_component_type_r_16: return *((const int16_t*) in); case cgltf_component_type_r_16u: return *((const uint16_t*) in); case cgltf_component_type_r_32u: return *((const uint32_t*) in); default: return 0; } } static cgltf_bool cgltf_element_read_uint(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_uint* out, cgltf_size element_size) { cgltf_size num_components = cgltf_num_components(type); if (element_size < num_components) { return 0; } // Reading integer matrices is not a valid use case if (type == cgltf_type_mat2 || type == cgltf_type_mat3 || type == cgltf_type_mat4) { return 0; } cgltf_size component_size = cgltf_component_size(component_type); for (cgltf_size i = 0; i < num_components; ++i) { out[i] = cgltf_component_read_uint(element + component_size * i, component_type); } return 1; } cgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size) { if (accessor->is_sparse) { const uint8_t* element = cgltf_find_sparse_index(accessor, index); if (element) return cgltf_element_read_uint(element, accessor->type, accessor->component_type, out, element_size); } if (accessor->buffer_view == NULL) { memset(out, 0, element_size * sizeof(cgltf_uint)); return 1; } const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); if (element == NULL) { return 0; } element += accessor->offset + accessor->stride * index; return cgltf_element_read_uint(element, accessor->type, accessor->component_type, out, element_size); } cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index) { if (accessor->is_sparse) { const uint8_t* element = cgltf_find_sparse_index(accessor, index); if (element) return cgltf_component_read_index(element, accessor->component_type); } if (accessor->buffer_view == NULL) { return 0; } const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); if (element == NULL) { return 0; // This is an error case, but we can't communicate the error with existing interface. } element += accessor->offset + accessor->stride * index; return cgltf_component_read_index(element, accessor->component_type); } cgltf_size cgltf_mesh_index(const cgltf_data* data, const cgltf_mesh* object) { assert(object && (cgltf_size)(object - data->meshes) < data->meshes_count); return (cgltf_size)(object - data->meshes); } cgltf_size cgltf_material_index(const cgltf_data* data, const cgltf_material* object) { assert(object && (cgltf_size)(object - data->materials) < data->materials_count); return (cgltf_size)(object - data->materials); } cgltf_size cgltf_accessor_index(const cgltf_data* data, const cgltf_accessor* object) { assert(object && (cgltf_size)(object - data->accessors) < data->accessors_count); return (cgltf_size)(object - data->accessors); } cgltf_size cgltf_buffer_view_index(const cgltf_data* data, const cgltf_buffer_view* object) { assert(object && (cgltf_size)(object - data->buffer_views) < data->buffer_views_count); return (cgltf_size)(object - data->buffer_views); } cgltf_size cgltf_buffer_index(const cgltf_data* data, const cgltf_buffer* object) { assert(object && (cgltf_size)(object - data->buffers) < data->buffers_count); return (cgltf_size)(object - data->buffers); } cgltf_size cgltf_image_index(const cgltf_data* data, const cgltf_image* object) { assert(object && (cgltf_size)(object - data->images) < data->images_count); return (cgltf_size)(object - data->images); } cgltf_size cgltf_texture_index(const cgltf_data* data, const cgltf_texture* object) { assert(object && (cgltf_size)(object - data->textures) < data->textures_count); return (cgltf_size)(object - data->textures); } cgltf_size cgltf_sampler_index(const cgltf_data* data, const cgltf_sampler* object) { assert(object && (cgltf_size)(object - data->samplers) < data->samplers_count); return (cgltf_size)(object - data->samplers); } cgltf_size cgltf_skin_index(const cgltf_data* data, const cgltf_skin* object) { assert(object && (cgltf_size)(object - data->skins) < data->skins_count); return (cgltf_size)(object - data->skins); } cgltf_size cgltf_camera_index(const cgltf_data* data, const cgltf_camera* object) { assert(object && (cgltf_size)(object - data->cameras) < data->cameras_count); return (cgltf_size)(object - data->cameras); } cgltf_size cgltf_light_index(const cgltf_data* data, const cgltf_light* object) { assert(object && (cgltf_size)(object - data->lights) < data->lights_count); return (cgltf_size)(object - data->lights); } cgltf_size cgltf_node_index(const cgltf_data* data, const cgltf_node* object) { assert(object && (cgltf_size)(object - data->nodes) < data->nodes_count); return (cgltf_size)(object - data->nodes); } cgltf_size cgltf_scene_index(const cgltf_data* data, const cgltf_scene* object) { assert(object && (cgltf_size)(object - data->scenes) < data->scenes_count); return (cgltf_size)(object - data->scenes); } cgltf_size cgltf_animation_index(const cgltf_data* data, const cgltf_animation* object) { assert(object && (cgltf_size)(object - data->animations) < data->animations_count); return (cgltf_size)(object - data->animations); } cgltf_size cgltf_animation_sampler_index(const cgltf_animation* animation, const cgltf_animation_sampler* object) { assert(object && (cgltf_size)(object - animation->samplers) < animation->samplers_count); return (cgltf_size)(object - animation->samplers); } cgltf_size cgltf_animation_channel_index(const cgltf_animation* animation, const cgltf_animation_channel* object) { assert(object && (cgltf_size)(object - animation->channels) < animation->channels_count); return (cgltf_size)(object - animation->channels); } cgltf_size cgltf_accessor_unpack_indices(const cgltf_accessor* accessor, void* out, cgltf_size out_component_size, cgltf_size index_count) { if (out == NULL) { return accessor->count; } cgltf_size numbers_per_element = cgltf_num_components(accessor->type); cgltf_size available_numbers = accessor->count * numbers_per_element; index_count = available_numbers < index_count ? available_numbers : index_count; cgltf_size index_component_size = cgltf_component_size(accessor->component_type); if (accessor->is_sparse) { return 0; } if (accessor->buffer_view == NULL) { return 0; } if (index_component_size > out_component_size) { return 0; } const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); if (element == NULL) { return 0; } element += accessor->offset; if (index_component_size == out_component_size && accessor->stride == out_component_size * numbers_per_element) { memcpy(out, element, index_count * index_component_size); return index_count; } // Data couldn't be copied with memcpy due to stride being larger than the component size. // OR // The component size of the output array is larger than the component size of the index data, so index data will be padded. switch (out_component_size) { case 1: for (cgltf_size index = 0; index < index_count; index++, element += accessor->stride) { ((uint8_t*)out)[index] = (uint8_t)cgltf_component_read_index(element, accessor->component_type); } break; case 2: for (cgltf_size index = 0; index < index_count; index++, element += accessor->stride) { ((uint16_t*)out)[index] = (uint16_t)cgltf_component_read_index(element, accessor->component_type); } break; case 4: for (cgltf_size index = 0; index < index_count; index++, element += accessor->stride) { ((uint32_t*)out)[index] = (uint32_t)cgltf_component_read_index(element, accessor->component_type); } break; default: return 0; } return index_count; } #define CGLTF_ERROR_JSON -1 #define CGLTF_ERROR_NOMEM -2 #define CGLTF_ERROR_LEGACY -3 #define CGLTF_CHECK_TOKTYPE(tok_, type_) if ((tok_).type != (type_)) { return CGLTF_ERROR_JSON; } #define CGLTF_CHECK_TOKTYPE_RET(tok_, type_, ret_) if ((tok_).type != (type_)) { return ret_; } #define CGLTF_CHECK_KEY(tok_) if ((tok_).type != JSMN_STRING || (tok_).size == 0) { return CGLTF_ERROR_JSON; } /* checking size for 0 verifies that a value follows the key */ #define CGLTF_PTRINDEX(type, idx) (type*)((cgltf_size)idx + 1) #define CGLTF_PTRFIXUP(var, data, size) if (var) { if ((cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; } #define CGLTF_PTRFIXUP_REQ(var, data, size) if (!var || (cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; static int cgltf_json_strcmp(jsmntok_t const* tok, const uint8_t* json_chunk, const char* str) { CGLTF_CHECK_TOKTYPE(*tok, JSMN_STRING); size_t const str_len = strlen(str); size_t const name_length = (size_t)(tok->end - tok->start); return (str_len == name_length) ? strncmp((const char*)json_chunk + tok->start, str, str_len) : 128; } static int cgltf_json_to_int(jsmntok_t const* tok, const uint8_t* json_chunk) { CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); char tmp[128]; int size = (size_t)(tok->end - tok->start) < sizeof(tmp) ? (int)(tok->end - tok->start) : (int)(sizeof(tmp) - 1); strncpy(tmp, (const char*)json_chunk + tok->start, size); tmp[size] = 0; return CGLTF_ATOI(tmp); } static cgltf_size cgltf_json_to_size(jsmntok_t const* tok, const uint8_t* json_chunk) { CGLTF_CHECK_TOKTYPE_RET(*tok, JSMN_PRIMITIVE, 0); char tmp[128]; int size = (size_t)(tok->end - tok->start) < sizeof(tmp) ? (int)(tok->end - tok->start) : (int)(sizeof(tmp) - 1); strncpy(tmp, (const char*)json_chunk + tok->start, size); tmp[size] = 0; long long res = CGLTF_ATOLL(tmp); return res < 0 ? 0 : (cgltf_size)res; } static cgltf_float cgltf_json_to_float(jsmntok_t const* tok, const uint8_t* json_chunk) { CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); char tmp[128]; int size = (size_t)(tok->end - tok->start) < sizeof(tmp) ? (int)(tok->end - tok->start) : (int)(sizeof(tmp) - 1); strncpy(tmp, (const char*)json_chunk + tok->start, size); tmp[size] = 0; return (cgltf_float)CGLTF_ATOF(tmp); } static cgltf_bool cgltf_json_to_bool(jsmntok_t const* tok, const uint8_t* json_chunk) { int size = (int)(tok->end - tok->start); return size == 4 && memcmp(json_chunk + tok->start, "true", 4) == 0; } static int cgltf_skip_json(jsmntok_t const* tokens, int i) { int end = i + 1; while (i < end) { switch (tokens[i].type) { case JSMN_OBJECT: end += tokens[i].size * 2; break; case JSMN_ARRAY: end += tokens[i].size; break; case JSMN_PRIMITIVE: case JSMN_STRING: break; default: return -1; } i++; } return i; } static void cgltf_fill_float_array(float* out_array, int size, float value) { for (int j = 0; j < size; ++j) { out_array[j] = value; } } static int cgltf_parse_json_float_array(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, float* out_array, int size) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); if (tokens[i].size != size) { return CGLTF_ERROR_JSON; } ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); out_array[j] = cgltf_json_to_float(tokens + i, json_chunk); ++i; } return i; } static int cgltf_parse_json_string(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char** out_string) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING); if (*out_string) { return CGLTF_ERROR_JSON; } int size = (int)(tokens[i].end - tokens[i].start); char* result = (char*)options->memory.alloc_func(options->memory.user_data, size + 1); if (!result) { return CGLTF_ERROR_NOMEM; } strncpy(result, (const char*)json_chunk + tokens[i].start, size); result[size] = 0; *out_string = result; return i + 1; } static int cgltf_parse_json_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, size_t element_size, void** out_array, cgltf_size* out_size) { (void)json_chunk; if (tokens[i].type != JSMN_ARRAY) { return tokens[i].type == JSMN_OBJECT ? CGLTF_ERROR_LEGACY : CGLTF_ERROR_JSON; } if (*out_array) { return CGLTF_ERROR_JSON; } int size = tokens[i].size; void* result = cgltf_calloc(options, element_size, size); if (!result) { return CGLTF_ERROR_NOMEM; } *out_array = result; *out_size = size; return i + 1; } static int cgltf_parse_json_string_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char*** out_array, cgltf_size* out_size) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(char*), (void**)out_array, out_size); if (i < 0) { return i; } for (cgltf_size j = 0; j < *out_size; ++j) { i = cgltf_parse_json_string(options, tokens, i, json_chunk, j + (*out_array)); if (i < 0) { return i; } } return i; } static void cgltf_parse_attribute_type(const char* name, cgltf_attribute_type* out_type, int* out_index) { if (*name == '_') { *out_type = cgltf_attribute_type_custom; return; } const char* us = strchr(name, '_'); size_t len = us ? (size_t)(us - name) : strlen(name); if (len == 8 && strncmp(name, "POSITION", 8) == 0) { *out_type = cgltf_attribute_type_position; } else if (len == 6 && strncmp(name, "NORMAL", 6) == 0) { *out_type = cgltf_attribute_type_normal; } else if (len == 7 && strncmp(name, "TANGENT", 7) == 0) { *out_type = cgltf_attribute_type_tangent; } else if (len == 8 && strncmp(name, "TEXCOORD", 8) == 0) { *out_type = cgltf_attribute_type_texcoord; } else if (len == 5 && strncmp(name, "COLOR", 5) == 0) { *out_type = cgltf_attribute_type_color; } else if (len == 6 && strncmp(name, "JOINTS", 6) == 0) { *out_type = cgltf_attribute_type_joints; } else if (len == 7 && strncmp(name, "WEIGHTS", 7) == 0) { *out_type = cgltf_attribute_type_weights; } else { *out_type = cgltf_attribute_type_invalid; } if (us && *out_type != cgltf_attribute_type_invalid) { *out_index = CGLTF_ATOI(us + 1); if (*out_index < 0) { *out_type = cgltf_attribute_type_invalid; *out_index = 0; } } } static int cgltf_parse_json_attribute_list(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_attribute** out_attributes, cgltf_size* out_attributes_count) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); if (*out_attributes) { return CGLTF_ERROR_JSON; } *out_attributes_count = tokens[i].size; *out_attributes = (cgltf_attribute*)cgltf_calloc(options, sizeof(cgltf_attribute), *out_attributes_count); ++i; if (!*out_attributes) { return CGLTF_ERROR_NOMEM; } for (cgltf_size j = 0; j < *out_attributes_count; ++j) { CGLTF_CHECK_KEY(tokens[i]); i = cgltf_parse_json_string(options, tokens, i, json_chunk, &(*out_attributes)[j].name); if (i < 0) { return CGLTF_ERROR_JSON; } cgltf_parse_attribute_type((*out_attributes)[j].name, &(*out_attributes)[j].type, &(*out_attributes)[j].index); (*out_attributes)[j].data = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } return i; } static int cgltf_parse_json_extras(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extras* out_extras) { if (out_extras->data) { return CGLTF_ERROR_JSON; } /* fill deprecated fields for now, this will be removed in the future */ out_extras->start_offset = tokens[i].start; out_extras->end_offset = tokens[i].end; size_t start = tokens[i].start; size_t size = tokens[i].end - start; out_extras->data = (char*)options->memory.alloc_func(options->memory.user_data, size + 1); if (!out_extras->data) { return CGLTF_ERROR_NOMEM; } strncpy(out_extras->data, (const char*)json_chunk + start, size); out_extras->data[size] = '\0'; i = cgltf_skip_json(tokens, i); return i; } static int cgltf_parse_json_unprocessed_extension(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extension* out_extension) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING); CGLTF_CHECK_TOKTYPE(tokens[i+1], JSMN_OBJECT); if (out_extension->name) { return CGLTF_ERROR_JSON; } cgltf_size name_length = tokens[i].end - tokens[i].start; out_extension->name = (char*)options->memory.alloc_func(options->memory.user_data, name_length + 1); if (!out_extension->name) { return CGLTF_ERROR_NOMEM; } strncpy(out_extension->name, (const char*)json_chunk + tokens[i].start, name_length); out_extension->name[name_length] = 0; i++; size_t start = tokens[i].start; size_t size = tokens[i].end - start; out_extension->data = (char*)options->memory.alloc_func(options->memory.user_data, size + 1); if (!out_extension->data) { return CGLTF_ERROR_NOMEM; } strncpy(out_extension->data, (const char*)json_chunk + start, size); out_extension->data[size] = '\0'; i = cgltf_skip_json(tokens, i); return i; } static int cgltf_parse_json_unprocessed_extensions(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_size* out_extensions_count, cgltf_extension** out_extensions) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); if(*out_extensions) { return CGLTF_ERROR_JSON; } int extensions_size = tokens[i].size; *out_extensions_count = 0; *out_extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); if (!*out_extensions) { return CGLTF_ERROR_NOMEM; } ++i; for (int j = 0; j < extensions_size; ++j) { CGLTF_CHECK_KEY(tokens[i]); cgltf_size extension_index = (*out_extensions_count)++; cgltf_extension* extension = &((*out_extensions)[extension_index]); i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, extension); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_draco_mesh_compression(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_draco_mesh_compression* out_draco_mesh_compression) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "attributes") == 0) { i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_draco_mesh_compression->attributes, &out_draco_mesh_compression->attributes_count); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferView") == 0) { ++i; out_draco_mesh_compression->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_mesh_gpu_instancing(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_mesh_gpu_instancing* out_mesh_gpu_instancing) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "attributes") == 0) { i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_mesh_gpu_instancing->attributes, &out_mesh_gpu_instancing->attributes_count); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_material_mapping_data(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material_mapping* out_mappings, cgltf_size* offset) { (void)options; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int obj_size = tokens[i].size; ++i; int material = -1; int variants_tok = -1; int extras_tok = -1; for (int k = 0; k < obj_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "material") == 0) { ++i; material = cgltf_json_to_int(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "variants") == 0) { variants_tok = i+1; CGLTF_CHECK_TOKTYPE(tokens[variants_tok], JSMN_ARRAY); i = cgltf_skip_json(tokens, i+1); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { extras_tok = i + 1; i = cgltf_skip_json(tokens, extras_tok); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } if (material < 0 || variants_tok < 0) { return CGLTF_ERROR_JSON; } if (out_mappings) { for (int k = 0; k < tokens[variants_tok].size; ++k) { int variant = cgltf_json_to_int(&tokens[variants_tok + 1 + k], json_chunk); if (variant < 0) return variant; out_mappings[*offset].material = CGLTF_PTRINDEX(cgltf_material, material); out_mappings[*offset].variant = variant; if (extras_tok >= 0) { int e = cgltf_parse_json_extras(options, tokens, extras_tok, json_chunk, &out_mappings[*offset].extras); if (e < 0) return e; } (*offset)++; } } else { (*offset) += tokens[variants_tok].size; } } return i; } static int cgltf_parse_json_material_mappings(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "mappings") == 0) { if (out_prim->mappings) { return CGLTF_ERROR_JSON; } cgltf_size mappings_offset = 0; int k = cgltf_parse_json_material_mapping_data(options, tokens, i + 1, json_chunk, NULL, &mappings_offset); if (k < 0) { return k; } out_prim->mappings_count = mappings_offset; out_prim->mappings = (cgltf_material_mapping*)cgltf_calloc(options, sizeof(cgltf_material_mapping), out_prim->mappings_count); mappings_offset = 0; i = cgltf_parse_json_material_mapping_data(options, tokens, i + 1, json_chunk, out_prim->mappings, &mappings_offset); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static cgltf_primitive_type cgltf_json_to_primitive_type(jsmntok_t const* tok, const uint8_t* json_chunk) { int type = cgltf_json_to_int(tok, json_chunk); switch (type) { case 0: return cgltf_primitive_type_points; case 1: return cgltf_primitive_type_lines; case 2: return cgltf_primitive_type_line_loop; case 3: return cgltf_primitive_type_line_strip; case 4: return cgltf_primitive_type_triangles; case 5: return cgltf_primitive_type_triangle_strip; case 6: return cgltf_primitive_type_triangle_fan; default: return cgltf_primitive_type_invalid; } } static int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); out_prim->type = cgltf_primitive_type_triangles; int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "mode") == 0) { ++i; out_prim->type = cgltf_json_to_primitive_type(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "indices") == 0) { ++i; out_prim->indices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "material") == 0) { ++i; out_prim->material = CGLTF_PTRINDEX(cgltf_material, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "attributes") == 0) { i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_prim->attributes, &out_prim->attributes_count); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "targets") == 0) { i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_morph_target), (void**)&out_prim->targets, &out_prim->targets_count); if (i < 0) { return i; } for (cgltf_size k = 0; k < out_prim->targets_count; ++k) { i = cgltf_parse_json_attribute_list(options, tokens, i, json_chunk, &out_prim->targets[k].attributes, &out_prim->targets[k].attributes_count); if (i < 0) { return i; } } } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_prim->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); if(out_prim->extensions) { return CGLTF_ERROR_JSON; } int extensions_size = tokens[i].size; out_prim->extensions_count = 0; out_prim->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); if (!out_prim->extensions) { return CGLTF_ERROR_NOMEM; } ++i; for (int k = 0; k < extensions_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_draco_mesh_compression") == 0) { out_prim->has_draco_mesh_compression = 1; i = cgltf_parse_json_draco_mesh_compression(options, tokens, i + 1, json_chunk, &out_prim->draco_mesh_compression); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_variants") == 0) { i = cgltf_parse_json_material_mappings(options, tokens, i + 1, json_chunk, out_prim); } else { i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_prim->extensions[out_prim->extensions_count++])); } if (i < 0) { return i; } } } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_mesh* out_mesh) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_mesh->name); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "primitives") == 0) { i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_primitive), (void**)&out_mesh->primitives, &out_mesh->primitives_count); if (i < 0) { return i; } for (cgltf_size prim_index = 0; prim_index < out_mesh->primitives_count; ++prim_index) { i = cgltf_parse_json_primitive(options, tokens, i, json_chunk, &out_mesh->primitives[prim_index]); if (i < 0) { return i; } } } else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0) { i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_mesh->weights, &out_mesh->weights_count); if (i < 0) { return i; } i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_mesh->weights, (int)out_mesh->weights_count); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { ++i; out_mesh->extras.start_offset = tokens[i].start; out_mesh->extras.end_offset = tokens[i].end; if (tokens[i].type == JSMN_OBJECT) { int extras_size = tokens[i].size; ++i; for (int k = 0; k < extras_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "targetNames") == 0 && tokens[i+1].type == JSMN_ARRAY) { i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_mesh->target_names, &out_mesh->target_names_count); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } } else { i = cgltf_skip_json(tokens, i); } } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_mesh->extensions_count, &out_mesh->extensions); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_meshes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_mesh), (void**)&out_data->meshes, &out_data->meshes_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->meshes_count; ++j) { i = cgltf_parse_json_mesh(options, tokens, i, json_chunk, &out_data->meshes[j]); if (i < 0) { return i; } } return i; } static cgltf_component_type cgltf_json_to_component_type(jsmntok_t const* tok, const uint8_t* json_chunk) { int type = cgltf_json_to_int(tok, json_chunk); switch (type) { case 5120: return cgltf_component_type_r_8; case 5121: return cgltf_component_type_r_8u; case 5122: return cgltf_component_type_r_16; case 5123: return cgltf_component_type_r_16u; case 5125: return cgltf_component_type_r_32u; case 5126: return cgltf_component_type_r_32f; default: return cgltf_component_type_invalid; } } static int cgltf_parse_json_accessor_sparse(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor_sparse* out_sparse) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) { ++i; out_sparse->count = cgltf_json_to_size(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "indices") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int indices_size = tokens[i].size; ++i; for (int k = 0; k < indices_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) { ++i; out_sparse->indices_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) { ++i; out_sparse->indices_byte_offset = cgltf_json_to_size(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) { ++i; out_sparse->indices_component_type = cgltf_json_to_component_type(tokens + i, json_chunk); ++i; } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } } else if (cgltf_json_strcmp(tokens+i, json_chunk, "values") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int values_size = tokens[i].size; ++i; for (int k = 0; k < values_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) { ++i; out_sparse->values_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) { ++i; out_sparse->values_byte_offset = cgltf_json_to_size(tokens + i, json_chunk); ++i; } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_accessor(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor* out_accessor) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_accessor->name); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) { ++i; out_accessor->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) { ++i; out_accessor->offset = cgltf_json_to_size(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) { ++i; out_accessor->component_type = cgltf_json_to_component_type(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "normalized") == 0) { ++i; out_accessor->normalized = cgltf_json_to_bool(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) { ++i; out_accessor->count = cgltf_json_to_size(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) { ++i; if (cgltf_json_strcmp(tokens+i, json_chunk, "SCALAR") == 0) { out_accessor->type = cgltf_type_scalar; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC2") == 0) { out_accessor->type = cgltf_type_vec2; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC3") == 0) { out_accessor->type = cgltf_type_vec3; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC4") == 0) { out_accessor->type = cgltf_type_vec4; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT2") == 0) { out_accessor->type = cgltf_type_mat2; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT3") == 0) { out_accessor->type = cgltf_type_mat3; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT4") == 0) { out_accessor->type = cgltf_type_mat4; } ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "min") == 0) { ++i; out_accessor->has_min = 1; // note: we can't parse the precise number of elements since type may not have been computed yet int min_size = tokens[i].size > 16 ? 16 : tokens[i].size; i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->min, min_size); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "max") == 0) { ++i; out_accessor->has_max = 1; // note: we can't parse the precise number of elements since type may not have been computed yet int max_size = tokens[i].size > 16 ? 16 : tokens[i].size; i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->max, max_size); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "sparse") == 0) { out_accessor->is_sparse = 1; i = cgltf_parse_json_accessor_sparse(tokens, i + 1, json_chunk, &out_accessor->sparse); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_accessor->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_accessor->extensions_count, &out_accessor->extensions); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_texture_transform(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_transform* out_texture_transform) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "offset") == 0) { i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->offset, 2); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "rotation") == 0) { ++i; out_texture_transform->rotation = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) { i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->scale, 2); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) { ++i; out_texture_transform->has_texcoord = 1; out_texture_transform->texcoord = cgltf_json_to_int(tokens + i, json_chunk); ++i; } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_texture_view(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_view* out_texture_view) { (void)options; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); out_texture_view->scale = 1.0f; cgltf_fill_float_array(out_texture_view->transform.scale, 2, 1.0f); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "index") == 0) { ++i; out_texture_view->texture = CGLTF_PTRINDEX(cgltf_texture, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) { ++i; out_texture_view->texcoord = cgltf_json_to_int(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) { ++i; out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "strength") == 0) { ++i; out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int extensions_size = tokens[i].size; ++i; for (int k = 0; k < extensions_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_texture_transform") == 0) { out_texture_view->has_transform = 1; i = cgltf_parse_json_texture_transform(tokens, i + 1, json_chunk, &out_texture_view->transform); } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_pbr_metallic_roughness(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_metallic_roughness* out_pbr) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "metallicFactor") == 0) { ++i; out_pbr->metallic_factor = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "roughnessFactor") == 0) { ++i; out_pbr->roughness_factor = cgltf_json_to_float(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorFactor") == 0) { i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->base_color_factor, 4); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->base_color_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "metallicRoughnessTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->metallic_roughness_texture); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_pbr_specular_glossiness(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_specular_glossiness* out_pbr) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "diffuseFactor") == 0) { i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->diffuse_factor, 4); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularFactor") == 0) { i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->specular_factor, 3); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "glossinessFactor") == 0) { ++i; out_pbr->glossiness_factor = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "diffuseTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->diffuse_texture); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularGlossinessTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->specular_glossiness_texture); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_clearcoat(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_clearcoat* out_clearcoat) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatFactor") == 0) { ++i; out_clearcoat->clearcoat_factor = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatRoughnessFactor") == 0) { ++i; out_clearcoat->clearcoat_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_texture); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatRoughnessTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_roughness_texture); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatNormalTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_normal_texture); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_ior(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_ior* out_ior) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; // Default values out_ior->ior = 1.5f; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "ior") == 0) { ++i; out_ior->ior = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_specular(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_specular* out_specular) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; // Default values out_specular->specular_factor = 1.0f; cgltf_fill_float_array(out_specular->specular_color_factor, 3, 1.0f); for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "specularFactor") == 0) { ++i; out_specular->specular_factor = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularColorFactor") == 0) { i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_specular->specular_color_factor, 3); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_specular->specular_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "specularColorTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_specular->specular_color_texture); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_transmission(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_transmission* out_transmission) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "transmissionFactor") == 0) { ++i; out_transmission->transmission_factor = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "transmissionTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_transmission->transmission_texture); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_volume(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_volume* out_volume) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "thicknessFactor") == 0) { ++i; out_volume->thickness_factor = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "thicknessTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_volume->thickness_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "attenuationColor") == 0) { i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_volume->attenuation_color, 3); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "attenuationDistance") == 0) { ++i; out_volume->attenuation_distance = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_sheen(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sheen* out_sheen) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenColorFactor") == 0) { i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_sheen->sheen_color_factor, 3); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenColorTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_sheen->sheen_color_texture); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenRoughnessFactor") == 0) { ++i; out_sheen->sheen_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenRoughnessTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_sheen->sheen_roughness_texture); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_emissive_strength(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_emissive_strength* out_emissive_strength) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; // Default out_emissive_strength->emissive_strength = 1.f; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveStrength") == 0) { ++i; out_emissive_strength->emissive_strength = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_iridescence(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_iridescence* out_iridescence) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; // Default out_iridescence->iridescence_ior = 1.3f; out_iridescence->iridescence_thickness_min = 100.f; out_iridescence->iridescence_thickness_max = 400.f; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceFactor") == 0) { ++i; out_iridescence->iridescence_factor = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_iridescence->iridescence_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceIor") == 0) { ++i; out_iridescence->iridescence_ior = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceThicknessMinimum") == 0) { ++i; out_iridescence->iridescence_thickness_min = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceThicknessMaximum") == 0) { ++i; out_iridescence->iridescence_thickness_max = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceThicknessTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_iridescence->iridescence_thickness_texture); } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_diffuse_transmission(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_diffuse_transmission* out_diff_transmission) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; // Defaults cgltf_fill_float_array(out_diff_transmission->diffuse_transmission_color_factor, 3, 1.0f); out_diff_transmission->diffuse_transmission_factor = 0.f; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "diffuseTransmissionFactor") == 0) { ++i; out_diff_transmission->diffuse_transmission_factor = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "diffuseTransmissionTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_diff_transmission->diffuse_transmission_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "diffuseTransmissionColorFactor") == 0) { i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_diff_transmission->diffuse_transmission_color_factor, 3); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "diffuseTransmissionColorTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_diff_transmission->diffuse_transmission_color_texture); } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_anisotropy(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_anisotropy* out_anisotropy) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "anisotropyStrength") == 0) { ++i; out_anisotropy->anisotropy_strength = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "anisotropyRotation") == 0) { ++i; out_anisotropy->anisotropy_rotation = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "anisotropyTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_anisotropy->anisotropy_texture); } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_dispersion(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_dispersion* out_dispersion) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "dispersion") == 0) { ++i; out_dispersion->dispersion = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_image* out_image) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "uri") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->uri); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) { ++i; out_image->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "mimeType") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->mime_type); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->name); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_image->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_image->extensions_count, &out_image->extensions); } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sampler* out_sampler) { (void)options; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); out_sampler->wrap_s = cgltf_wrap_mode_repeat; out_sampler->wrap_t = cgltf_wrap_mode_repeat; int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_sampler->name); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "magFilter") == 0) { ++i; out_sampler->mag_filter = (cgltf_filter_type)cgltf_json_to_int(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "minFilter") == 0) { ++i; out_sampler->min_filter = (cgltf_filter_type)cgltf_json_to_int(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapS") == 0) { ++i; out_sampler->wrap_s = (cgltf_wrap_mode)cgltf_json_to_int(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapT") == 0) { ++i; out_sampler->wrap_t = (cgltf_wrap_mode)cgltf_json_to_int(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_sampler->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sampler->extensions_count, &out_sampler->extensions); } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture* out_texture) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_texture->name); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "sampler") == 0) { ++i; out_texture->sampler = CGLTF_PTRINDEX(cgltf_sampler, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) { ++i; out_texture->image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_texture->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); if (out_texture->extensions) { return CGLTF_ERROR_JSON; } int extensions_size = tokens[i].size; ++i; out_texture->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); out_texture->extensions_count = 0; if (!out_texture->extensions) { return CGLTF_ERROR_NOMEM; } for (int k = 0; k < extensions_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_texture_basisu") == 0) { out_texture->has_basisu = 1; ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int num_properties = tokens[i].size; ++i; for (int t = 0; t < num_properties; ++t) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) { ++i; out_texture->basisu_image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } } else if (cgltf_json_strcmp(tokens + i, json_chunk, "EXT_texture_webp") == 0) { out_texture->has_webp = 1; ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int num_properties = tokens[i].size; ++i; for (int t = 0; t < num_properties; ++t) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) { ++i; out_texture->webp_image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } } else { i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_texture->extensions[out_texture->extensions_count++])); } if (i < 0) { return i; } } } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material* out_material) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); cgltf_fill_float_array(out_material->pbr_metallic_roughness.base_color_factor, 4, 1.0f); out_material->pbr_metallic_roughness.metallic_factor = 1.0f; out_material->pbr_metallic_roughness.roughness_factor = 1.0f; cgltf_fill_float_array(out_material->pbr_specular_glossiness.diffuse_factor, 4, 1.0f); cgltf_fill_float_array(out_material->pbr_specular_glossiness.specular_factor, 3, 1.0f); out_material->pbr_specular_glossiness.glossiness_factor = 1.0f; cgltf_fill_float_array(out_material->volume.attenuation_color, 3, 1.0f); out_material->volume.attenuation_distance = FLT_MAX; out_material->alpha_cutoff = 0.5f; int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_material->name); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "pbrMetallicRoughness") == 0) { out_material->has_pbr_metallic_roughness = 1; i = cgltf_parse_json_pbr_metallic_roughness(options, tokens, i + 1, json_chunk, &out_material->pbr_metallic_roughness); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "emissiveFactor") == 0) { i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_material->emissive_factor, 3); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "normalTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_material->normal_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "occlusionTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_material->occlusion_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveTexture") == 0) { i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_material->emissive_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaMode") == 0) { ++i; if (cgltf_json_strcmp(tokens + i, json_chunk, "OPAQUE") == 0) { out_material->alpha_mode = cgltf_alpha_mode_opaque; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "MASK") == 0) { out_material->alpha_mode = cgltf_alpha_mode_mask; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "BLEND") == 0) { out_material->alpha_mode = cgltf_alpha_mode_blend; } ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaCutoff") == 0) { ++i; out_material->alpha_cutoff = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "doubleSided") == 0) { ++i; out_material->double_sided = cgltf_json_to_bool(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_material->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); if(out_material->extensions) { return CGLTF_ERROR_JSON; } int extensions_size = tokens[i].size; ++i; out_material->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); out_material->extensions_count= 0; if (!out_material->extensions) { return CGLTF_ERROR_NOMEM; } for (int k = 0; k < extensions_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_pbrSpecularGlossiness") == 0) { out_material->has_pbr_specular_glossiness = 1; i = cgltf_parse_json_pbr_specular_glossiness(options, tokens, i + 1, json_chunk, &out_material->pbr_specular_glossiness); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_unlit") == 0) { out_material->unlit = 1; i = cgltf_skip_json(tokens, i+1); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_clearcoat") == 0) { out_material->has_clearcoat = 1; i = cgltf_parse_json_clearcoat(options, tokens, i + 1, json_chunk, &out_material->clearcoat); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_ior") == 0) { out_material->has_ior = 1; i = cgltf_parse_json_ior(tokens, i + 1, json_chunk, &out_material->ior); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_specular") == 0) { out_material->has_specular = 1; i = cgltf_parse_json_specular(options, tokens, i + 1, json_chunk, &out_material->specular); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_transmission") == 0) { out_material->has_transmission = 1; i = cgltf_parse_json_transmission(options, tokens, i + 1, json_chunk, &out_material->transmission); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_volume") == 0) { out_material->has_volume = 1; i = cgltf_parse_json_volume(options, tokens, i + 1, json_chunk, &out_material->volume); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_sheen") == 0) { out_material->has_sheen = 1; i = cgltf_parse_json_sheen(options, tokens, i + 1, json_chunk, &out_material->sheen); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_emissive_strength") == 0) { out_material->has_emissive_strength = 1; i = cgltf_parse_json_emissive_strength(tokens, i + 1, json_chunk, &out_material->emissive_strength); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_iridescence") == 0) { out_material->has_iridescence = 1; i = cgltf_parse_json_iridescence(options, tokens, i + 1, json_chunk, &out_material->iridescence); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_diffuse_transmission") == 0) { out_material->has_diffuse_transmission = 1; i = cgltf_parse_json_diffuse_transmission(options, tokens, i + 1, json_chunk, &out_material->diffuse_transmission); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_anisotropy") == 0) { out_material->has_anisotropy = 1; i = cgltf_parse_json_anisotropy(options, tokens, i + 1, json_chunk, &out_material->anisotropy); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_dispersion") == 0) { out_material->has_dispersion = 1; i = cgltf_parse_json_dispersion(tokens, i + 1, json_chunk, &out_material->dispersion); } else { i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_material->extensions[out_material->extensions_count++])); } if (i < 0) { return i; } } } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_accessors(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_accessor), (void**)&out_data->accessors, &out_data->accessors_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->accessors_count; ++j) { i = cgltf_parse_json_accessor(options, tokens, i, json_chunk, &out_data->accessors[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_materials(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material), (void**)&out_data->materials, &out_data->materials_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->materials_count; ++j) { i = cgltf_parse_json_material(options, tokens, i, json_chunk, &out_data->materials[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_images(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_image), (void**)&out_data->images, &out_data->images_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->images_count; ++j) { i = cgltf_parse_json_image(options, tokens, i, json_chunk, &out_data->images[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_textures(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_texture), (void**)&out_data->textures, &out_data->textures_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->textures_count; ++j) { i = cgltf_parse_json_texture(options, tokens, i, json_chunk, &out_data->textures[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_samplers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_sampler), (void**)&out_data->samplers, &out_data->samplers_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->samplers_count; ++j) { i = cgltf_parse_json_sampler(options, tokens, i, json_chunk, &out_data->samplers[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_meshopt_compression(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_meshopt_compression* out_meshopt_compression) { (void)options; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "buffer") == 0) { ++i; out_meshopt_compression->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) { ++i; out_meshopt_compression->offset = cgltf_json_to_size(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) { ++i; out_meshopt_compression->size = cgltf_json_to_size(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteStride") == 0) { ++i; out_meshopt_compression->stride = cgltf_json_to_size(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) { ++i; out_meshopt_compression->count = cgltf_json_to_size(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "mode") == 0) { ++i; if (cgltf_json_strcmp(tokens+i, json_chunk, "ATTRIBUTES") == 0) { out_meshopt_compression->mode = cgltf_meshopt_compression_mode_attributes; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "TRIANGLES") == 0) { out_meshopt_compression->mode = cgltf_meshopt_compression_mode_triangles; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "INDICES") == 0) { out_meshopt_compression->mode = cgltf_meshopt_compression_mode_indices; } ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "filter") == 0) { ++i; if (cgltf_json_strcmp(tokens+i, json_chunk, "NONE") == 0) { out_meshopt_compression->filter = cgltf_meshopt_compression_filter_none; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "OCTAHEDRAL") == 0) { out_meshopt_compression->filter = cgltf_meshopt_compression_filter_octahedral; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "QUATERNION") == 0) { out_meshopt_compression->filter = cgltf_meshopt_compression_filter_quaternion; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "EXPONENTIAL") == 0) { out_meshopt_compression->filter = cgltf_meshopt_compression_filter_exponential; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "COLOR") == 0) { out_meshopt_compression->filter = cgltf_meshopt_compression_filter_color; } ++i; } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_buffer_view(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer_view* out_buffer_view) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer_view->name); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "buffer") == 0) { ++i; out_buffer_view->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) { ++i; out_buffer_view->offset = cgltf_json_to_size(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) { ++i; out_buffer_view->size = cgltf_json_to_size(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteStride") == 0) { ++i; out_buffer_view->stride = cgltf_json_to_size(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "target") == 0) { ++i; int type = cgltf_json_to_int(tokens+i, json_chunk); switch (type) { case 34962: type = cgltf_buffer_view_type_vertices; break; case 34963: type = cgltf_buffer_view_type_indices; break; default: type = cgltf_buffer_view_type_invalid; break; } out_buffer_view->type = (cgltf_buffer_view_type)type; ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_buffer_view->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); if(out_buffer_view->extensions) { return CGLTF_ERROR_JSON; } int extensions_size = tokens[i].size; out_buffer_view->extensions_count = 0; out_buffer_view->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); if (!out_buffer_view->extensions) { return CGLTF_ERROR_NOMEM; } ++i; for (int k = 0; k < extensions_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "EXT_meshopt_compression") == 0) { out_buffer_view->has_meshopt_compression = 1; i = cgltf_parse_json_meshopt_compression(options, tokens, i + 1, json_chunk, &out_buffer_view->meshopt_compression); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_meshopt_compression") == 0) { out_buffer_view->has_meshopt_compression = 1; out_buffer_view->meshopt_compression.is_khr = 1; i = cgltf_parse_json_meshopt_compression(options, tokens, i + 1, json_chunk, &out_buffer_view->meshopt_compression); } else { i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_buffer_view->extensions[out_buffer_view->extensions_count++])); } if (i < 0) { return i; } } } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_buffer_views(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer_view), (void**)&out_data->buffer_views, &out_data->buffer_views_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->buffer_views_count; ++j) { i = cgltf_parse_json_buffer_view(options, tokens, i, json_chunk, &out_data->buffer_views[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer* out_buffer) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->name); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) { ++i; out_buffer->size = cgltf_json_to_size(tokens+i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "uri") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->uri); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_buffer->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_buffer->extensions_count, &out_buffer->extensions); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_buffers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer), (void**)&out_data->buffers, &out_data->buffers_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->buffers_count; ++j) { i = cgltf_parse_json_buffer(options, tokens, i, json_chunk, &out_data->buffers[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_skin(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_skin* out_skin) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_skin->name); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "joints") == 0) { i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_skin->joints, &out_skin->joints_count); if (i < 0) { return i; } for (cgltf_size k = 0; k < out_skin->joints_count; ++k) { out_skin->joints[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } } else if (cgltf_json_strcmp(tokens+i, json_chunk, "skeleton") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); out_skin->skeleton = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "inverseBindMatrices") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); out_skin->inverse_bind_matrices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_skin->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_skin->extensions_count, &out_skin->extensions); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_skins(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_skin), (void**)&out_data->skins, &out_data->skins_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->skins_count; ++j) { i = cgltf_parse_json_skin(options, tokens, i, json_chunk, &out_data->skins[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_camera* out_camera) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_camera->name); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "perspective") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int data_size = tokens[i].size; ++i; if (out_camera->type != cgltf_camera_type_invalid) { return CGLTF_ERROR_JSON; } out_camera->type = cgltf_camera_type_perspective; for (int k = 0; k < data_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "aspectRatio") == 0) { ++i; out_camera->data.perspective.has_aspect_ratio = 1; out_camera->data.perspective.aspect_ratio = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "yfov") == 0) { ++i; out_camera->data.perspective.yfov = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) { ++i; out_camera->data.perspective.has_zfar = 1; out_camera->data.perspective.zfar = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "znear") == 0) { ++i; out_camera->data.perspective.znear = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_camera->data.perspective.extras); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } } else if (cgltf_json_strcmp(tokens+i, json_chunk, "orthographic") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int data_size = tokens[i].size; ++i; if (out_camera->type != cgltf_camera_type_invalid) { return CGLTF_ERROR_JSON; } out_camera->type = cgltf_camera_type_orthographic; for (int k = 0; k < data_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "xmag") == 0) { ++i; out_camera->data.orthographic.xmag = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "ymag") == 0) { ++i; out_camera->data.orthographic.ymag = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) { ++i; out_camera->data.orthographic.zfar = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "znear") == 0) { ++i; out_camera->data.orthographic.znear = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_camera->data.orthographic.extras); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_camera->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_camera->extensions_count, &out_camera->extensions); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_cameras(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_camera), (void**)&out_data->cameras, &out_data->cameras_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->cameras_count; ++j) { i = cgltf_parse_json_camera(options, tokens, i, json_chunk, &out_data->cameras[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_light(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_light* out_light) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); out_light->color[0] = 1.f; out_light->color[1] = 1.f; out_light->color[2] = 1.f; out_light->intensity = 1.f; out_light->spot_inner_cone_angle = 0.f; out_light->spot_outer_cone_angle = 3.1415926535f / 4.0f; int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_light->name); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "color") == 0) { i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_light->color, 3); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "intensity") == 0) { ++i; out_light->intensity = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) { ++i; if (cgltf_json_strcmp(tokens + i, json_chunk, "directional") == 0) { out_light->type = cgltf_light_type_directional; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "point") == 0) { out_light->type = cgltf_light_type_point; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "spot") == 0) { out_light->type = cgltf_light_type_spot; } ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "range") == 0) { ++i; out_light->range = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "spot") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int data_size = tokens[i].size; ++i; for (int k = 0; k < data_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "innerConeAngle") == 0) { ++i; out_light->spot_inner_cone_angle = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "outerConeAngle") == 0) { ++i; out_light->spot_outer_cone_angle = cgltf_json_to_float(tokens + i, json_chunk); ++i; } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_light->extras); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_lights(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_light), (void**)&out_data->lights, &out_data->lights_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->lights_count; ++j) { i = cgltf_parse_json_light(options, tokens, i, json_chunk, &out_data->lights[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_node(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_node* out_node) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); out_node->rotation[3] = 1.0f; out_node->scale[0] = 1.0f; out_node->scale[1] = 1.0f; out_node->scale[2] = 1.0f; out_node->matrix[0] = 1.0f; out_node->matrix[5] = 1.0f; out_node->matrix[10] = 1.0f; out_node->matrix[15] = 1.0f; int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_node->name); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "children") == 0) { i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_node->children, &out_node->children_count); if (i < 0) { return i; } for (cgltf_size k = 0; k < out_node->children_count; ++k) { out_node->children[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } } else if (cgltf_json_strcmp(tokens+i, json_chunk, "mesh") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); out_node->mesh = CGLTF_PTRINDEX(cgltf_mesh, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "skin") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); out_node->skin = CGLTF_PTRINDEX(cgltf_skin, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "camera") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); out_node->camera = CGLTF_PTRINDEX(cgltf_camera, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "translation") == 0) { out_node->has_translation = 1; i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->translation, 3); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "rotation") == 0) { out_node->has_rotation = 1; i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->rotation, 4); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "scale") == 0) { out_node->has_scale = 1; i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->scale, 3); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "matrix") == 0) { out_node->has_matrix = 1; i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->matrix, 16); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0) { i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_node->weights, &out_node->weights_count); if (i < 0) { return i; } i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_node->weights, (int)out_node->weights_count); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_node->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); if(out_node->extensions) { return CGLTF_ERROR_JSON; } int extensions_size = tokens[i].size; out_node->extensions_count= 0; out_node->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); if (!out_node->extensions) { return CGLTF_ERROR_NOMEM; } ++i; for (int k = 0; k < extensions_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_lights_punctual") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int data_size = tokens[i].size; ++i; for (int m = 0; m < data_size; ++m) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "light") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); out_node->light = CGLTF_PTRINDEX(cgltf_light, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } } else if (cgltf_json_strcmp(tokens + i, json_chunk, "EXT_mesh_gpu_instancing") == 0) { out_node->has_mesh_gpu_instancing = 1; i = cgltf_parse_json_mesh_gpu_instancing(options, tokens, i + 1, json_chunk, &out_node->mesh_gpu_instancing); } else { i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_node->extensions[out_node->extensions_count++])); } if (i < 0) { return i; } } } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_nodes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_node), (void**)&out_data->nodes, &out_data->nodes_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->nodes_count; ++j) { i = cgltf_parse_json_node(options, tokens, i, json_chunk, &out_data->nodes[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_scene(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_scene* out_scene) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_scene->name); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "nodes") == 0) { i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_scene->nodes, &out_scene->nodes_count); if (i < 0) { return i; } for (cgltf_size k = 0; k < out_scene->nodes_count; ++k) { out_scene->nodes[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_scene->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_scene->extensions_count, &out_scene->extensions); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_scenes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_scene), (void**)&out_data->scenes, &out_data->scenes_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->scenes_count; ++j) { i = cgltf_parse_json_scene(options, tokens, i, json_chunk, &out_data->scenes[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_animation_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_sampler* out_sampler) { (void)options; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "input") == 0) { ++i; out_sampler->input = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "output") == 0) { ++i; out_sampler->output = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "interpolation") == 0) { ++i; if (cgltf_json_strcmp(tokens + i, json_chunk, "LINEAR") == 0) { out_sampler->interpolation = cgltf_interpolation_type_linear; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "STEP") == 0) { out_sampler->interpolation = cgltf_interpolation_type_step; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "CUBICSPLINE") == 0) { out_sampler->interpolation = cgltf_interpolation_type_cubic_spline; } ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_sampler->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sampler->extensions_count, &out_sampler->extensions); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_animation_channel(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_channel* out_channel) { (void)options; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "sampler") == 0) { ++i; out_channel->sampler = CGLTF_PTRINDEX(cgltf_animation_sampler, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "target") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int target_size = tokens[i].size; ++i; for (int k = 0; k < target_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "node") == 0) { ++i; out_channel->target_node = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "path") == 0) { ++i; if (cgltf_json_strcmp(tokens+i, json_chunk, "translation") == 0) { out_channel->target_path = cgltf_animation_path_type_translation; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "rotation") == 0) { out_channel->target_path = cgltf_animation_path_type_rotation; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "scale") == 0) { out_channel->target_path = cgltf_animation_path_type_scale; } else if (cgltf_json_strcmp(tokens+i, json_chunk, "weights") == 0) { out_channel->target_path = cgltf_animation_path_type_weights; } ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_channel->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_channel->extensions_count, &out_channel->extensions); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_animation(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation* out_animation) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_animation->name); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "samplers") == 0) { i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_sampler), (void**)&out_animation->samplers, &out_animation->samplers_count); if (i < 0) { return i; } for (cgltf_size k = 0; k < out_animation->samplers_count; ++k) { i = cgltf_parse_json_animation_sampler(options, tokens, i, json_chunk, &out_animation->samplers[k]); if (i < 0) { return i; } } } else if (cgltf_json_strcmp(tokens+i, json_chunk, "channels") == 0) { i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_channel), (void**)&out_animation->channels, &out_animation->channels_count); if (i < 0) { return i; } for (cgltf_size k = 0; k < out_animation->channels_count; ++k) { i = cgltf_parse_json_animation_channel(options, tokens, i, json_chunk, &out_animation->channels[k]); if (i < 0) { return i; } } } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_animation->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_animation->extensions_count, &out_animation->extensions); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_animations(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_animation), (void**)&out_data->animations, &out_data->animations_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->animations_count; ++j) { i = cgltf_parse_json_animation(options, tokens, i, json_chunk, &out_data->animations[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_variant(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material_variant* out_variant) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_variant->name); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_variant->extras); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } return i; } static int cgltf_parse_json_variants(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material_variant), (void**)&out_data->variants, &out_data->variants_count); if (i < 0) { return i; } for (cgltf_size j = 0; j < out_data->variants_count; ++j) { i = cgltf_parse_json_variant(options, tokens, i, json_chunk, &out_data->variants[j]); if (i < 0) { return i; } } return i; } static int cgltf_parse_json_asset(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_asset* out_asset) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "copyright") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->copyright); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "generator") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->generator); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "version") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->version); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "minVersion") == 0) { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->min_version); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_asset->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_asset->extensions_count, &out_asset->extensions); } else { i = cgltf_skip_json(tokens, i+1); } if (i < 0) { return i; } } if (out_asset->version && CGLTF_ATOF(out_asset->version) < 2) { return CGLTF_ERROR_LEGACY; } return i; } cgltf_size cgltf_num_components(cgltf_type type) { switch (type) { case cgltf_type_vec2: return 2; case cgltf_type_vec3: return 3; case cgltf_type_vec4: return 4; case cgltf_type_mat2: return 4; case cgltf_type_mat3: return 9; case cgltf_type_mat4: return 16; case cgltf_type_invalid: case cgltf_type_scalar: default: return 1; } } cgltf_size cgltf_component_size(cgltf_component_type component_type) { switch (component_type) { case cgltf_component_type_r_8: case cgltf_component_type_r_8u: return 1; case cgltf_component_type_r_16: case cgltf_component_type_r_16u: return 2; case cgltf_component_type_r_32u: case cgltf_component_type_r_32f: return 4; case cgltf_component_type_invalid: default: return 0; } } cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type) { cgltf_size component_size = cgltf_component_size(component_type); if (type == cgltf_type_mat2 && component_size == 1) { return 8 * component_size; } else if (type == cgltf_type_mat3 && (component_size == 1 || component_size == 2)) { return 12 * component_size; } return component_size * cgltf_num_components(type); } static int cgltf_fixup_pointers(cgltf_data* out_data); static int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; ++i; for (int j = 0; j < size; ++j) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "asset") == 0) { i = cgltf_parse_json_asset(options, tokens, i + 1, json_chunk, &out_data->asset); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "meshes") == 0) { i = cgltf_parse_json_meshes(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "accessors") == 0) { i = cgltf_parse_json_accessors(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferViews") == 0) { i = cgltf_parse_json_buffer_views(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "buffers") == 0) { i = cgltf_parse_json_buffers(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "materials") == 0) { i = cgltf_parse_json_materials(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "images") == 0) { i = cgltf_parse_json_images(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "textures") == 0) { i = cgltf_parse_json_textures(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "samplers") == 0) { i = cgltf_parse_json_samplers(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "skins") == 0) { i = cgltf_parse_json_skins(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "cameras") == 0) { i = cgltf_parse_json_cameras(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "nodes") == 0) { i = cgltf_parse_json_nodes(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "scenes") == 0) { i = cgltf_parse_json_scenes(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "scene") == 0) { ++i; out_data->scene = CGLTF_PTRINDEX(cgltf_scene, cgltf_json_to_int(tokens + i, json_chunk)); ++i; } else if (cgltf_json_strcmp(tokens + i, json_chunk, "animations") == 0) { i = cgltf_parse_json_animations(options, tokens, i + 1, json_chunk, out_data); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_data->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); if(out_data->data_extensions) { return CGLTF_ERROR_JSON; } int extensions_size = tokens[i].size; out_data->data_extensions_count = 0; out_data->data_extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); if (!out_data->data_extensions) { return CGLTF_ERROR_NOMEM; } ++i; for (int k = 0; k < extensions_size; ++k) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_lights_punctual") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int data_size = tokens[i].size; ++i; for (int m = 0; m < data_size; ++m) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "lights") == 0) { i = cgltf_parse_json_lights(options, tokens, i + 1, json_chunk, out_data); } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } } else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_variants") == 0) { ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int data_size = tokens[i].size; ++i; for (int m = 0; m < data_size; ++m) { CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens + i, json_chunk, "variants") == 0) { i = cgltf_parse_json_variants(options, tokens, i + 1, json_chunk, out_data); } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } } else { i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_data->data_extensions[out_data->data_extensions_count++])); } if (i < 0) { return i; } } } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsUsed") == 0) { i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_used, &out_data->extensions_used_count); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsRequired") == 0) { i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_required, &out_data->extensions_required_count); } else { i = cgltf_skip_json(tokens, i + 1); } if (i < 0) { return i; } } return i; } cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data) { jsmn_parser parser = { 0, 0, 0 }; if (options->json_token_count == 0) { int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, NULL, 0); if (token_count <= 0) { return cgltf_result_invalid_json; } options->json_token_count = token_count; } jsmntok_t* tokens = (jsmntok_t*)options->memory.alloc_func(options->memory.user_data, sizeof(jsmntok_t) * (options->json_token_count + 1)); if (!tokens) { return cgltf_result_out_of_memory; } jsmn_init(&parser); int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, tokens, options->json_token_count); if (token_count <= 0) { options->memory.free_func(options->memory.user_data, tokens); return cgltf_result_invalid_json; } // this makes sure that we always have an UNDEFINED token at the end of the stream // for invalid JSON inputs this makes sure we don't perform out of bound reads of token data tokens[token_count].type = JSMN_UNDEFINED; cgltf_data* data = (cgltf_data*)options->memory.alloc_func(options->memory.user_data, sizeof(cgltf_data)); if (!data) { options->memory.free_func(options->memory.user_data, tokens); return cgltf_result_out_of_memory; } memset(data, 0, sizeof(cgltf_data)); data->memory = options->memory; data->file = options->file; int i = cgltf_parse_json_root(options, tokens, 0, json_chunk, data); options->memory.free_func(options->memory.user_data, tokens); if (i < 0) { cgltf_free(data); switch (i) { case CGLTF_ERROR_NOMEM: return cgltf_result_out_of_memory; case CGLTF_ERROR_LEGACY: return cgltf_result_legacy_gltf; default: return cgltf_result_invalid_gltf; } } if (cgltf_fixup_pointers(data) < 0) { cgltf_free(data); return cgltf_result_invalid_gltf; } data->json = (const char*)json_chunk; data->json_size = size; *out_data = data; return cgltf_result_success; } static int cgltf_fixup_pointers(cgltf_data* data) { for (cgltf_size i = 0; i < data->meshes_count; ++i) { for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) { CGLTF_PTRFIXUP(data->meshes[i].primitives[j].indices, data->accessors, data->accessors_count); CGLTF_PTRFIXUP(data->meshes[i].primitives[j].material, data->materials, data->materials_count); for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) { CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].attributes[k].data, data->accessors, data->accessors_count); } for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) { for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) { CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].targets[k].attributes[m].data, data->accessors, data->accessors_count); } } if (data->meshes[i].primitives[j].has_draco_mesh_compression) { CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.buffer_view, data->buffer_views, data->buffer_views_count); for (cgltf_size m = 0; m < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++m) { CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.attributes[m].data, data->accessors, data->accessors_count); } } for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k) { CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].mappings[k].material, data->materials, data->materials_count); } } } for (cgltf_size i = 0; i < data->accessors_count; ++i) { CGLTF_PTRFIXUP(data->accessors[i].buffer_view, data->buffer_views, data->buffer_views_count); if (data->accessors[i].is_sparse) { CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.indices_buffer_view, data->buffer_views, data->buffer_views_count); CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.values_buffer_view, data->buffer_views, data->buffer_views_count); } if (data->accessors[i].buffer_view) { data->accessors[i].stride = data->accessors[i].buffer_view->stride; } if (data->accessors[i].stride == 0) { data->accessors[i].stride = cgltf_calc_size(data->accessors[i].type, data->accessors[i].component_type); } } for (cgltf_size i = 0; i < data->textures_count; ++i) { CGLTF_PTRFIXUP(data->textures[i].image, data->images, data->images_count); CGLTF_PTRFIXUP(data->textures[i].basisu_image, data->images, data->images_count); CGLTF_PTRFIXUP(data->textures[i].webp_image, data->images, data->images_count); CGLTF_PTRFIXUP(data->textures[i].sampler, data->samplers, data->samplers_count); } for (cgltf_size i = 0; i < data->images_count; ++i) { CGLTF_PTRFIXUP(data->images[i].buffer_view, data->buffer_views, data->buffer_views_count); } for (cgltf_size i = 0; i < data->materials_count; ++i) { CGLTF_PTRFIXUP(data->materials[i].normal_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].emissive_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].occlusion_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.base_color_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.diffuse_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_roughness_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_normal_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].specular.specular_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].specular.specular_color_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].transmission.transmission_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].volume.thickness_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].sheen.sheen_color_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].sheen.sheen_roughness_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].iridescence.iridescence_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].iridescence.iridescence_thickness_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].diffuse_transmission.diffuse_transmission_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].diffuse_transmission.diffuse_transmission_color_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].anisotropy.anisotropy_texture.texture, data->textures, data->textures_count); } for (cgltf_size i = 0; i < data->buffer_views_count; ++i) { CGLTF_PTRFIXUP_REQ(data->buffer_views[i].buffer, data->buffers, data->buffers_count); if (data->buffer_views[i].has_meshopt_compression) { CGLTF_PTRFIXUP_REQ(data->buffer_views[i].meshopt_compression.buffer, data->buffers, data->buffers_count); } } for (cgltf_size i = 0; i < data->skins_count; ++i) { for (cgltf_size j = 0; j < data->skins[i].joints_count; ++j) { CGLTF_PTRFIXUP_REQ(data->skins[i].joints[j], data->nodes, data->nodes_count); } CGLTF_PTRFIXUP(data->skins[i].skeleton, data->nodes, data->nodes_count); CGLTF_PTRFIXUP(data->skins[i].inverse_bind_matrices, data->accessors, data->accessors_count); } for (cgltf_size i = 0; i < data->nodes_count; ++i) { for (cgltf_size j = 0; j < data->nodes[i].children_count; ++j) { CGLTF_PTRFIXUP_REQ(data->nodes[i].children[j], data->nodes, data->nodes_count); if (data->nodes[i].children[j]->parent) { return CGLTF_ERROR_JSON; } data->nodes[i].children[j]->parent = &data->nodes[i]; } CGLTF_PTRFIXUP(data->nodes[i].mesh, data->meshes, data->meshes_count); CGLTF_PTRFIXUP(data->nodes[i].skin, data->skins, data->skins_count); CGLTF_PTRFIXUP(data->nodes[i].camera, data->cameras, data->cameras_count); CGLTF_PTRFIXUP(data->nodes[i].light, data->lights, data->lights_count); if (data->nodes[i].has_mesh_gpu_instancing) { for (cgltf_size m = 0; m < data->nodes[i].mesh_gpu_instancing.attributes_count; ++m) { CGLTF_PTRFIXUP_REQ(data->nodes[i].mesh_gpu_instancing.attributes[m].data, data->accessors, data->accessors_count); } } } for (cgltf_size i = 0; i < data->scenes_count; ++i) { for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j) { CGLTF_PTRFIXUP_REQ(data->scenes[i].nodes[j], data->nodes, data->nodes_count); if (data->scenes[i].nodes[j]->parent) { return CGLTF_ERROR_JSON; } } } CGLTF_PTRFIXUP(data->scene, data->scenes, data->scenes_count); for (cgltf_size i = 0; i < data->animations_count; ++i) { for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j) { CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].input, data->accessors, data->accessors_count); CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].output, data->accessors, data->accessors_count); } for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) { CGLTF_PTRFIXUP_REQ(data->animations[i].channels[j].sampler, data->animations[i].samplers, data->animations[i].samplers_count); CGLTF_PTRFIXUP(data->animations[i].channels[j].target_node, data->nodes, data->nodes_count); } } return 0; } /* * -- jsmn.c start -- * Source: https://github.com/zserge/jsmn * License: MIT * * Copyright (c) 2010 Serge A. Zaitsev * 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. */ /** * Allocates a fresh unused token from the token pull. */ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *tok; if (parser->toknext >= num_tokens) { return NULL; } tok = &tokens[parser->toknext++]; tok->start = tok->end = -1; tok->size = 0; #ifdef JSMN_PARENT_LINKS tok->parent = -1; #endif return tok; } /** * Fills token type and boundaries. */ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, ptrdiff_t start, ptrdiff_t end) { token->type = type; token->start = start; token->end = end; token->size = 0; } /** * Fills next available token with JSON primitive. */ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; ptrdiff_t start; start = parser->pos; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { #ifndef JSMN_STRICT /* In strict mode primitive must be followed by "," or "}" or "]" */ case ':': #endif case '\t' : case '\r' : case '\n' : case ' ' : case ',' : case ']' : case '}' : goto found; } if (js[parser->pos] < 32 || js[parser->pos] >= 127) { parser->pos = start; return JSMN_ERROR_INVAL; } } #ifdef JSMN_STRICT /* In strict mode primitive must be followed by a comma/object/array */ parser->pos = start; return JSMN_ERROR_PART; #endif found: if (tokens == NULL) { parser->pos--; return 0; } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { parser->pos = start; return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif parser->pos--; return 0; } /** * Fills next token with JSON string. */ static int jsmn_parse_string(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; ptrdiff_t start = parser->pos; parser->pos++; /* Skip starting quote */ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c = js[parser->pos]; /* Quote: end of string */ if (c == '\"') { if (tokens == NULL) { return 0; } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { parser->pos = start; return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif return 0; } /* Backslash: Quoted symbol expected */ if (c == '\\' && parser->pos + 1 < len) { int i; parser->pos++; switch (js[parser->pos]) { /* Allowed escaped symbols */ case '\"': case '/' : case '\\' : case 'b' : case 'f' : case 'r' : case 'n' : case 't' : break; /* Allows escaped symbol \uXXXX */ case 'u': parser->pos++; for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { /* If it isn't a hex character we have an error */ if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ parser->pos = start; return JSMN_ERROR_INVAL; } parser->pos++; } parser->pos--; break; /* Unexpected symbol */ default: parser->pos = start; return JSMN_ERROR_INVAL; } } } parser->pos = start; return JSMN_ERROR_PART; } /** * Parse JSON string and fill tokens. */ static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens) { int r; int i; jsmntok_t *token; int count = parser->toknext; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c; jsmntype_t type; c = js[parser->pos]; switch (c) { case '{': case '[': count++; if (tokens == NULL) { break; } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) return JSMN_ERROR_NOMEM; if (parser->toksuper != -1) { tokens[parser->toksuper].size++; #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif } token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); token->start = parser->pos; parser->toksuper = parser->toknext - 1; break; case '}': case ']': if (tokens == NULL) break; type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); #ifdef JSMN_PARENT_LINKS if (parser->toknext < 1) { return JSMN_ERROR_INVAL; } token = &tokens[parser->toknext - 1]; for (;;) { if (token->start != -1 && token->end == -1) { if (token->type != type) { return JSMN_ERROR_INVAL; } token->end = parser->pos + 1; parser->toksuper = token->parent; break; } if (token->parent == -1) { if(token->type != type || parser->toksuper == -1) { return JSMN_ERROR_INVAL; } break; } token = &tokens[token->parent]; } #else for (i = parser->toknext - 1; i >= 0; i--) { token = &tokens[i]; if (token->start != -1 && token->end == -1) { if (token->type != type) { return JSMN_ERROR_INVAL; } parser->toksuper = -1; token->end = parser->pos + 1; break; } } /* Error if unmatched closing bracket */ if (i == -1) return JSMN_ERROR_INVAL; for (; i >= 0; i--) { token = &tokens[i]; if (token->start != -1 && token->end == -1) { parser->toksuper = i; break; } } #endif break; case '\"': r = jsmn_parse_string(parser, js, len, tokens, num_tokens); if (r < 0) return r; count++; if (parser->toksuper != -1 && tokens != NULL) tokens[parser->toksuper].size++; break; case '\t' : case '\r' : case '\n' : case ' ': break; case ':': parser->toksuper = parser->toknext - 1; break; case ',': if (tokens != NULL && parser->toksuper != -1 && tokens[parser->toksuper].type != JSMN_ARRAY && tokens[parser->toksuper].type != JSMN_OBJECT) { #ifdef JSMN_PARENT_LINKS parser->toksuper = tokens[parser->toksuper].parent; #else for (i = parser->toknext - 1; i >= 0; i--) { if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { if (tokens[i].start != -1 && tokens[i].end == -1) { parser->toksuper = i; break; } } } #endif } break; #ifdef JSMN_STRICT /* In strict mode primitives are: numbers and booleans */ case '-': case '0': case '1' : case '2': case '3' : case '4': case '5': case '6': case '7' : case '8': case '9': case 't': case 'f': case 'n' : /* And they must not be keys of the object */ if (tokens != NULL && parser->toksuper != -1) { jsmntok_t *t = &tokens[parser->toksuper]; if (t->type == JSMN_OBJECT || (t->type == JSMN_STRING && t->size != 0)) { return JSMN_ERROR_INVAL; } } #else /* In non-strict mode every unquoted value is a primitive */ default: #endif r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); if (r < 0) return r; count++; if (parser->toksuper != -1 && tokens != NULL) tokens[parser->toksuper].size++; break; #ifdef JSMN_STRICT /* Unexpected char in strict mode */ default: return JSMN_ERROR_INVAL; #endif } } if (tokens != NULL) { for (i = parser->toknext - 1; i >= 0; i--) { /* Unmatched opened object or array */ if (tokens[i].start != -1 && tokens[i].end == -1) { return JSMN_ERROR_PART; } } } return count; } /** * Creates a new parser based over a given buffer with an array of tokens * available. */ static void jsmn_init(jsmn_parser *parser) { parser->pos = 0; parser->toknext = 0; parser->toksuper = -1; } /* * -- jsmn.c end -- */ #endif /* #ifdef CGLTF_IMPLEMENTATION */ /* cgltf is distributed under MIT license: * * Copyright (c) 2018-2021 Johannes Kuhlmann * 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. */ ================================================ FILE: extern/fast_obj.h ================================================ /* * fast_obj * * Version 1.3 * * MIT License * * Copyright (c) 2018-2021 Richard Knight * * 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. * */ #ifndef FAST_OBJ_HDR #define FAST_OBJ_HDR #define FAST_OBJ_VERSION_MAJOR 1 #define FAST_OBJ_VERSION_MINOR 3 #define FAST_OBJ_VERSION ((FAST_OBJ_VERSION_MAJOR << 8) | FAST_OBJ_VERSION_MINOR) #include typedef struct { /* Texture name from .mtl file */ char* name; /* Resolved path to texture */ char* path; } fastObjTexture; typedef struct { /* Material name */ char* name; /* Parameters */ float Ka[3]; /* Ambient */ float Kd[3]; /* Diffuse */ float Ks[3]; /* Specular */ float Ke[3]; /* Emission */ float Kt[3]; /* Transmittance */ float Ns; /* Shininess */ float Ni; /* Index of refraction */ float Tf[3]; /* Transmission filter */ float d; /* Disolve (alpha) */ int illum; /* Illumination model */ /* Set for materials that don't come from the associated mtllib */ int fallback; /* Texture map indices in fastObjMesh textures array */ unsigned int map_Ka; unsigned int map_Kd; unsigned int map_Ks; unsigned int map_Ke; unsigned int map_Kt; unsigned int map_Ns; unsigned int map_Ni; unsigned int map_d; unsigned int map_bump; } fastObjMaterial; /* Allows user override to bigger indexable array */ #ifndef FAST_OBJ_UINT_TYPE #define FAST_OBJ_UINT_TYPE unsigned int #endif typedef FAST_OBJ_UINT_TYPE fastObjUInt; typedef struct { fastObjUInt p; fastObjUInt t; fastObjUInt n; } fastObjIndex; typedef struct { /* Group name */ char* name; /* Number of faces */ unsigned int face_count; /* First face in fastObjMesh face_* arrays */ unsigned int face_offset; /* First index in fastObjMesh indices array */ unsigned int index_offset; } fastObjGroup; /* Note: a dummy zero-initialized value is added to the first index of the positions, texcoords, normals and textures arrays. Hence, valid indices into these arrays start from 1, with an index of 0 indicating that the attribute is not present. */ typedef struct { /* Vertex data */ unsigned int position_count; float* positions; unsigned int texcoord_count; float* texcoords; unsigned int normal_count; float* normals; unsigned int color_count; float* colors; /* Face data: one element for each face */ unsigned int face_count; unsigned int* face_vertices; unsigned int* face_materials; unsigned char* face_lines; /* Index data: one element for each face vertex */ unsigned int index_count; fastObjIndex* indices; /* Materials */ unsigned int material_count; fastObjMaterial* materials; /* Texture maps */ unsigned int texture_count; fastObjTexture* textures; /* Mesh objects ('o' tag in .obj file) */ unsigned int object_count; fastObjGroup* objects; /* Mesh groups ('g' tag in .obj file) */ unsigned int group_count; fastObjGroup* groups; } fastObjMesh; typedef struct { void* (*file_open)(const char* path, void* user_data); void (*file_close)(void* file, void* user_data); size_t (*file_read)(void* file, void* dst, size_t bytes, void* user_data); unsigned long (*file_size)(void* file, void* user_data); } fastObjCallbacks; #ifdef __cplusplus extern "C" { #endif fastObjMesh* fast_obj_read(const char* path); fastObjMesh* fast_obj_read_with_callbacks(const char* path, const fastObjCallbacks* callbacks, void* user_data); void fast_obj_destroy(fastObjMesh* mesh); #ifdef __cplusplus } #endif #endif #ifdef FAST_OBJ_IMPLEMENTATION #include #include #ifndef FAST_OBJ_REALLOC #define FAST_OBJ_REALLOC realloc #endif #ifndef FAST_OBJ_FREE #define FAST_OBJ_FREE free #endif #ifdef _WIN32 #define FAST_OBJ_SEPARATOR '\\' #define FAST_OBJ_OTHER_SEP '/' #else #define FAST_OBJ_SEPARATOR '/' #define FAST_OBJ_OTHER_SEP '\\' #endif /* Size of buffer to read into */ #define BUFFER_SIZE 65536 /* Max supported power when parsing float */ #define MAX_POWER 20 typedef struct { /* Final mesh */ fastObjMesh* mesh; /* Current object/group */ fastObjGroup object; fastObjGroup group; /* Current material index */ unsigned int material; /* Current line in file */ unsigned int line; /* Base path for materials/textures */ char* base; } fastObjData; static const double POWER_10_POS[MAX_POWER] = { 1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, 1.0e7, 1.0e8, 1.0e9, 1.0e10, 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18, 1.0e19, }; static const double POWER_10_NEG[MAX_POWER] = { 1.0e0, 1.0e-1, 1.0e-2, 1.0e-3, 1.0e-4, 1.0e-5, 1.0e-6, 1.0e-7, 1.0e-8, 1.0e-9, 1.0e-10, 1.0e-11, 1.0e-12, 1.0e-13, 1.0e-14, 1.0e-15, 1.0e-16, 1.0e-17, 1.0e-18, 1.0e-19, }; static void* memory_realloc(void* ptr, size_t bytes) { return FAST_OBJ_REALLOC(ptr, bytes); } static void memory_dealloc(void* ptr) { FAST_OBJ_FREE(ptr); } #define array_clean(_arr) ((_arr) ? memory_dealloc(_array_header(_arr)), 0 : 0) #define array_push(_arr, _val) (_array_mgrow(_arr, 1) ? ((_arr)[_array_size(_arr)++] = (_val), _array_size(_arr) - 1) : 0) #define array_size(_arr) ((_arr) ? _array_size(_arr) : 0) #define array_capacity(_arr) ((_arr) ? _array_capacity(_arr) : 0) #define array_empty(_arr) (array_size(_arr) == 0) #define _array_header(_arr) ((fastObjUInt*)(_arr)-2) #define _array_size(_arr) (_array_header(_arr)[0]) #define _array_capacity(_arr) (_array_header(_arr)[1]) #define _array_ngrow(_arr, _n) ((_arr) == 0 || (_array_size(_arr) + (_n) >= _array_capacity(_arr))) #define _array_mgrow(_arr, _n) (_array_ngrow(_arr, _n) ? (_array_grow(_arr, _n) != 0) : 1) #define _array_grow(_arr, _n) (*((void**)&(_arr)) = array_realloc(_arr, _n, sizeof(*(_arr)))) static void* array_realloc(void* ptr, fastObjUInt n, fastObjUInt b) { fastObjUInt sz = array_size(ptr); fastObjUInt nsz = sz + n; fastObjUInt cap = array_capacity(ptr); fastObjUInt ncap = cap + cap / 2; fastObjUInt* r; if (ncap < nsz) ncap = nsz; ncap = (ncap + 15) & ~15u; r = (fastObjUInt*)(memory_realloc(ptr ? _array_header(ptr) : 0, (size_t)b * ncap + 2 * sizeof(fastObjUInt))); if (!r) return 0; r[0] = sz; r[1] = ncap; return (r + 2); } static void* file_open(const char* path, void* user_data) { (void)(user_data); return fopen(path, "rb"); } static void file_close(void* file, void* user_data) { FILE* f; (void)(user_data); f = (FILE*)(file); fclose(f); } static size_t file_read(void* file, void* dst, size_t bytes, void* user_data) { FILE* f; (void)(user_data); f = (FILE*)(file); return fread(dst, 1, bytes, f); } static unsigned long file_size(void* file, void* user_data) { FILE* f; long p; long n; (void)(user_data); f = (FILE*)(file); p = ftell(f); fseek(f, 0, SEEK_END); n = ftell(f); fseek(f, p, SEEK_SET); if (n > 0) return (unsigned long)(n); else return 0; } static char* string_copy(const char* s, const char* e) { size_t n; char* p; n = (size_t)(e - s); p = (char*)(memory_realloc(0, n + 1)); if (p) { memcpy(p, s, n); p[n] = '\0'; } return p; } static char* string_substr(const char* s, size_t a, size_t b) { return string_copy(s + a, s + b); } static char* string_concat(const char* a, const char* s, const char* e) { size_t an; size_t sn; char* p; an = a ? strlen(a) : 0; sn = (size_t)(e - s); p = (char*)(memory_realloc(0, an + sn + 1)); if (p) { if (a) memcpy(p, a, an); memcpy(p + an, s, sn); p[an + sn] = '\0'; } return p; } static int string_equal(const char* a, const char* s, const char* e) { size_t an = strlen(a); size_t sn = (size_t)(e - s); return an == sn && memcmp(a, s, an) == 0; } static void string_fix_separators(char* s) { while (*s) { if (*s == FAST_OBJ_OTHER_SEP) *s = FAST_OBJ_SEPARATOR; s++; } } static int is_whitespace(char c) { return (c == ' ' || c == '\t' || c == '\r'); } static int is_newline(char c) { return (c == '\n'); } static int is_digit(char c) { return (c >= '0' && c <= '9'); } static int is_exponent(char c) { return (c == 'e' || c == 'E'); } static const char* skip_name(const char* ptr) { const char* s = ptr; while (!is_newline(*ptr)) ptr++; while (ptr > s && is_whitespace(*(ptr - 1))) ptr--; return ptr; } static const char* skip_whitespace(const char* ptr) { while (is_whitespace(*ptr)) ptr++; return ptr; } static const char* skip_line(const char* ptr) { while (!is_newline(*ptr++)) ; return ptr; } static fastObjGroup object_default(void) { fastObjGroup object; object.name = 0; object.face_count = 0; object.face_offset = 0; object.index_offset = 0; return object; } static void object_clean(fastObjGroup* object) { memory_dealloc(object->name); } static void flush_object(fastObjData* data) { /* Add object if not empty */ if (data->object.face_count > 0) array_push(data->mesh->objects, data->object); else object_clean(&data->object); /* Reset for more data */ data->object = object_default(); data->object.face_offset = array_size(data->mesh->face_vertices); data->object.index_offset = array_size(data->mesh->indices); } static fastObjGroup group_default(void) { fastObjGroup group; group.name = 0; group.face_count = 0; group.face_offset = 0; group.index_offset = 0; return group; } static void group_clean(fastObjGroup* group) { memory_dealloc(group->name); } static void flush_group(fastObjData* data) { /* Add group if not empty */ if (data->group.face_count > 0) array_push(data->mesh->groups, data->group); else group_clean(&data->group); /* Reset for more data */ data->group = group_default(); data->group.face_offset = array_size(data->mesh->face_vertices); data->group.index_offset = array_size(data->mesh->indices); } static const char* parse_int(const char* ptr, int* val) { int sign; int num; if (*ptr == '-') { sign = -1; ptr++; } else { sign = +1; } num = 0; while (is_digit(*ptr)) num = 10 * num + (*ptr++ - '0'); *val = sign * num; return ptr; } static const char* parse_float(const char* ptr, float* val) { double sign; double num; double fra; double div; unsigned int eval; const double* powers; ptr = skip_whitespace(ptr); switch (*ptr) { case '+': sign = 1.0; ptr++; break; case '-': sign = -1.0; ptr++; break; default: sign = 1.0; break; } num = 0.0; while (is_digit(*ptr)) num = 10.0 * num + (double)(*ptr++ - '0'); if (*ptr == '.') ptr++; fra = 0.0; div = 1.0; while (is_digit(*ptr)) { fra = 10.0 * fra + (double)(*ptr++ - '0'); div *= 10.0; } num += fra / div; if (is_exponent(*ptr)) { ptr++; switch (*ptr) { case '+': powers = POWER_10_POS; ptr++; break; case '-': powers = POWER_10_NEG; ptr++; break; default: powers = POWER_10_POS; break; } eval = 0; while (is_digit(*ptr)) eval = 10 * eval + (*ptr++ - '0'); num *= (eval >= MAX_POWER) ? 0.0 : powers[eval]; } *val = (float)(sign * num); return ptr; } static const char* parse_vertex(fastObjData* data, const char* ptr) { unsigned int ii; float v; for (ii = 0; ii < 3; ii++) { ptr = parse_float(ptr, &v); array_push(data->mesh->positions, v); } ptr = skip_whitespace(ptr); if (!is_newline(*ptr)) { /* Fill the colors array until it matches the size of the positions array */ for (ii = array_size(data->mesh->colors); ii < array_size(data->mesh->positions) - 3; ++ii) { array_push(data->mesh->colors, 1.0f); } for (ii = 0; ii < 3; ++ii) { ptr = parse_float(ptr, &v); array_push(data->mesh->colors, v); } } return ptr; } static const char* parse_texcoord(fastObjData* data, const char* ptr) { unsigned int ii; float v; for (ii = 0; ii < 2; ii++) { ptr = parse_float(ptr, &v); array_push(data->mesh->texcoords, v); } return ptr; } static const char* parse_normal(fastObjData* data, const char* ptr) { unsigned int ii; float v; for (ii = 0; ii < 3; ii++) { ptr = parse_float(ptr, &v); array_push(data->mesh->normals, v); } return ptr; } static const char* parse_face(fastObjData* data, const char* ptr, unsigned char line) { unsigned int count; fastObjIndex vn; int v; int t; int n; ptr = skip_whitespace(ptr); count = 0; while (!is_newline(*ptr)) { v = 0; t = 0; n = 0; ptr = parse_int(ptr, &v); if (*ptr == '/') { ptr++; if (*ptr != '/') ptr = parse_int(ptr, &t); if (*ptr == '/') { ptr++; ptr = parse_int(ptr, &n); } } if (v < 0) vn.p = (array_size(data->mesh->positions) / 3) - (fastObjUInt)(-v); else if (v > 0) vn.p = (fastObjUInt)(v); else return ptr; /* Skip lines with no valid vertex index */ if (t < 0) vn.t = (array_size(data->mesh->texcoords) / 2) - (fastObjUInt)(-t); else if (t > 0) vn.t = (fastObjUInt)(t); else vn.t = 0; if (n < 0) vn.n = (array_size(data->mesh->normals) / 3) - (fastObjUInt)(-n); else if (n > 0) vn.n = (fastObjUInt)(n); else vn.n = 0; array_push(data->mesh->indices, vn); count++; ptr = skip_whitespace(ptr); } array_push(data->mesh->face_vertices, count); array_push(data->mesh->face_materials, data->material); if (line || data->mesh->face_lines) { /* when line info exists, ensure it uses aligned indexing with other face data */ size_t skipped = array_size(data->mesh->face_vertices) - array_size(data->mesh->face_lines); while (--skipped > 0) array_push(data->mesh->face_lines, 0); array_push(data->mesh->face_lines, line); } data->group.face_count++; data->object.face_count++; return ptr; } static const char* parse_object(fastObjData* data, const char* ptr) { const char* s; const char* e; ptr = skip_whitespace(ptr); s = ptr; ptr = skip_name(ptr); e = ptr; flush_object(data); data->object.name = string_copy(s, e); return ptr; } static const char* parse_group(fastObjData* data, const char* ptr) { const char* s; const char* e; ptr = skip_whitespace(ptr); s = ptr; ptr = skip_name(ptr); e = ptr; flush_group(data); data->group.name = string_copy(s, e); return ptr; } static fastObjTexture map_default(void) { fastObjTexture map; map.name = 0; map.path = 0; return map; } static fastObjMaterial mtl_default(void) { fastObjMaterial mtl; mtl.name = 0; mtl.Ka[0] = 0.0; mtl.Ka[1] = 0.0; mtl.Ka[2] = 0.0; mtl.Kd[0] = 1.0; mtl.Kd[1] = 1.0; mtl.Kd[2] = 1.0; mtl.Ks[0] = 0.0; mtl.Ks[1] = 0.0; mtl.Ks[2] = 0.0; mtl.Ke[0] = 0.0; mtl.Ke[1] = 0.0; mtl.Ke[2] = 0.0; mtl.Kt[0] = 0.0; mtl.Kt[1] = 0.0; mtl.Kt[2] = 0.0; mtl.Ns = 1.0; mtl.Ni = 1.0; mtl.Tf[0] = 1.0; mtl.Tf[1] = 1.0; mtl.Tf[2] = 1.0; mtl.d = 1.0; mtl.illum = 1; mtl.fallback = 0; mtl.map_Ka = 0; mtl.map_Kd = 0; mtl.map_Ks = 0; mtl.map_Ke = 0; mtl.map_Kt = 0; mtl.map_Ns = 0; mtl.map_Ni = 0; mtl.map_d = 0; mtl.map_bump = 0; return mtl; } static const char* parse_usemtl(fastObjData* data, const char* ptr) { const char* s; const char* e; unsigned int idx; fastObjMaterial* mtl; ptr = skip_whitespace(ptr); /* Parse the material name */ s = ptr; ptr = skip_name(ptr); e = ptr; /* Find an existing material with the same name */ idx = 0; while (idx < array_size(data->mesh->materials)) { mtl = &data->mesh->materials[idx]; if (mtl->name && string_equal(mtl->name, s, e)) break; idx++; } /* If doesn't exist, create a default one with this name Note: this case happens when OBJ doesn't have its MTL */ if (idx == array_size(data->mesh->materials)) { fastObjMaterial new_mtl = mtl_default(); new_mtl.name = string_copy(s, e); new_mtl.fallback = 1; array_push(data->mesh->materials, new_mtl); } data->material = idx; return ptr; } static void map_clean(fastObjTexture* map) { memory_dealloc(map->name); memory_dealloc(map->path); } static void mtl_clean(fastObjMaterial* mtl) { memory_dealloc(mtl->name); } static const char* read_mtl_int(const char* p, int* v) { return parse_int(p, v); } static const char* read_mtl_single(const char* p, float* v) { return parse_float(p, v); } static const char* read_mtl_triple(const char* p, float v[3]) { p = read_mtl_single(p, &v[0]); p = read_mtl_single(p, &v[1]); p = read_mtl_single(p, &v[2]); return p; } static const char* read_map(fastObjData* data, const char* ptr, unsigned int* idx) { const char* s; const char* e; fastObjTexture* map; ptr = skip_whitespace(ptr); /* Don't support options at present */ if (*ptr == '-') return ptr; /* Read name */ s = ptr; ptr = skip_name(ptr); e = ptr; /* Try to find an existing texture map with the same name */ *idx = 1; /* skip dummy at index 0 */ while (*idx < array_size(data->mesh->textures)) { map = &data->mesh->textures[*idx]; if (map->name && string_equal(map->name, s, e)) break; (*idx)++; } /* Add it to the texture array if it didn't already exist */ if (*idx == array_size(data->mesh->textures)) { fastObjTexture new_map = map_default(); new_map.name = string_copy(s, e); new_map.path = string_concat(data->base, s, e); string_fix_separators(new_map.path); array_push(data->mesh->textures, new_map); } return e; } static int read_mtllib(fastObjData* data, void* file, const fastObjCallbacks* callbacks, void* user_data) { unsigned long n; const char* s; char* contents; size_t l; const char* p; const char* e; int found_d; fastObjMaterial mtl; /* Read entire file */ n = callbacks->file_size(file, user_data); contents = (char*)(memory_realloc(0, n + 1)); if (!contents) return 0; l = callbacks->file_read(file, contents, n, user_data); contents[l] = '\n'; mtl = mtl_default(); found_d = 0; p = contents; e = contents + l; while (p < e) { p = skip_whitespace(p); switch (*p) { case 'n': p++; if (p[0] == 'e' && p[1] == 'w' && p[2] == 'm' && p[3] == 't' && p[4] == 'l' && is_whitespace(p[5])) { /* Push previous material (if there is one) */ if (mtl.name) { array_push(data->mesh->materials, mtl); mtl = mtl_default(); } /* Read name */ p += 5; while (is_whitespace(*p)) p++; s = p; p = skip_name(p); mtl.name = string_copy(s, p); } break; case 'K': if (p[1] == 'a') p = read_mtl_triple(p + 2, mtl.Ka); else if (p[1] == 'd') p = read_mtl_triple(p + 2, mtl.Kd); else if (p[1] == 's') p = read_mtl_triple(p + 2, mtl.Ks); else if (p[1] == 'e') p = read_mtl_triple(p + 2, mtl.Ke); else if (p[1] == 't') p = read_mtl_triple(p + 2, mtl.Kt); break; case 'N': if (p[1] == 's') p = read_mtl_single(p + 2, &mtl.Ns); else if (p[1] == 'i') p = read_mtl_single(p + 2, &mtl.Ni); break; case 'T': if (p[1] == 'r') { float Tr; p = read_mtl_single(p + 2, &Tr); if (!found_d) { /* Ignore Tr if we've already read d */ mtl.d = 1.0f - Tr; } } else if (p[1] == 'f') p = read_mtl_triple(p + 2, mtl.Tf); break; case 'd': if (is_whitespace(p[1])) { p = read_mtl_single(p + 1, &mtl.d); found_d = 1; } break; case 'i': p++; if (p[0] == 'l' && p[1] == 'l' && p[2] == 'u' && p[3] == 'm' && is_whitespace(p[4])) { p = read_mtl_int(p + 4, &mtl.illum); } break; case 'm': p++; if (p[0] == 'a' && p[1] == 'p' && p[2] == '_') { p += 3; if (*p == 'K') { p++; if (is_whitespace(p[1])) { if (*p == 'a') p = read_map(data, p + 1, &mtl.map_Ka); else if (*p == 'd') p = read_map(data, p + 1, &mtl.map_Kd); else if (*p == 's') p = read_map(data, p + 1, &mtl.map_Ks); else if (*p == 'e') p = read_map(data, p + 1, &mtl.map_Ke); else if (*p == 't') p = read_map(data, p + 1, &mtl.map_Kt); } } else if (*p == 'N') { p++; if (is_whitespace(p[1])) { if (*p == 's') p = read_map(data, p + 1, &mtl.map_Ns); else if (*p == 'i') p = read_map(data, p + 1, &mtl.map_Ni); } } else if (*p == 'd') { p++; if (is_whitespace(*p)) p = read_map(data, p, &mtl.map_d); } else if ((p[0] == 'b' || p[0] == 'B') && p[1] == 'u' && p[2] == 'm' && p[3] == 'p' && is_whitespace(p[4])) { p = read_map(data, p + 4, &mtl.map_bump); } } break; case '#': break; } p = skip_line(p); } /* Push final material */ if (mtl.name) array_push(data->mesh->materials, mtl); memory_dealloc(contents); return 1; } static const char* parse_mtllib(fastObjData* data, const char* ptr, const fastObjCallbacks* callbacks, void* user_data) { const char* s; const char* e; char* lib; void* file; ptr = skip_whitespace(ptr); s = ptr; ptr = skip_name(ptr); e = ptr; lib = string_concat(data->base, s, e); if (lib) { string_fix_separators(lib); file = callbacks->file_open(lib, user_data); if (file) { read_mtllib(data, file, callbacks, user_data); callbacks->file_close(file, user_data); } memory_dealloc(lib); } return ptr; } static void parse_buffer(fastObjData* data, const char* ptr, const char* end, const fastObjCallbacks* callbacks, void* user_data) { const char* p; p = ptr; while (p != end) { p = skip_whitespace(p); switch (*p) { case 'v': p++; switch (*p++) { case ' ': case '\t': p = parse_vertex(data, p); break; case 't': p = parse_texcoord(data, p); break; case 'n': p = parse_normal(data, p); break; default: p--; /* roll p++ back in case *p was a newline */ } break; case 'f': p++; switch (*p++) { case ' ': case '\t': p = parse_face(data, p, 0); break; default: p--; /* roll p++ back in case *p was a newline */ } break; case 'l': p++; switch (*p++) { case ' ': case '\t': p = parse_face(data, p, 1); break; default: p--; /* roll p++ back in case *p was a newline */ } break; case 'o': p++; switch (*p++) { case ' ': case '\t': p = parse_object(data, p); break; default: p--; /* roll p++ back in case *p was a newline */ } break; case 'g': p++; switch (*p++) { case ' ': case '\t': p = parse_group(data, p); break; default: p--; /* roll p++ back in case *p was a newline */ } break; case 'm': p++; if (p[0] == 't' && p[1] == 'l' && p[2] == 'l' && p[3] == 'i' && p[4] == 'b' && is_whitespace(p[5])) p = parse_mtllib(data, p + 5, callbacks, user_data); break; case 'u': p++; if (p[0] == 's' && p[1] == 'e' && p[2] == 'm' && p[3] == 't' && p[4] == 'l' && is_whitespace(p[5])) p = parse_usemtl(data, p + 5); break; case '#': break; } p = skip_line(p); data->line++; } if (array_size(data->mesh->colors) > 0) { /* Fill the remaining slots in the colors array */ unsigned int ii; for (ii = array_size(data->mesh->colors); ii < array_size(data->mesh->positions); ++ii) { array_push(data->mesh->colors, 1.0f); } } } void fast_obj_destroy(fastObjMesh* m) { unsigned int ii; for (ii = 0; ii < array_size(m->objects); ii++) object_clean(&m->objects[ii]); for (ii = 0; ii < array_size(m->groups); ii++) group_clean(&m->groups[ii]); for (ii = 0; ii < array_size(m->materials); ii++) mtl_clean(&m->materials[ii]); for (ii = 0; ii < array_size(m->textures); ii++) map_clean(&m->textures[ii]); array_clean(m->positions); array_clean(m->texcoords); array_clean(m->normals); array_clean(m->colors); array_clean(m->face_vertices); array_clean(m->face_materials); array_clean(m->face_lines); array_clean(m->indices); array_clean(m->objects); array_clean(m->groups); array_clean(m->materials); array_clean(m->textures); memory_dealloc(m); } fastObjMesh* fast_obj_read(const char* path) { fastObjCallbacks callbacks; callbacks.file_open = file_open; callbacks.file_close = file_close; callbacks.file_read = file_read; callbacks.file_size = file_size; return fast_obj_read_with_callbacks(path, &callbacks, 0); } fastObjMesh* fast_obj_read_with_callbacks(const char* path, const fastObjCallbacks* callbacks, void* user_data) { fastObjData data; fastObjMesh* m; void* file; char* buffer; char* start; char* end; char* last; fastObjUInt read; fastObjUInt bytes; /* Check if callbacks are valid */ if(!callbacks) return 0; /* Open file */ file = callbacks->file_open(path, user_data); if (!file) return 0; /* Empty mesh */ m = (fastObjMesh*)(memory_realloc(0, sizeof(fastObjMesh))); if (!m) return 0; m->positions = 0; m->texcoords = 0; m->normals = 0; m->colors = 0; m->face_vertices = 0; m->face_materials = 0; m->face_lines = 0; m->indices = 0; m->materials = 0; m->textures = 0; m->objects = 0; m->groups = 0; /* Add dummy position/texcoord/normal/texture */ array_push(m->positions, 0.0f); array_push(m->positions, 0.0f); array_push(m->positions, 0.0f); array_push(m->texcoords, 0.0f); array_push(m->texcoords, 0.0f); array_push(m->normals, 0.0f); array_push(m->normals, 0.0f); array_push(m->normals, 1.0f); array_push(m->textures, map_default()); /* Data needed during parsing */ data.mesh = m; data.object = object_default(); data.group = group_default(); data.material = 0; data.line = 1; data.base = 0; /* Find base path for materials/textures */ { const char* sep1 = strrchr(path, FAST_OBJ_SEPARATOR); const char* sep2 = strrchr(path, FAST_OBJ_OTHER_SEP); /* Use the last separator in the path */ const char* sep = sep2 && (!sep1 || sep1 < sep2) ? sep2 : sep1; if (sep) data.base = string_substr(path, 0, sep - path + 1); } /* Create buffer for reading file */ buffer = (char*)(memory_realloc(0, 2 * BUFFER_SIZE * sizeof(char))); if (!buffer) return 0; start = buffer; for (;;) { /* Read another buffer's worth from file */ read = (fastObjUInt)(callbacks->file_read(file, start, BUFFER_SIZE, user_data)); if (read == 0 && start == buffer) break; /* Ensure buffer ends in a newline */ if (read < BUFFER_SIZE) { if (read == 0 || start[read - 1] != '\n') start[read++] = '\n'; } end = start + read; if (end == buffer) break; /* Find last new line */ last = end; while (last > buffer) { last--; if (*last == '\n') break; } /* Check there actually is a new line */ if (*last != '\n') break; last++; /* Process buffer */ parse_buffer(&data, buffer, last, callbacks, user_data); /* Copy overflow for next buffer */ bytes = (fastObjUInt)(end - last); memmove(buffer, last, bytes); start = buffer + bytes; } /* Flush final object/group */ flush_object(&data); object_clean(&data.object); flush_group(&data); group_clean(&data.group); m->position_count = array_size(m->positions) / 3; m->texcoord_count = array_size(m->texcoords) / 2; m->normal_count = array_size(m->normals) / 3; m->color_count = array_size(m->colors) / 3; m->face_count = array_size(m->face_vertices); m->index_count = array_size(m->indices); m->material_count = array_size(m->materials); m->texture_count = array_size(m->textures); m->object_count = array_size(m->objects); m->group_count = array_size(m->groups); /* Clean up */ memory_dealloc(buffer); memory_dealloc(data.base); callbacks->file_close(file, user_data); return m; } #endif ================================================ FILE: extern/sdefl.h ================================================ /*# Small Deflate `sdefl` is a small bare bone lossless compression library in ANSI C (ISO C90) which implements the Deflate (RFC 1951) compressed data format specification standard. It is mainly tuned to get as much speed and compression ratio from as little code as needed to keep the implementation as concise as possible. ## Features - Portable single header and source file duo written in ANSI C (ISO C90) - Dual license with either MIT or public domain - Small implementation - Deflate: 525 LoC - Inflate: 320 LoC - Webassembly: - Deflate ~3.7 KB (~2.2KB compressed) - Inflate ~3.6 KB (~2.2KB compressed) ## Usage: This file behaves differently depending on what symbols you define before including it. Header-File mode: If you do not define `SDEFL_IMPLEMENTATION` before including this file, it will operate in header only mode. In this mode it declares all used structs and the API of the library without including the implementation of the library. Implementation mode: If you define `SDEFL_IMPLEMENTATION` before including this file, it will compile the implementation . Make sure that you only include this file implementation in *one* C or C++ file to prevent collisions. ### Benchmark | Compressor name | Compression| Decompress.| Compr. size | Ratio | | ------------------------| -----------| -----------| ----------- | ----- | | miniz 1.0 -1 | 122 MB/s | 208 MB/s | 48510028 | 48.51 | | miniz 1.0 -6 | 27 MB/s | 260 MB/s | 36513697 | 36.51 | | miniz 1.0 -9 | 23 MB/s | 261 MB/s | 36460101 | 36.46 | | zlib 1.2.11 -1 | 72 MB/s | 307 MB/s | 42298774 | 42.30 | | zlib 1.2.11 -6 | 24 MB/s | 313 MB/s | 36548921 | 36.55 | | zlib 1.2.11 -9 | 20 MB/s | 314 MB/s | 36475792 | 36.48 | | sdefl 1.0 -0 | 127 MB/s | 355 MB/s | 40004116 | 39.88 | | sdefl 1.0 -1 | 111 MB/s | 413 MB/s | 38940674 | 38.82 | | sdefl 1.0 -5 | 45 MB/s | 436 MB/s | 36577183 | 36.46 | | sdefl 1.0 -7 | 38 MB/s | 432 MB/s | 36523781 | 36.41 | | libdeflate 1.3 -1 | 147 MB/s | 667 MB/s | 39597378 | 39.60 | | libdeflate 1.3 -6 | 69 MB/s | 689 MB/s | 36648318 | 36.65 | | libdeflate 1.3 -9 | 13 MB/s | 672 MB/s | 35197141 | 35.20 | | libdeflate 1.3 -12 | 8.13 MB/s | 670 MB/s | 35100568 | 35.10 | ### Compression Results on the [Silesia compression corpus](http://sun.aei.polsl.pl/~sdeor/index.php?page=silesia): | File | Original | `sdefl 0` | `sdefl 5` | `sdefl 7` | | --------| -----------| -------------| ---------- | ------------| | dickens | 10.192.446 | 4,260,187 | 3,845,261 | 3,833,657 | | mozilla | 51.220.480 | 20,774,706 | 19,607,009 | 19,565,867 | | mr | 9.970.564 | 3,860,531 | 3,673,460 | 3,665,627 | | nci | 33.553.445 | 4,030,283 | 3,094,526 | 3,006,075 | | ooffice | 6.152.192 | 3,320,063 | 3,186,373 | 3,183,815 | | osdb | 10.085.684 | 3,919,646 | 3,649,510 | 3,649,477 | | reymont | 6.627.202 | 2,263,378 | 1,857,588 | 1,827,237 | | samba | 21.606.400 | 6,121,797 | 5,462,670 | 5,450,762 | | sao | 7.251.944 | 5,612,421 | 5,485,380 | 5,481,765 | | webster | 41.458.703 | 13,972,648 | 12,059,432 | 11,991,421 | | xml | 5.345.280 | 886,620 | 674,009 | 662,141 | | x-ray | 8.474.240 | 6,304,655 | 6,244,779 | 6,244,779 | ## License ``` ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2020-2023 Micha Mettke 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. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. 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 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. ------------------------------------------------------------------------------ ``` */ #ifndef SDEFL_H_INCLUDED #define SDEFL_H_INCLUDED #ifdef __cplusplus extern "C" { #endif #define SDEFL_MAX_OFF (1 << 15) #define SDEFL_WIN_SIZ SDEFL_MAX_OFF #define SDEFL_WIN_MSK (SDEFL_WIN_SIZ-1) #define SDEFL_HASH_BITS 15 #define SDEFL_HASH_SIZ (1 << SDEFL_HASH_BITS) #define SDEFL_HASH_MSK (SDEFL_HASH_SIZ-1) #define SDEFL_MIN_MATCH 4 #define SDEFL_BLK_MAX (256*1024) #define SDEFL_SEQ_SIZ ((SDEFL_BLK_MAX+2)/3) #define SDEFL_SYM_MAX (288) #define SDEFL_OFF_MAX (32) #define SDEFL_PRE_MAX (19) #define SDEFL_LVL_MIN 0 #define SDEFL_LVL_DEF 5 #define SDEFL_LVL_MAX 8 struct sdefl_freq { unsigned lit[SDEFL_SYM_MAX]; unsigned off[SDEFL_OFF_MAX]; }; struct sdefl_code_words { unsigned lit[SDEFL_SYM_MAX]; unsigned off[SDEFL_OFF_MAX]; }; struct sdefl_lens { unsigned char lit[SDEFL_SYM_MAX]; unsigned char off[SDEFL_OFF_MAX]; }; struct sdefl_codes { struct sdefl_code_words word; struct sdefl_lens len; }; struct sdefl_seqt { int off, len; }; struct sdefl { int bits, bitcnt; int tbl[SDEFL_HASH_SIZ]; int prv[SDEFL_WIN_SIZ]; int seq_cnt; struct sdefl_seqt seq[SDEFL_SEQ_SIZ]; struct sdefl_freq freq; struct sdefl_codes cod; }; extern int sdefl_bound(int in_len); extern int sdeflate(struct sdefl *s, void *o, const void *i, int n, int lvl); extern int zsdeflate(struct sdefl *s, void *o, const void *i, int n, int lvl); #ifdef __cplusplus } #endif #endif /* SDEFL_H_INCLUDED */ #ifdef SDEFL_IMPLEMENTATION #include /* assert */ #include /* memcpy */ #include /* CHAR_BIT */ #ifdef __cplusplus extern "C" { #endif #define SDEFL_NIL (-1) #define SDEFL_MAX_MATCH 258 #define SDEFL_MAX_CODE_LEN (15) #define SDEFL_SYM_BITS (10u) #define SDEFL_SYM_MSK ((1u << SDEFL_SYM_BITS)-1u) #define SDEFL_RAW_BLK_SIZE (65535) #define SDEFL_LIT_LEN_CODES (14) #define SDEFL_OFF_CODES (15) #define SDEFL_PRE_CODES (7) #define SDEFL_CNT_NUM(n) ((((n)+3u/4u)+3u)&~3u) #define SDEFL_EOB (256) #define sdefl_npow2(n) (1 << (sdefl_ilog2((n)-1) + 1)) #define sdefl_div_round_up(n,d) (((n)+((d)-1))/(d)) static int sdefl_ilog2(int n) { if (!n) return 0; #ifdef _MSC_VER unsigned long msbp = 0; _BitScanReverse(&msbp, (unsigned long)n); return (int)msbp; #elif defined(__GNUC__) || defined(__clang__) return (int)sizeof(unsigned long) * CHAR_BIT - 1 - __builtin_clzl((unsigned long)n); #else #define lt(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n static const char tbl[256] = { 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,lt(4), lt(5), lt(5), lt(6), lt(6), lt(6), lt(6), lt(7), lt(7), lt(7), lt(7), lt(7), lt(7), lt(7), lt(7)}; int tt, t; if ((tt = (n >> 16))) { return (t = (tt >> 8)) ? 24 + tbl[t] : 16 + tbl[tt]; } else { return (t = (n >> 8)) ? 8 + tbl[t] : tbl[n]; } #undef lt #endif } static unsigned sdefl_uload32(const void *p) { /* hopefully will be optimized to an unaligned read */ unsigned n = 0; memcpy(&n, p, sizeof(n)); return n; } static unsigned sdefl_hash32(const void *p) { unsigned n = sdefl_uload32(p); return (n * 0x9E377989) >> (32 - SDEFL_HASH_BITS); } static void sdefl_put(unsigned char **dst, struct sdefl *s, int code, int bitcnt) { s->bits |= (code << s->bitcnt); s->bitcnt += bitcnt; while (s->bitcnt >= 8) { unsigned char *tar = *dst; *tar = (unsigned char)(s->bits & 0xFF); s->bits >>= 8; s->bitcnt -= 8; *dst = *dst + 1; } } static void sdefl_heap_sub(unsigned A[], unsigned len, unsigned sub) { unsigned c, p = sub; unsigned v = A[sub]; while ((c = p << 1) <= len) { if (c < len && A[c + 1] > A[c]) c++; if (v >= A[c]) break; A[p] = A[c], p = c; } A[p] = v; } static void sdefl_heap_array(unsigned *A, unsigned len) { unsigned sub; for (sub = len >> 1; sub >= 1; sub--) sdefl_heap_sub(A, len, sub); } static void sdefl_heap_sort(unsigned *A, unsigned n) { A--; sdefl_heap_array(A, n); while (n >= 2) { unsigned tmp = A[n]; A[n--] = A[1]; A[1] = tmp; sdefl_heap_sub(A, n, 1); } } static unsigned sdefl_sort_sym(unsigned sym_cnt, unsigned *freqs, unsigned char *lens, unsigned *sym_out) { unsigned cnts[SDEFL_CNT_NUM(SDEFL_SYM_MAX)] = {0}; unsigned cnt_num = SDEFL_CNT_NUM(sym_cnt); unsigned used_sym = 0; unsigned sym, i; for (sym = 0; sym < sym_cnt; sym++) cnts[freqs[sym] < cnt_num-1 ? freqs[sym]: cnt_num-1]++; for (i = 1; i < cnt_num; i++) { unsigned cnt = cnts[i]; cnts[i] = used_sym; used_sym += cnt; } for (sym = 0; sym < sym_cnt; sym++) { unsigned freq = freqs[sym]; if (freq) { unsigned idx = freq < cnt_num-1 ? freq : cnt_num-1; sym_out[cnts[idx]++] = sym | (freq << SDEFL_SYM_BITS); } else lens[sym] = 0; } sdefl_heap_sort(sym_out + cnts[cnt_num-2], cnts[cnt_num-1] - cnts[cnt_num-2]); return used_sym; } static void sdefl_build_tree(unsigned *A, unsigned sym_cnt) { unsigned i = 0, b = 0, e = 0; do { unsigned m, n, freq_shift; if (i != sym_cnt && (b == e || (A[i] >> SDEFL_SYM_BITS) <= (A[b] >> SDEFL_SYM_BITS))) m = i++; else m = b++; if (i != sym_cnt && (b == e || (A[i] >> SDEFL_SYM_BITS) <= (A[b] >> SDEFL_SYM_BITS))) n = i++; else n = b++; freq_shift = (A[m] & ~SDEFL_SYM_MSK) + (A[n] & ~SDEFL_SYM_MSK); A[m] = (A[m] & SDEFL_SYM_MSK) | (e << SDEFL_SYM_BITS); A[n] = (A[n] & SDEFL_SYM_MSK) | (e << SDEFL_SYM_BITS); A[e] = (A[e] & SDEFL_SYM_MSK) | freq_shift; } while (sym_cnt - ++e > 1); } static void sdefl_gen_len_cnt(unsigned *A, unsigned root, unsigned *len_cnt, unsigned max_code_len) { int n; unsigned i; for (i = 0; i <= max_code_len; i++) len_cnt[i] = 0; len_cnt[1] = 2; A[root] &= SDEFL_SYM_MSK; for (n = (int)root - 1; n >= 0; n--) { unsigned p = A[n] >> SDEFL_SYM_BITS; unsigned pdepth = A[p] >> SDEFL_SYM_BITS; unsigned depth = pdepth + 1; unsigned len = depth; A[n] = (A[n] & SDEFL_SYM_MSK) | (depth << SDEFL_SYM_BITS); if (len >= max_code_len) { len = max_code_len; do len--; while (!len_cnt[len]); } len_cnt[len]--; len_cnt[len+1] += 2; } } static void sdefl_gen_codes(unsigned *A, unsigned char *lens, const unsigned *len_cnt, unsigned max_code_word_len, unsigned sym_cnt) { unsigned i, sym, len, nxt[SDEFL_MAX_CODE_LEN + 1]; for (i = 0, len = max_code_word_len; len >= 1; len--) { unsigned cnt = len_cnt[len]; while (cnt--) lens[A[i++] & SDEFL_SYM_MSK] = (unsigned char)len; } nxt[0] = nxt[1] = 0; for (len = 2; len <= max_code_word_len; len++) nxt[len] = (nxt[len-1] + len_cnt[len-1]) << 1; for (sym = 0; sym < sym_cnt; sym++) A[sym] = nxt[lens[sym]]++; } static unsigned sdefl_rev(unsigned c, unsigned char n) { c = ((c & 0x5555) << 1) | ((c & 0xAAAA) >> 1); c = ((c & 0x3333) << 2) | ((c & 0xCCCC) >> 2); c = ((c & 0x0F0F) << 4) | ((c & 0xF0F0) >> 4); c = ((c & 0x00FF) << 8) | ((c & 0xFF00) >> 8); return c >> (16-n); } static void sdefl_huff(unsigned char *lens, unsigned *codes, unsigned *freqs, unsigned num_syms, unsigned max_code_len) { unsigned c, *A = codes; unsigned len_cnt[SDEFL_MAX_CODE_LEN + 1]; unsigned used_syms = sdefl_sort_sym(num_syms, freqs, lens, A); if (!used_syms) return; if (used_syms == 1) { unsigned s = A[0] & SDEFL_SYM_MSK; unsigned i = s ? s : 1; codes[0] = 0, lens[0] = 1; codes[i] = 1, lens[i] = 1; return; } sdefl_build_tree(A, used_syms); sdefl_gen_len_cnt(A, used_syms-2, len_cnt, max_code_len); sdefl_gen_codes(A, lens, len_cnt, max_code_len, num_syms); for (c = 0; c < num_syms; c++) { codes[c] = sdefl_rev(codes[c], lens[c]); } } struct sdefl_symcnt { int items; int lit; int off; }; static void sdefl_precode(struct sdefl_symcnt *cnt, unsigned *freqs, unsigned *items, const unsigned char *litlen, const unsigned char *offlen) { unsigned *at = items; unsigned run_start = 0; unsigned total = 0; unsigned char lens[SDEFL_SYM_MAX + SDEFL_OFF_MAX]; for (cnt->lit = SDEFL_SYM_MAX; cnt->lit > 257; cnt->lit--) if (litlen[cnt->lit - 1]) break; for (cnt->off = SDEFL_OFF_MAX; cnt->off > 1; cnt->off--) if (offlen[cnt->off - 1]) break; total = (unsigned)(cnt->lit + cnt->off); memcpy(lens, litlen, sizeof(unsigned char) * (size_t)cnt->lit); memcpy(lens + cnt->lit, offlen, sizeof(unsigned char) * (size_t)cnt->off); do { unsigned len = lens[run_start]; unsigned run_end = run_start; do run_end++; while (run_end != total && len == lens[run_end]); if (!len) { while ((run_end - run_start) >= 11) { unsigned n = (run_end - run_start) - 11; unsigned xbits = n < 0x7f ? n : 0x7f; freqs[18]++; *at++ = 18u | (xbits << 5u); run_start += 11 + xbits; } if ((run_end - run_start) >= 3) { unsigned n = (run_end - run_start) - 3; unsigned xbits = n < 0x7 ? n : 0x7; freqs[17]++; *at++ = 17u | (xbits << 5u); run_start += 3 + xbits; } } else if ((run_end - run_start) >= 4) { freqs[len]++; *at++ = len; run_start++; do { unsigned xbits = (run_end - run_start) - 3; xbits = xbits < 0x03 ? xbits : 0x03; *at++ = 16 | (xbits << 5); run_start += 3 + xbits; freqs[16]++; } while ((run_end - run_start) >= 3); } while (run_start != run_end) { freqs[len]++; *at++ = len; run_start++; } } while (run_start != total); cnt->items = (int)(at - items); } struct sdefl_match_codest { int ls, lc; int dc, dx; }; static void sdefl_match_codes(struct sdefl_match_codest *cod, int dist, int len) { static const short dxmax[] = {0,6,12,24,48,96,192,384,768,1536,3072,6144,12288,24576}; static const unsigned char lslot[258+1] = { 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 }; assert(len <= 258); assert(dist <= 32768); cod->ls = lslot[len]; cod->lc = 257 + cod->ls; assert(cod->lc <= 285); cod->dx = sdefl_ilog2(sdefl_npow2(dist) >> 2); cod->dc = cod->dx ? ((cod->dx + 1) << 1) + (dist > dxmax[cod->dx]) : dist-1; } enum sdefl_blk_type { SDEFL_BLK_UCOMPR, SDEFL_BLK_DYN }; static enum sdefl_blk_type sdefl_blk_type(const struct sdefl *s, int blk_len, int pre_item_len, const unsigned *pre_freq, const unsigned char *pre_len) { static const unsigned char x_pre_bits[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7}; static const unsigned char x_len_bits[] = {0,0,0,0,0,0,0,0, 1,1,1,1,2,2,2,2, 3,3,3,3,4,4,4,4, 5,5,5,5,0}; static const unsigned char x_off_bits[] = {0,0,0,0,1,1,2,2, 3,3,4,4,5,5,6,6, 7,7,8,8,9,9,10,10, 11,11,12,12,13,13}; int dyn_cost = 0; int fix_cost = 0; int sym = 0; dyn_cost += 5 + 5 + 4 + (3 * pre_item_len); for (sym = 0; sym < SDEFL_PRE_MAX; sym++) dyn_cost += pre_freq[sym] * (x_pre_bits[sym] + pre_len[sym]); for (sym = 0; sym < 256; sym++) dyn_cost += s->freq.lit[sym] * s->cod.len.lit[sym]; dyn_cost += s->cod.len.lit[SDEFL_EOB]; for (sym = 257; sym < 286; sym++) dyn_cost += s->freq.lit[sym] * (x_len_bits[sym - 257] + s->cod.len.lit[sym]); for (sym = 0; sym < 30; sym++) dyn_cost += s->freq.off[sym] * (x_off_bits[sym] + s->cod.len.off[sym]); fix_cost += 8*(5 * sdefl_div_round_up(blk_len, SDEFL_RAW_BLK_SIZE) + blk_len + 1 + 2); return (dyn_cost < fix_cost) ? SDEFL_BLK_DYN : SDEFL_BLK_UCOMPR; } static void sdefl_put16(unsigned char **dst, unsigned short x) { unsigned char *val = *dst; val[0] = (unsigned char)(x & 0xff); val[1] = (unsigned char)(x >> 8); *dst = val + 2; } static void sdefl_match(unsigned char **dst, struct sdefl *s, int dist, int len) { static const char lxn[] = {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0}; static const short lmin[] = {3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43, 51,59,67,83,99,115,131,163,195,227,258}; static const short dmin[] = {1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257, 385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577}; struct sdefl_match_codest cod; sdefl_match_codes(&cod, dist, len); sdefl_put(dst, s, (int)s->cod.word.lit[cod.lc], s->cod.len.lit[cod.lc]); sdefl_put(dst, s, len - lmin[cod.ls], lxn[cod.ls]); sdefl_put(dst, s, (int)s->cod.word.off[cod.dc], s->cod.len.off[cod.dc]); sdefl_put(dst, s, dist - dmin[cod.dc], cod.dx); } static void sdefl_flush(unsigned char **dst, struct sdefl *s, int is_last, const unsigned char *in, int blk_begin, int blk_end) { int blk_len = blk_end - blk_begin; int j, i = 0, item_cnt = 0; struct sdefl_symcnt symcnt = {0}; unsigned codes[SDEFL_PRE_MAX]; unsigned char lens[SDEFL_PRE_MAX]; unsigned freqs[SDEFL_PRE_MAX] = {0}; unsigned items[SDEFL_SYM_MAX + SDEFL_OFF_MAX]; static const unsigned char perm[SDEFL_PRE_MAX] = {16,17,18,0,8,7,9,6,10,5,11, 4,12,3,13,2,14,1,15}; /* calculate huffman codes */ s->freq.lit[SDEFL_EOB]++; sdefl_huff(s->cod.len.lit, s->cod.word.lit, s->freq.lit, SDEFL_SYM_MAX, SDEFL_LIT_LEN_CODES); sdefl_huff(s->cod.len.off, s->cod.word.off, s->freq.off, SDEFL_OFF_MAX, SDEFL_OFF_CODES); sdefl_precode(&symcnt, freqs, items, s->cod.len.lit, s->cod.len.off); sdefl_huff(lens, codes, freqs, SDEFL_PRE_MAX, SDEFL_PRE_CODES); for (item_cnt = SDEFL_PRE_MAX; item_cnt > 4; item_cnt--) { if (lens[perm[item_cnt - 1]]){ break; } } /* write block */ switch (sdefl_blk_type(s, blk_len, item_cnt, freqs, lens)) { case SDEFL_BLK_UCOMPR: { /* uncompressed blocks */ int n = sdefl_div_round_up(blk_len, SDEFL_RAW_BLK_SIZE); for (i = 0; i < n; ++i) { int fin = is_last && (i + 1 == n); int amount = blk_len < SDEFL_RAW_BLK_SIZE ? blk_len : SDEFL_RAW_BLK_SIZE; sdefl_put(dst, s, !!fin, 1); /* block */ sdefl_put(dst, s, 0x00, 2); /* stored block */ if (s->bitcnt) { sdefl_put(dst, s, 0x00, 8 - s->bitcnt); } assert(s->bitcnt == 0); sdefl_put16(dst, (unsigned short)amount); sdefl_put16(dst, ~(unsigned short)amount); memcpy(*dst, in + blk_begin + i * SDEFL_RAW_BLK_SIZE, amount); *dst = *dst + amount; blk_len -= amount; } } break; case SDEFL_BLK_DYN: { /* dynamic huffman block */ sdefl_put(dst, s, !!is_last, 1); /* block */ sdefl_put(dst, s, 0x02, 2); /* dynamic huffman */ sdefl_put(dst, s, symcnt.lit - 257, 5); sdefl_put(dst, s, symcnt.off - 1, 5); sdefl_put(dst, s, item_cnt - 4, 4); for (i = 0; i < item_cnt; ++i) { sdefl_put(dst, s, lens[perm[i]], 3); } for (i = 0; i < symcnt.items; ++i) { unsigned sym = items[i] & 0x1F; sdefl_put(dst, s, (int)codes[sym], lens[sym]); if (sym < 16) continue; if (sym == 16) sdefl_put(dst, s, items[i] >> 5, 2); else if(sym == 17) sdefl_put(dst, s, items[i] >> 5, 3); else sdefl_put(dst, s, items[i] >> 5, 7); } /* block sequences */ for (i = 0; i < s->seq_cnt; ++i) { if (s->seq[i].off >= 0) { for (j = 0; j < s->seq[i].len; ++j) { int c = in[s->seq[i].off + j]; sdefl_put(dst, s, (int)s->cod.word.lit[c], s->cod.len.lit[c]); } } else { sdefl_match(dst, s, -s->seq[i].off, s->seq[i].len); } } sdefl_put(dst, s, (int)(s)->cod.word.lit[SDEFL_EOB], (s)->cod.len.lit[SDEFL_EOB]); } break;} memset(&s->freq, 0, sizeof(s->freq)); s->seq_cnt = 0; } static void sdefl_seq(struct sdefl *s, int off, int len) { assert(s->seq_cnt + 2 < SDEFL_SEQ_SIZ); s->seq[s->seq_cnt].off = off; s->seq[s->seq_cnt].len = len; s->seq_cnt++; } static void sdefl_reg_match(struct sdefl *s, int off, int len) { struct sdefl_match_codest cod; sdefl_match_codes(&cod, off, len); assert(cod.lc < SDEFL_SYM_MAX); assert(cod.dc < SDEFL_OFF_MAX); s->freq.lit[cod.lc]++; s->freq.off[cod.dc]++; } struct sdefl_match { int off; int len; }; static void sdefl_fnd(struct sdefl_match *m, const struct sdefl *s, int chain_len, int max_match, const unsigned char *in, int p, int e) { int i = s->tbl[sdefl_hash32(in + p)]; int limit = ((p - SDEFL_WIN_SIZ) < SDEFL_NIL) ? SDEFL_NIL : (p-SDEFL_WIN_SIZ); (void)e; /* used in assertions */ assert(p < e); assert(p + max_match <= e); while (i > limit) { assert(i + m->len < e); assert(p + m->len < e); assert(i + SDEFL_MIN_MATCH < e); assert(p + SDEFL_MIN_MATCH < e); if (in[i + m->len] == in[p + m->len] && (sdefl_uload32(&in[i]) == sdefl_uload32(&in[p]))) { int n = SDEFL_MIN_MATCH; while (n < max_match && in[i + n] == in[p + n]) { assert(i + n < e); assert(p + n < e); n++; } if (n > m->len) { m->len = n, m->off = p - i; if (n == max_match) break; } } if (!(--chain_len)) break; i = s->prv[i & SDEFL_WIN_MSK]; } } static int sdefl_compr(struct sdefl *s, unsigned char *out, const unsigned char *in, int in_len, int lvl) { unsigned char *q = out; static const unsigned char pref[] = {8,10,14,24,30,48,65,96,130}; int max_chain = (lvl < 8) ? (1 << (lvl + 1)): (1 << 13); int n, i = 0, litlen = 0; for (n = 0; n < SDEFL_HASH_SIZ; ++n) { s->tbl[n] = SDEFL_NIL; } do {int blk_begin = i; int blk_end = ((i + SDEFL_BLK_MAX) < in_len) ? (i + SDEFL_BLK_MAX) : in_len; while (i < blk_end) { struct sdefl_match m = {0}; int left = blk_end - i; int max_match = (left > SDEFL_MAX_MATCH) ? SDEFL_MAX_MATCH : left; int nice_match = pref[lvl] < max_match ? pref[lvl] : max_match; int run = 1, inc = 1, run_inc = 0; if (max_match > SDEFL_MIN_MATCH) { sdefl_fnd(&m, s, max_chain, max_match, in, i, in_len); } if (lvl >= 5 && m.len >= SDEFL_MIN_MATCH && m.len + 1 < nice_match){ struct sdefl_match m2 = {0}; sdefl_fnd(&m2, s, max_chain, m.len + 1, in, i + 1, in_len); m.len = (m2.len > m.len) ? 0 : m.len; } if (m.len >= SDEFL_MIN_MATCH) { if (litlen) { sdefl_seq(s, i - litlen, litlen); litlen = 0; } sdefl_seq(s, -m.off, m.len); sdefl_reg_match(s, m.off, m.len); if (lvl < 2 && m.len >= nice_match) { inc = m.len; } else { run = m.len; } } else { s->freq.lit[in[i]]++; litlen++; } run_inc = run * inc; if (in_len - (i + run_inc) > SDEFL_MIN_MATCH) { while (run-- > 0) { unsigned h = sdefl_hash32(&in[i]); s->prv[i&SDEFL_WIN_MSK] = s->tbl[h]; s->tbl[h] = i, i += inc; assert(i <= blk_end); } } else { i += run_inc; assert(i <= blk_end); } } if (litlen) { sdefl_seq(s, i - litlen, litlen); litlen = 0; } sdefl_flush(&q, s, blk_end == in_len, in, blk_begin, blk_end); } while (i < in_len); if (s->bitcnt) { sdefl_put(&q, s, 0x00, 8 - s->bitcnt); } assert(s->bitcnt == 0); return (int)(q - out); } extern int sdeflate(struct sdefl *s, void *out, const void *in, int n, int lvl) { s->bits = s->bitcnt = 0; return sdefl_compr(s, (unsigned char*)out, (const unsigned char*)in, n, lvl); } static unsigned sdefl_adler32(unsigned adler32, const unsigned char *in, int in_len) { #define SDEFL_ADLER_INIT (1) const unsigned ADLER_MOD = 65521; unsigned s1 = adler32 & 0xffff; unsigned s2 = adler32 >> 16; unsigned blk_len, i; blk_len = in_len % 5552; while (in_len) { for (i = 0; i + 7 < blk_len; i += 8) { s1 += in[0]; s2 += s1; s1 += in[1]; s2 += s1; s1 += in[2]; s2 += s1; s1 += in[3]; s2 += s1; s1 += in[4]; s2 += s1; s1 += in[5]; s2 += s1; s1 += in[6]; s2 += s1; s1 += in[7]; s2 += s1; in += 8; } for (; i < blk_len; ++i) { s1 += *in++, s2 += s1; } s1 %= ADLER_MOD; s2 %= ADLER_MOD; in_len -= blk_len; blk_len = 5552; } return (unsigned)(s2 << 16) + (unsigned)s1; } extern int zsdeflate(struct sdefl *s, void *out, const void *in, int n, int lvl) { int p = 0; unsigned a = 0; unsigned char *q = (unsigned char*)out; s->bits = s->bitcnt = 0; sdefl_put(&q, s, 0x78, 8); /* deflate, 32k window */ sdefl_put(&q, s, 0x01, 8); /* fast compression */ q += sdefl_compr(s, q, (const unsigned char*)in, n, lvl); /* append adler checksum */ a = sdefl_adler32(SDEFL_ADLER_INIT, (const unsigned char*)in, n); for (p = 0; p < 4; ++p) { sdefl_put(&q, s, (a >> 24) & 0xFF, 8); a <<= 8; } return (int)(q - (unsigned char*)out); } extern int sdefl_bound(int len) { int max_blocks = 1 + sdefl_div_round_up(len, SDEFL_RAW_BLK_SIZE); int bound = 5 * max_blocks + len + 1 + 4 + 8 + 3; return bound; } #ifdef __cplusplus } #endif #endif /* SDEFL_IMPLEMENTATION */ ================================================ FILE: gltf/README.md ================================================ # 📦 gltfpack gltfpack is a tool that can automatically optimize glTF files to reduce the download size and improve loading and rendering speed. ## Installation You can download a pre-built binary for gltfpack on [Releases page](https://github.com/zeux/meshoptimizer/releases), or install [npm package](https://www.npmjs.com/package/gltfpack). Native binaries are recommended over npm since they can work with larger files, run faster, and support texture compression. ## Usage To convert a glTF file using gltfpack, run the command-line binary like this on an input `.gltf`/`.glb`/`.obj` file (run it without arguments for a list of options): ``` gltfpack -i scene.gltf -o scene.glb ``` gltfpack substantially changes the glTF data by optimizing the meshes for vertex fetch and transform cache, quantizing the geometry to reduce the memory consumption and size, merging meshes to reduce the draw call count, quantizing and resampling animations to reduce animation size and simplify playback, and pruning the node tree by removing or collapsing redundant nodes. It will also simplify the meshes when requested to do so. By default gltfpack outputs regular `.glb`/`.gltf` files that have been optimized for GPU consumption using various cache optimizers and quantization. These files can be loaded by GLTF loaders that support `KHR_mesh_quantization` extension such as [three.js](https://threejs.org/) (r111+) and [Babylon.js](https://www.babylonjs.com/) (4.1+). When using `-c` option, gltfpack outputs compressed `.glb`/`.gltf` files that use meshoptimizer codecs to reduce the download size further. Loading these files requires extending GLTF loaders with support for [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md) extension; three.js supports it in r122+ (requires calling `GLTFLoader.setMeshoptDecoder`), Babylon.js supports it in 5.0+ without further setup. For better compression, you can use `-cc` option which applies additional compression; additionally make sure that your content delivery method is configured to use deflate (gzip) - meshoptimizer codecs are designed to produce output that can be compressed further with general purpose compressors. For maximum compression, consider using [KHR_meshopt_compression](https://github.com/KhronosGroup/glTF/pull/2517) extension with a higher compression level (`-cz` option). gltfpack can also compress textures using Basis Universal format stored in a KTX2 container (`-tc` flag, requires support for `KHR_texture_basisu`) or using WebP format (`-tw` flag, requires support for `EXT_texture_webp`). When working with glTF files that contain point clouds, gltfpack automatically processes the point cloud data to reduce the download size to the extent possible. In addition to aforementioned compression options (either `-c` or `-cc` are recommended), gltfpack can also prune point clouds to provide a more uniform density when `-si` option is used. ## Decompression When using compressed files, [js/meshopt_decoder.mjs](https://github.com/zeux/meshoptimizer/blob/master/js/meshopt_decoder.mjs) needs to be loaded to provide the WebAssembly decoder module like this: ```js import { MeshoptDecoder } from './meshopt_decoder.mjs'; ... var loader = new GLTFLoader(); loader.setMeshoptDecoder(MeshoptDecoder); loader.load('pirate.glb', function (gltf) { scene.add(gltf.scene); }); ``` When using Three.js, this module can be imported from three.js repository from `examples/jsm/libs/meshopt_decoder.module.js`. Note that `meshopt_decoder` assumes that WebAssembly is supported. This is the case for all modern browsers; if support for legacy browsers such as Internet Explorer 11 is desired, it's recommended to use `-cf` flag when creating the glTF content. This will create and load fallback uncompressed buffers, but only on browsers that don't support WebAssembly. ## Options By default gltfpack makes certain assumptions when optimizing the scenes, for example meshes that belong to nodes that aren't animated can be merged together, and has some defaults that represent a tradeoff between precision and size that are picked to fit most use cases. However, in some cases the resulting `.gltf` file needs to retain some way for the application to manipulate individual scene elements, and in other cases precision or size are more important to optimize for. gltfpack has a rich set of command line options to control various aspects of its behavior, with the full list available via `gltfpack -h`. The following settings are frequently used to reduce the resulting data size: * `-cc`: produce compressed gltf/glb files (requires `EXT_meshopt_compression`) * `-tc`: convert all textures to KTX2 with BasisU supercompression (requires `KHR_texture_basisu` and may require `-tp` flag for compatibility with WebGL 1) * `-tw`: convert all textures to WebP (requires `EXT_texture_webp`) * `-mi`: use mesh instancing when serializing references to the same meshes (requires `EXT_mesh_gpu_instancing`) * `-si R`: simplify meshes targeting triangle/point count ratio R (default: 1; R should be between 0 and 1) The following settings are frequently used to restrict some optimizations: * `-kn`: keep named nodes and meshes attached to named nodes so that named nodes can be transformed externally * `-km`: keep named materials and disable named material merging * `-ke`: keep extras data * `-vpf`: use floating-point position quantization instead of the default fixed-point (this results in larger position data, but does not insert new nodes with dequantization transforms; when using this option, `-cc` is recommended as well) ## Extensions gltfpack supports most Khronos extensions and some multi-vendor extensions in the input scenes, with newer extensions added regularly. The following extensions are fully supported: - KHR_lights_punctual - KHR_materials_anisotropy - KHR_materials_clearcoat - KHR_materials_diffuse_transmission - KHR_materials_dispersion - KHR_materials_emissive_strength - KHR_materials_ior - KHR_materials_iridescence - KHR_materials_pbrSpecularGlossiness - KHR_materials_sheen - KHR_materials_specular - KHR_materials_transmission - KHR_materials_unlit - KHR_materials_variants - KHR_materials_volume - KHR_mesh_quantization - KHR_meshopt_compression - KHR_texture_basisu - KHR_texture_transform - EXT_mesh_gpu_instancing - EXT_meshopt_compression - EXT_texture_webp Even if the source file does not use extensions, gltfpack may use some extensions in the output file either by default or when certain options are used: - KHR_mesh_quantization (used by default unless disabled via `-noq`) - KHR_meshopt_compression (used when requested via `-ce khr` or `-cz`) - KHR_texture_transform (used by default when textures are present, unless disabled via `-noq` or `-vtf`) - KHR_texture_basisu (used when requested via `-tc` or `-tu`) - EXT_meshopt_compression (used when requested via `-c` or `-cc`) - EXT_mesh_gpu_instancing (used when requested via `-mi`) - EXT_texture_webp (used when requested via `-tw`) gltfpack does not support vendor-specific extensions or custom extensions, including ones defined in [Khronos glTF repository](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor). Unknown extension nodes are discarded from the output. ## Custom data glTF files may contain custom application-specific data stored outside of custom extensions. gltfpack has limited support for preserving this data. Unknown vertex or instance attributes are generally discarded. While it would be possible to preserve them in theory, many scene transformations and analyses require a full understanding of the attribute semantics - some attributes need to be transformed with the mesh transformation matrix, some need to be adjusted when meshes are merged, some influence how meshes are rendered, etc. However, gltfpack supports some specific attributes that are commonly used in the ecosystem: - Vertex attributes that store integer IDs with names `_ID`, `_BATCHID` or `_FEATURE_ID_n` are preserved as-is. These can be used to identify objects. - Instance attribute `_COLOR_0` is preserved as-is. This can be used to provide per-instance color tinting. Additional application-specific data can be stored via the glTF `extras` property. While extras (outside of `asset`) are discarded by default, using the `-ke` option preserves extras in materials, nodes, primitives and scenes. In addition to `asset` extras, gltfpack unconditionally preserves morph target names specified via the `targetNames` property inside `extras` on meshes. ## Building gltfpack can be built from source using CMake or Make. To build a full version of gltfpack that supports texture compression, CMake configuration needs to specify the path to https://github.com/zeux/basis_universal fork (branch gltfpack) via `MESHOPT_GLTFPACK_BASISU_PATH` variable, as well as libwebp path via `MESHOPT_GLTFPACK_LIBWEBP_PATH` variable: ``` git clone -b gltfpack https://github.com/zeux/basis_universal git clone https://github.com/webmproject/libwebp cmake . -DMESHOPT_BUILD_GLTFPACK=ON -DMESHOPT_GLTFPACK_BASISU_PATH=basis_universal -DMESHOPT_GLTFPACK_LIBWEBP_PATH=libwebp -DCMAKE_BUILD_TYPE=Release cmake --build . --target gltfpack --config Release ``` ## License gltfpack is available to anybody free of charge, under the terms of MIT License (see LICENSE.md). ================================================ FILE: gltf/animation.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include #include #include #include static float getDelta(const Attr& l, const Attr& r, cgltf_animation_path_type type) { switch (type) { case cgltf_animation_path_type_translation: return std::max(std::max(fabsf(l.f[0] - r.f[0]), fabsf(l.f[1] - r.f[1])), fabsf(l.f[2] - r.f[2])); case cgltf_animation_path_type_rotation: return 2 * acosf(std::min(1.f, fabsf(l.f[0] * r.f[0] + l.f[1] * r.f[1] + l.f[2] * r.f[2] + l.f[3] * r.f[3]))); case cgltf_animation_path_type_scale: return std::max(std::max(fabsf(l.f[0] / r.f[0] - 1), fabsf(l.f[1] / r.f[1] - 1)), fabsf(l.f[2] / r.f[2] - 1)); case cgltf_animation_path_type_weights: return fabsf(l.f[0] - r.f[0]); default: assert(!"Uknown animation path"); return 0; } } static float getDeltaTolerance(cgltf_animation_path_type type) { switch (type) { case cgltf_animation_path_type_translation: return 0.0001f; // 0.1mm linear case cgltf_animation_path_type_rotation: return 0.1f * (3.1415926f / 180.f); // 0.1 degrees case cgltf_animation_path_type_scale: return 0.001f; // 0.1% ratio case cgltf_animation_path_type_weights: return 0.001f; // 0.1% linear default: assert(!"Uknown animation path"); return 0; } } static Attr interpolateLinear(const Attr& l, const Attr& r, float t, cgltf_animation_path_type type) { if (type == cgltf_animation_path_type_rotation) { // Approximating slerp, https://zeux.io/2015/07/23/approximating-slerp/ // We also handle quaternion double-cover float ca = l.f[0] * r.f[0] + l.f[1] * r.f[1] + l.f[2] * r.f[2] + l.f[3] * r.f[3]; float d = fabsf(ca); float A = 1.0904f + d * (-3.2452f + d * (3.55645f - d * 1.43519f)); float B = 0.848013f + d * (-1.06021f + d * 0.215638f); float k = A * (t - 0.5f) * (t - 0.5f) + B; float ot = t + t * (t - 0.5f) * (t - 1) * k; float t0 = 1 - ot; float t1 = ca > 0 ? ot : -ot; Attr lerp = {{ l.f[0] * t0 + r.f[0] * t1, l.f[1] * t0 + r.f[1] * t1, l.f[2] * t0 + r.f[2] * t1, l.f[3] * t0 + r.f[3] * t1, }}; float len = sqrtf(lerp.f[0] * lerp.f[0] + lerp.f[1] * lerp.f[1] + lerp.f[2] * lerp.f[2] + lerp.f[3] * lerp.f[3]); if (len > 0.f) { lerp.f[0] /= len; lerp.f[1] /= len; lerp.f[2] /= len; lerp.f[3] /= len; } return lerp; } else { Attr lerp = {{ l.f[0] * (1 - t) + r.f[0] * t, l.f[1] * (1 - t) + r.f[1] * t, l.f[2] * (1 - t) + r.f[2] * t, l.f[3] * (1 - t) + r.f[3] * t, }}; return lerp; } } static Attr interpolateHermite(const Attr& v0, const Attr& t0, const Attr& v1, const Attr& t1, float t, float dt, cgltf_animation_path_type type) { float s0 = 1 + t * t * (2 * t - 3); float s1 = t + t * t * (t - 2); float s2 = 1 - s0; float s3 = t * t * (t - 1); float ts1 = dt * s1; float ts3 = dt * s3; Attr lerp = {{ s0 * v0.f[0] + ts1 * t0.f[0] + s2 * v1.f[0] + ts3 * t1.f[0], s0 * v0.f[1] + ts1 * t0.f[1] + s2 * v1.f[1] + ts3 * t1.f[1], s0 * v0.f[2] + ts1 * t0.f[2] + s2 * v1.f[2] + ts3 * t1.f[2], s0 * v0.f[3] + ts1 * t0.f[3] + s2 * v1.f[3] + ts3 * t1.f[3], }}; if (type == cgltf_animation_path_type_rotation) { float len = sqrtf(lerp.f[0] * lerp.f[0] + lerp.f[1] * lerp.f[1] + lerp.f[2] * lerp.f[2] + lerp.f[3] * lerp.f[3]); if (len > 0.f) { lerp.f[0] /= len; lerp.f[1] /= len; lerp.f[2] /= len; lerp.f[3] /= len; } } return lerp; } static void resampleKeyframes(std::vector& data, const std::vector& input, const std::vector& output, cgltf_animation_path_type type, cgltf_interpolation_type interpolation, size_t components, int frames, float mint, int freq) { size_t cursor = 0; for (int i = 0; i < frames; ++i) { float time = mint + float(i) / freq; while (cursor + 1 < input.size()) { float next_time = input[cursor + 1]; if (next_time > time) break; cursor++; } if (cursor + 1 < input.size()) { float cursor_time = input[cursor + 0]; float next_time = input[cursor + 1]; float range = next_time - cursor_time; float inv_range = (range == 0.f) ? 0.f : 1.f / (next_time - cursor_time); float t = std::max(0.f, std::min(1.f, (time - cursor_time) * inv_range)); for (size_t j = 0; j < components; ++j) { switch (interpolation) { case cgltf_interpolation_type_linear: { const Attr& v0 = output[(cursor + 0) * components + j]; const Attr& v1 = output[(cursor + 1) * components + j]; data.push_back(interpolateLinear(v0, v1, t, type)); } break; case cgltf_interpolation_type_step: { const Attr& v = output[cursor * components + j]; data.push_back(v); } break; case cgltf_interpolation_type_cubic_spline: { const Attr& v0 = output[(cursor * 3 + 1) * components + j]; const Attr& b0 = output[(cursor * 3 + 2) * components + j]; const Attr& a1 = output[(cursor * 3 + 3) * components + j]; const Attr& v1 = output[(cursor * 3 + 4) * components + j]; data.push_back(interpolateHermite(v0, b0, v1, a1, t, range, type)); } break; default: assert(!"Unknown interpolation type"); } } } else { size_t offset = (interpolation == cgltf_interpolation_type_cubic_spline) ? cursor * 3 + 1 : cursor; for (size_t j = 0; j < components; ++j) { const Attr& v = output[offset * components + j]; data.push_back(v); } } } } static float getMaxDelta(const std::vector& data, cgltf_animation_path_type type, const Attr* value, size_t components) { assert(data.size() % components == 0); float result = 0; for (size_t i = 0; i < data.size(); i += components) { for (size_t j = 0; j < components; ++j) { float delta = getDelta(value[j], data[i + j], type); result = (result < delta) ? delta : result; } } return result; } static void getBaseTransform(Attr* result, size_t components, cgltf_animation_path_type type, cgltf_node* node) { switch (type) { case cgltf_animation_path_type_translation: memcpy(result->f, node->translation, 3 * sizeof(float)); break; case cgltf_animation_path_type_rotation: memcpy(result->f, node->rotation, 4 * sizeof(float)); break; case cgltf_animation_path_type_scale: memcpy(result->f, node->scale, 3 * sizeof(float)); break; case cgltf_animation_path_type_weights: if (node->weights_count) { assert(node->weights_count == components); memcpy(result->f, node->weights, components * sizeof(float)); } else if (node->mesh && node->mesh->weights_count) { assert(node->mesh->weights_count == components); memcpy(result->f, node->mesh->weights, components * sizeof(float)); } break; default: assert(!"Unknown animation path"); } } static float getWorldScale(cgltf_node* node) { float transform[16]; cgltf_node_transform_world(node, transform); // 3x3 determinant computes scale^3 float a0 = transform[5] * transform[10] - transform[6] * transform[9]; float a1 = transform[4] * transform[10] - transform[6] * transform[8]; float a2 = transform[4] * transform[9] - transform[5] * transform[8]; float det = transform[0] * a0 - transform[1] * a1 + transform[2] * a2; return powf(fabsf(det), 1.f / 3.f); } void processAnimation(Animation& animation, const Settings& settings) { float mint = FLT_MAX, maxt = 0; for (size_t i = 0; i < animation.tracks.size(); ++i) { const Track& track = animation.tracks[i]; assert(!track.time.empty()); mint = std::min(mint, track.time.front()); maxt = std::max(maxt, track.time.back()); } animation.start = mint = std::min(mint, maxt); if (settings.anim_freq) { // round the number of frames to nearest but favor the "up" direction // this means that at 100 Hz resampling, we will try to preserve the last frame <10ms // but if the last frame is <2ms we favor just removing this data int frames = 1 + int((maxt - mint) * settings.anim_freq + 0.8f); animation.frames = frames; } std::vector base; for (size_t i = 0; i < animation.tracks.size(); ++i) { Track& track = animation.tracks[i]; if (settings.anim_freq) { std::vector result; resampleKeyframes(result, track.time, track.data, track.path, track.interpolation, track.components, animation.frames, animation.start, settings.anim_freq); track.time.clear(); track.data.swap(result); track.interpolation = track.interpolation == cgltf_interpolation_type_cubic_spline ? cgltf_interpolation_type_linear : track.interpolation; } // getMaxDelta assumes linear/step interpolation for now if (track.interpolation == cgltf_interpolation_type_cubic_spline) continue; float tolerance = getDeltaTolerance(track.path); // translation tracks use world space tolerance; in the future, we should compute all errors as linear using hierarchy if (track.node && track.node->parent && track.path == cgltf_animation_path_type_translation) { float scale = getWorldScale(track.node->parent); tolerance /= scale == 0.f ? 1.f : scale; } float deviation = getMaxDelta(track.data, track.path, &track.data[0], track.components); if (deviation <= tolerance) { // track is constant (equal to first keyframe), we only need the first keyframe track.constant = true; track.time.clear(); track.data.resize(track.components); // track.dummy is true iff track redundantly sets up the value to be equal to default node transform base.resize(track.components); getBaseTransform(&base[0], track.components, track.path, track.node); track.dummy = getMaxDelta(track.data, track.path, &base[0], track.components) <= tolerance; } } } ================================================ FILE: gltf/cli.js ================================================ #!/usr/bin/env node // This file is part of gltfpack and is distributed under the terms of MIT License. import { pack } from './library.js'; import fs from 'fs'; var args = process.argv.slice(2); var iface = { read: function (path) { return fs.readFileSync(path); }, write: function (path, data) { fs.writeFileSync(path, data); }, }; pack(args, iface) .then(function (log) { process.stdout.write(log); process.exit(0); }) .catch(function (err) { process.stderr.write(err.message); process.exit(1); }); ================================================ FILE: gltf/encodebasis.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #ifdef WITH_BASISU #include "gltfpack.h" #define BASISU_NO_ITERATOR_DEBUG_LEVEL #ifdef __clang__ #pragma GCC diagnostic ignored "-Wunknown-warning-option" #pragma GCC diagnostic ignored "-Warray-bounds" #pragma GCC diagnostic ignored "-Wc++17-extensions" #pragma GCC diagnostic ignored "-Wdeprecated-builtins" #endif #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wclass-memaccess" #pragma GCC diagnostic ignored "-Wshadow" #pragma GCC diagnostic ignored "-Wunused-local-typedefs" #pragma GCC diagnostic ignored "-Wunused-value" #endif #if defined(__GNUC__) && __GNUC__ >= 12 #pragma GCC diagnostic ignored "-Wc++17-extensions" #endif #include "encoder/basisu_comp.h" struct BasisSettings { int etc1s_l; int etc1s_q; int uastc_l; float uastc_q; }; static const BasisSettings kBasisSettings[10] = { {1, 1, 0, 4.f}, {1, 32, 0, 3.f}, {1, 64, 1, 2.f}, {1, 96, 1, 1.5f}, {1, 128, 1, 1.f}, // quality arguments aligned with basisu defaults {1, 150, 1, 0.8f}, {1, 170, 1, 0.6f}, {1, 192, 1, 0.4f}, // gltfpack defaults {1, 224, 2, 0.2f}, {1, 255, 2, 0.f}, }; static void fillParams(basisu::basis_compressor_params& params, const char* input, const char* output, bool uastc, int width, int height, const BasisSettings& bs, const ImageInfo& info, const Settings& settings) { if (uastc) { static const uint32_t s_level_flags[basisu::TOTAL_PACK_UASTC_LEVELS] = {basisu::cPackUASTCLevelFastest, basisu::cPackUASTCLevelFaster, basisu::cPackUASTCLevelDefault, basisu::cPackUASTCLevelSlower, basisu::cPackUASTCLevelVerySlow}; params.m_uastc = true; #if BASISU_LIB_VERSION >= 160 params.m_pack_uastc_ldr_4x4_flags &= ~basisu::cPackUASTCLevelMask; params.m_pack_uastc_ldr_4x4_flags |= s_level_flags[bs.uastc_l]; params.m_rdo_uastc_ldr_4x4 = bs.uastc_q > 0; params.m_rdo_uastc_ldr_4x4_quality_scalar = bs.uastc_q; params.m_rdo_uastc_ldr_4x4_dict_size = 1024; #else params.m_pack_uastc_flags &= ~basisu::cPackUASTCLevelMask; params.m_pack_uastc_flags |= s_level_flags[bs.uastc_l]; params.m_rdo_uastc = bs.uastc_q > 0; params.m_rdo_uastc_quality_scalar = bs.uastc_q; params.m_rdo_uastc_dict_size = 1024; #endif } else { #if BASISU_LIB_VERSION >= 200 params.m_etc1s_compression_level = bs.etc1s_l; params.m_quality_level = bs.etc1s_q; params.m_etc1s_max_endpoint_clusters = 0; params.m_etc1s_max_selector_clusters = 0; #elif BASISU_LIB_VERSION >= 160 params.m_compression_level = bs.etc1s_l; params.m_etc1s_quality_level = bs.etc1s_q; params.m_etc1s_max_endpoint_clusters = 0; params.m_etc1s_max_selector_clusters = 0; #else params.m_compression_level = bs.etc1s_l; params.m_quality_level = bs.etc1s_q; params.m_max_endpoint_clusters = 0; params.m_max_selector_clusters = 0; #endif params.m_no_selector_rdo = info.normal_map; params.m_no_endpoint_rdo = info.normal_map; } params.m_perceptual = info.srgb; params.m_mip_gen = true; params.m_mip_srgb = info.srgb; params.m_resample_width = width; params.m_resample_height = height; params.m_y_flip = settings.texture_flipy; params.m_create_ktx2_file = true; #if BASISU_LIB_VERSION >= 200 params.m_ktx2_and_basis_srgb_transfer_function = info.srgb; #else params.m_ktx2_srgb_transfer_func = info.srgb; #endif if (uastc) { params.m_ktx2_uastc_supercompression = basist::KTX2_SS_ZSTANDARD; params.m_ktx2_zstd_supercompression_level = 9; } params.m_read_source_images = true; #if BASISU_LIB_VERSION >= 150 params.m_write_output_basis_or_ktx2_files = true; #else params.m_write_output_basis_files = true; #endif params.m_source_filenames.resize(1); params.m_source_filenames[0] = input; params.m_out_filename = output; params.m_status_output = false; } static const char* prepareEncode(basisu::basis_compressor_params& params, const cgltf_image& image, const char* input_path, const ImageInfo& info, const Settings& settings, const std::string& temp_prefix, std::string& temp_input, std::string& temp_output) { std::string img_data; std::string mime_type; if (!readImage(image, input_path, img_data, mime_type)) return "error reading source file"; if (mime_type != "image/png" && mime_type != "image/jpeg") return NULL; int width = 0, height = 0; if (!getDimensions(img_data, mime_type.c_str(), width, height)) return "error parsing image header"; adjustDimensions(width, height, settings.texture_scale[info.kind], settings.texture_limit[info.kind], settings.texture_pow2); temp_input = temp_prefix + mimeExtension(mime_type.c_str()); temp_output = temp_prefix + ".ktx2"; if (!writeFile(temp_input.c_str(), img_data)) return "error writing temporary file"; int quality = settings.texture_quality[info.kind]; bool uastc = settings.texture_mode[info.kind] == TextureMode_UASTC; const BasisSettings& bs = kBasisSettings[quality - 1]; fillParams(params, temp_input.c_str(), temp_output.c_str(), uastc, width, height, bs, info, settings); return NULL; } void encodeImagesBasis(std::string* encoded, const cgltf_data* data, const std::vector& images, const char* input_path, const Settings& settings) { basisu::basisu_encoder_init(); basisu::vector params(data->images_count); basisu::vector results(data->images_count); std::string temp_prefix = getTempPrefix(); std::vector temp_inputs(data->images_count); std::vector temp_outputs(data->images_count); for (size_t i = 0; i < data->images_count; ++i) { const cgltf_image& image = data->images[i]; ImageInfo info = images[i]; if (settings.texture_mode[info.kind] == TextureMode_ETC1S || settings.texture_mode[info.kind] == TextureMode_UASTC) if (const char* error = prepareEncode(params[i], image, input_path, info, settings, temp_prefix + "-" + std::to_string(i), temp_inputs[i], temp_outputs[i])) encoded[i] = error; } uint32_t num_threads = settings.texture_jobs == 0 ? std::thread::hardware_concurrency() : settings.texture_jobs; basisu::basis_parallel_compress(num_threads, params, results); for (size_t i = 0; i < data->images_count; ++i) { if (params[i].m_source_filenames.empty()) ; // encoding was skipped or preparation resulted in an error else if (results[i].m_error_code == basisu::basis_compressor::cECFailedReadingSourceImages) encoded[i] = "error decoding source image"; else if (results[i].m_error_code != basisu::basis_compressor::cECSuccess) encoded[i] = "error encoding image"; else if (!readFile(temp_outputs[i].c_str(), encoded[i])) encoded[i] = "error reading temporary file"; } for (size_t i = 0; i < data->images_count; ++i) { if (!temp_inputs[i].empty()) removeFile(temp_inputs[i].c_str()); if (!temp_outputs[i].empty()) removeFile(temp_outputs[i].c_str()); } } #endif ================================================ FILE: gltf/encodewebp.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #ifdef WITH_LIBWEBP #include "gltfpack.h" #include "webp/decode.h" #include "webp/encode.h" #ifndef WITH_LIBWEBP_BASIS #include "imageio/image_dec.h" #endif #include #include #include static int writeWebP(const uint8_t* data, size_t data_size, const WebPPicture* picture) { std::string* encoded = static_cast(picture->custom_ptr); encoded->append(reinterpret_cast(data), data_size); return 1; } // when gltfpack is built with Basis Universal, we have easy access to its jpeg/png decoders #ifdef WITH_LIBWEBP_BASIS namespace pv_png { void* load_png(const void* pImage_buf, size_t buf_size, uint32_t desired_chans, uint32_t& width, uint32_t& height, uint32_t& num_chans); } // namespace pv_png namespace jpgd { static const uint32_t cFlagLinearChromaFiltering = 1; unsigned char* decompress_jpeg_image_from_memory(const unsigned char* pSrc_data, int src_data_size, int* width, int* height, int* actual_comps, int req_comps, uint32_t flags = 0); } // namespace jpgd typedef int (*WebPImageReader)(const uint8_t* const data, size_t data_size, struct WebPPicture* const pic, int keep_alpha, struct Metadata* const metadata); static int readPngBasis(const uint8_t* const data, size_t data_size, struct WebPPicture* const pic, int keep_alpha, struct Metadata* const metadata) { (void)keep_alpha; (void)metadata; uint32_t width = 0, height = 0, channels = 0; void* img = pv_png::load_png(data, data_size, 4, width, height, channels); if (!img) return 0; pic->width = width; pic->height = height; int ok = WebPPictureImportRGBA(pic, static_cast(img), width * 4); free(img); return ok; } static int readJpegBasis(const uint8_t* const data, size_t data_size, struct WebPPicture* const pic, int keep_alpha, struct Metadata* const metadata) { (void)keep_alpha; (void)metadata; int width = 0, height = 0, channels = 0; unsigned char* img = jpgd::decompress_jpeg_image_from_memory(data, int(data_size), &width, &height, &channels, 4, jpgd::cFlagLinearChromaFiltering); if (!img) return 0; pic->width = width; pic->height = height; int ok = WebPPictureImportRGBA(pic, img, width * 4); free(img); return ok; } #endif static const char* encodeWebP(const cgltf_image& image, const char* input_path, const ImageInfo& info, const Settings& settings, std::string& encoded) { WebPConfig config; if (!WebPConfigInit(&config)) return "error initializing WebP"; std::string img_data; std::string mime_type; if (!readImage(image, input_path, img_data, mime_type)) return "error reading source file"; if (mime_type != "image/png" && mime_type != "image/jpeg") return NULL; int width = 0, height = 0; if (!getDimensions(img_data, mime_type.c_str(), width, height)) return "error parsing image header"; adjustDimensions(width, height, settings.texture_scale[info.kind], settings.texture_limit[info.kind], settings.texture_pow2); #ifdef WITH_LIBWEBP_BASIS WebPImageReader reader = (mime_type == "image/jpeg") ? readJpegBasis : readPngBasis; #else WebPInputFileFormat format = (mime_type == "image/jpeg") ? WEBP_JPEG_FORMAT : WEBP_PNG_FORMAT; WebPImageReader reader = WebPGetImageReader(format); if (!reader) return "unsupported image format"; #endif WebPPicture pic; if (!WebPPictureInit(&pic)) return "error initializing picture"; std::unique_ptr pic_storage(&pic, WebPPictureFree); if (!reader(reinterpret_cast(img_data.data()), img_data.size(), &pic, 1, NULL)) return "error decoding source image"; if ((width != pic.width || height != pic.height) && !WebPPictureRescale(&pic, width, height)) return "error resizing image"; int quality = settings.texture_quality[info.kind]; if (info.normal_map) config.quality = float(50 + quality * 5); // map 1-10 to 55-100 else config.quality = float(20 + quality * 8); // map 1-10 to 28-100 config.emulate_jpeg_size = 1; // for flatter quality curve pic.writer = writeWebP; pic.custom_ptr = &encoded; encoded.clear(); if (!WebPEncode(&config, &pic)) return "error encoding image"; return NULL; } void encodeImagesWebP(std::string* encoded, const cgltf_data* data, const std::vector& images, const char* input_path, const Settings& settings) { std::atomic next_image{0}; auto encode = [&]() { for (;;) { size_t i = next_image++; if (i >= data->images_count) break; const cgltf_image& image = data->images[i]; ImageInfo info = images[i]; if (settings.texture_mode[info.kind] == TextureMode_WebP) if (const char* error = encodeWebP(image, input_path, info, settings, encoded[i])) encoded[i] = error; } }; // we use main thread as a worker as well size_t worker_count = settings.texture_jobs == 0 ? std::thread::hardware_concurrency() : settings.texture_jobs; size_t thread_count = worker_count > 0 ? worker_count - 1 : 0; std::vector threads; for (size_t i = 0; i < thread_count; ++i) threads.emplace_back(encode); encode(); for (size_t i = 0; i < thread_count; ++i) threads[i].join(); } #endif ================================================ FILE: gltf/fileio.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include #include #include #ifdef _WIN32 #include #else #include #endif std::string getTempPrefix() { #if defined(_WIN32) const char* temp_dir = getenv("TEMP"); std::string path = temp_dir ? temp_dir : "."; path += "\\gltfpack-temp"; path += std::to_string(_getpid()); return path; #elif defined(__wasi__) return "gltfpack-temp"; #else std::string path = "/tmp/gltfpack-temp"; path += std::to_string(getpid()); return path; #endif } std::string getFullPath(const char* path, const char* base_path) { std::string result = base_path; std::string::size_type slash = result.find_last_of("/\\"); result.erase(slash == std::string::npos ? 0 : slash + 1); result += path; return result; } std::string getFileName(const char* path) { std::string result = path; std::string::size_type slash = result.find_last_of("/\\"); if (slash != std::string::npos) result.erase(0, slash + 1); std::string::size_type dot = result.find_last_of('.'); if (dot != std::string::npos) result.erase(dot); return result; } std::string getExtension(const char* path) { std::string result = path; std::string::size_type slash = result.find_last_of("/\\"); std::string::size_type dot = result.find_last_of('.'); if (slash != std::string::npos && dot != std::string::npos && dot < slash) dot = std::string::npos; result.erase(0, dot); for (size_t i = 0; i < result.length(); ++i) if (unsigned(result[i] - 'A') < 26) result[i] = (result[i] - 'A') + 'a'; return result; } bool readFile(const char* path, std::string& data) { FILE* file = fopen(path, "rb"); if (!file) return false; fseek(file, 0, SEEK_END); long length = ftell(file); fseek(file, 0, SEEK_SET); if (length <= 0) { fclose(file); return false; } data.resize(length); size_t result = fread(&data[0], 1, data.size(), file); int rc = fclose(file); return rc == 0 && result == data.size(); } bool writeFile(const char* path, const std::string& data) { FILE* file = fopen(path, "wb"); if (!file) return false; size_t result = fwrite(&data[0], 1, data.size(), file); int rc = fclose(file); return rc == 0 && result == data.size(); } void removeFile(const char* path) { remove(path); } ================================================ FILE: gltf/fuzz.dict ================================================ # # AFL dictionary for JSON # ----------------------- # # Just the very basics. # # Inspired by a dictionary by Jakub Wilk # "0" ",0" ":0" "0:" "-1.2e+3" "true" "false" "null" "\"\"" ",\"\"" ":\"\"" "\"\":" "{}" ",{}" ":{}" "{\"\":0}" "{{}}" "[]" ",[]" ":[]" "[0]" "[[]]" "''" "\\" "\\b" "\\f" "\\n" "\\r" "\\t" "\\u0000" "\\x00" "\\0" "\\uD800\\uDC00" "\\uDBFF\\uDFFF" "\"\":0" "//" "/**/" # # AFL dictionary for GLTF core # ----------------------- "5120" "5121" "5122" "5123" "5125" "5126" "\"BLEND\"" "\"CUBICSPLINE\"" "\"LINEAR\"" "\"MASK\"" "\"MAT2\"" "\"MAT3\"" "\"MAT4\"" "\"OPAQUE\"" "\"SCALAR\"" "\"STEP\"" "\"VEC2\"" "\"VEC3\"" "\"VEC4\"" "\"accessor\"" "\"accessors\"" "\"alphaCutoff\"" "\"alphaMode\"" "\"animations\"" "\"aspectRatio\"" "\"asset\"" "\"attributes\"" "\"baseColorFactor\"" "\"baseColorTexture\"" "\"bufferView\"" "\"bufferViews\"" "\"buffer\"" "\"buffers\"" "\"byteLength\"" "\"byteOffset\"" "\"byteStride\"" "\"camera\"" "\"cameras\"" "\"channel\"" "\"channels\"" "\"children\"" "\"componentType\"" "\"copyright\"" "\"count\"" "\"doubleSided\"" "\"emissiveFactor\"" "\"emissiveTexture\"" "\"extensionsRequired\"" "\"extensionsUsed\"" "\"extensions\"" "\"extras\"" "\"generator\"" "\"image\"" "\"images\"" "\"index\"" "\"indices\"" "\"input\"" "\"interpolation\"" "\"inverseBindMatrices\"" "\"joints\"" "\"magFilter\"" "\"material\"" "\"materials\"" "\"matrix\"" "\"max\"" "\"mesh\"" "\"meshes\"" "\"metallicFactor\"" "\"metallicRoughnessTexture\"" "\"mimeType\"" "\"minFilter\"" "\"minVersion\"" "\"min\"" "\"mode\"" "\"name\"" "\"node\"" "\"nodes\"" "\"normalTextureInfo\"" "\"normalTexture\"" "\"normalized\"" "\"occlusionTextureInfo\"" "\"occlusionTexture\"" "\"orthographic\"" "\"output\"" "\"path\"" "\"pbrMetallicRoughness\"" "\"perspective\"" "\"primitive\"" "\"primitives\"" "\"rotation\"" "\"roughnessFactor\"" "\"sampler\"" "\"samplers\"" "\"scale\"" "\"scene\"" "\"scenes\"" "\"skeleton\"" "\"skin\"" "\"skins\"" "\"source\"" "\"sparse\"" "\"strength\"" "\"target\"" "\"targets\"" "\"texCoord\"" "\"textureInfo\"" "\"texture\"" "\"textures\"" "\"translation\"" "\"type\"" "\"uri\"" "\"values\"" "\"version\"" "\"weights\"" "\"wrapS\"" "\"wrapT\"" "\"xmag\"" "\"yfov\"" "\"ymag\"" "\"zfar\"" "\"znear\"" # # AFL dictionary for GLTF extensions # ----------------------- "\"KHR_materials_unlit\"" "\"KHR_texture_basisu\"" "\"KHR_materials_pbrSpecularGlossiness\"" "\"diffuseFactor\"" "\"diffuseTexture\"" "\"specularFactor\"" "\"glossinessFactor\"" "\"specularGlossinessTexture\"" "\"KHR_texture_transform\"" "\"offset\"" "\"rotation\"" "\"scale\"" "\"texCoord\"" "\"KHR_lights_punctual\"" "\"color\"" "\"intensity\"" "\"type\"" "\"range\"" "\"innerConeAngle\"" "\"outerConeAngle\"" "\"KHR_materials_transmission\"" "\"transmissionFactor\"" "\"transmissionTexture\"" "\"KHR_materials_volume\"" "\"thicknessFactor\"" "\"thicknessTexture\"" "\"attenuationColor\"" "\"attenuationDistance\"" "\"KHR_materials_sheen\"" "\"sheenColorFactor\"" "\"sheenColorTexture\"" "\"sheenRoughnessFactor\"" "\"sheenRoughnessTexture\"" "\"KHR_materials_emissive_strength\"" "\"emissiveStrength"\"" ================================================ FILE: gltf/gltfpack.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include #include #include #include #include #include #include #ifdef __wasi__ #include #endif #include "../src/meshoptimizer.h" std::string getVersion() { char result[32]; snprintf(result, sizeof(result), "%d.%d", MESHOPTIMIZER_VERSION / 1000, (MESHOPTIMIZER_VERSION % 1000) / 10); return result; } static void finalizeBufferViews(std::string& json, std::vector& views, std::string& bin, std::string* fallback, size_t& fallback_size, const char* meshopt_ext, int attribute_level) { for (size_t i = 0; i < views.size(); ++i) { BufferView& view = views[i]; size_t bin_offset = bin.size(); size_t fallback_offset = fallback_size; size_t count = view.data.size() / view.stride; if (view.compression == BufferView::Compression_None) { bin += view.data; } else { switch (view.compression) { case BufferView::Compression_Attribute: compressVertexStream(bin, view.data, count, view.stride, attribute_level); break; case BufferView::Compression_Index: compressIndexStream(bin, view.data, count, view.stride); break; case BufferView::Compression_IndexSequence: compressIndexSequence(bin, view.data, count, view.stride); break; default: assert(!"Unknown compression type"); } if (fallback) *fallback += view.data; fallback_size += view.data.size(); } size_t raw_offset = (view.compression != BufferView::Compression_None) ? fallback_offset : bin_offset; comma(json); writeBufferView(json, view.kind, view.filter, count, view.stride, raw_offset, view.data.size(), view.compression, bin_offset, bin.size() - bin_offset, meshopt_ext); // record written bytes for statistics view.bytes = bin.size() - bin_offset; // align each bufferView by 4 bytes bin.resize((bin.size() + 3) & ~3); if (fallback) fallback->resize((fallback->size() + 3) & ~3); fallback_size = (fallback_size + 3) & ~3; } } static void printMeshStats(const std::vector& meshes, const char* name) { size_t mesh_triangles = 0; size_t mesh_vertices = 0; size_t total_triangles = 0; size_t total_instances = 0; size_t total_draws = 0; for (size_t i = 0; i < meshes.size(); ++i) { const Mesh& mesh = meshes[i]; size_t triangles = mesh.type == cgltf_primitive_type_triangles ? mesh.indices.size() / 3 : 0; mesh_triangles += triangles; mesh_vertices += mesh.streams.empty() ? 0 : mesh.streams[0].data.size(); size_t instances = std::max(size_t(1), mesh.nodes.size() + mesh.instances.size()); total_triangles += triangles * instances; total_instances += instances; total_draws += std::max(size_t(1), mesh.nodes.size()); } printf("%s: %d mesh primitives (%d triangles, %d vertices); %d draw calls (%d instances, %lld triangles)\n", name, int(meshes.size()), int(mesh_triangles), int(mesh_vertices), int(total_draws), int(total_instances), (long long)total_triangles); } static void printSceneStats(const std::vector& views, const std::vector& meshes, size_t node_offset, size_t mesh_offset, size_t material_offset, size_t json_size, size_t bin_size) { size_t bytes[BufferView::Kind_Count] = {}; for (size_t i = 0; i < views.size(); ++i) { const BufferView& view = views[i]; bytes[view.kind] += view.bytes; } printf("output: %d nodes, %d meshes (%d primitives), %d materials\n", int(node_offset), int(mesh_offset), int(meshes.size()), int(material_offset)); printf("output: JSON %d bytes, buffers %d bytes\n", int(json_size), int(bin_size)); printf("output: buffers: vertex %d bytes, index %d bytes, skin %d bytes, time %d bytes, keyframe %d bytes, instance %d bytes, image %d bytes\n", int(bytes[BufferView::Kind_Vertex]), int(bytes[BufferView::Kind_Index]), int(bytes[BufferView::Kind_Skin]), int(bytes[BufferView::Kind_Time]), int(bytes[BufferView::Kind_Keyframe]), int(bytes[BufferView::Kind_Instance]), int(bytes[BufferView::Kind_Image])); } static void printAttributeStats(const std::vector& views, BufferView::Kind kind, const char* name) { for (size_t i = 0; i < views.size(); ++i) { const BufferView& view = views[i]; if (view.kind != kind) continue; const char* variant = "unknown"; switch (kind) { case BufferView::Kind_Vertex: variant = attributeType(cgltf_attribute_type(view.variant)); break; case BufferView::Kind_Index: variant = "index"; break; case BufferView::Kind_Keyframe: case BufferView::Kind_Instance: variant = animationPath(cgltf_animation_path_type(view.variant)); break; default:; } size_t count = view.data.size() / view.stride; if (view.compression == BufferView::Compression_None) printf("stats: %s %s: %d bytes (%.1f bits)\n", name, variant, int(view.bytes), double(view.bytes) / double(count) * 8); else printf("stats: %s %s: compressed %d bytes (%.1f bits), raw %d bytes (%d bits)\n", name, variant, int(view.bytes), double(view.bytes) / double(count) * 8, int(view.data.size()), int(view.stride * 8)); } } static void printImageStats(const std::vector& views, TextureKind kind, const char* name) { size_t bytes = 0; size_t count = 0; for (size_t i = 0; i < views.size(); ++i) { const BufferView& view = views[i]; if (view.kind != BufferView::Kind_Image) continue; if (view.variant != -1 - kind) continue; count += 1; bytes += view.data.size(); } if (count) printf("stats: image %s: %d bytes in %d images\n", name, int(bytes), int(count)); } static bool printReport(const char* path, const std::vector& views, const std::vector& meshes, size_t node_count, size_t mesh_count, size_t texture_count, size_t material_count, size_t animation_count, size_t json_size, size_t bin_size) { size_t bytes[BufferView::Kind_Count] = {}; for (size_t i = 0; i < views.size(); ++i) { const BufferView& view = views[i]; bytes[view.kind] += view.bytes; } size_t total_triangles = 0; size_t total_instances = 0; size_t total_draws = 0; for (size_t i = 0; i < meshes.size(); ++i) { const Mesh& mesh = meshes[i]; size_t triangles = mesh.type == cgltf_primitive_type_triangles ? mesh.indices.size() / 3 : 0; size_t instances = std::max(size_t(1), mesh.nodes.size() + mesh.instances.size()); total_triangles += triangles * instances; total_instances += instances; total_draws += std::max(size_t(1), mesh.nodes.size()); } FILE* out = fopen(path, "wb"); if (!out) return false; fprintf(out, "{\n"); fprintf(out, "\t\"generator\": \"gltfpack %s\",\n", getVersion().c_str()); fprintf(out, "\t\"scene\": {\n"); fprintf(out, "\t\t\"nodeCount\": %d,\n", int(node_count)); fprintf(out, "\t\t\"meshCount\": %d,\n", int(mesh_count)); fprintf(out, "\t\t\"materialCount\": %d,\n", int(material_count)); fprintf(out, "\t\t\"textureCount\": %d,\n", int(texture_count)); fprintf(out, "\t\t\"animationCount\": %d\n", int(animation_count)); fprintf(out, "\t},\n"); fprintf(out, "\t\"render\": {\n"); fprintf(out, "\t\t\"drawCount\": %d,\n", int(total_draws)); fprintf(out, "\t\t\"instanceCount\": %d,\n", int(total_instances)); fprintf(out, "\t\t\"triangleCount\": %lld\n", (long long)total_triangles); fprintf(out, "\t},\n"); fprintf(out, "\t\"data\": {\n"); fprintf(out, "\t\t\"json\": %d,\n", int(json_size)); fprintf(out, "\t\t\"binary\": %d,\n", int(bin_size)); fprintf(out, "\t\t\"buffers\": {\n"); fprintf(out, "\t\t\t\"vertex\": %d,\n", int(bytes[BufferView::Kind_Vertex])); fprintf(out, "\t\t\t\"index\": %d,\n", int(bytes[BufferView::Kind_Index])); fprintf(out, "\t\t\t\"animation\": %d,\n", int(bytes[BufferView::Kind_Time] + bytes[BufferView::Kind_Keyframe])); fprintf(out, "\t\t\t\"transform\": %d,\n", int(bytes[BufferView::Kind_Skin] + bytes[BufferView::Kind_Instance])); fprintf(out, "\t\t\t\"image\": %d\n", int(bytes[BufferView::Kind_Image])); fprintf(out, "\t\t}\n"); fprintf(out, "\t}\n"); fprintf(out, "}\n"); int rc = fclose(out); return rc == 0; } static bool canTransformMesh(const Mesh& mesh) { // volume thickness is specified in mesh coordinate space; to avoid modifying materials we prohibit transforming meshes with volume materials if (mesh.material && mesh.material->has_volume && mesh.material->volume.thickness_factor > 0.f) return false; return true; } static void detachMesh(Mesh& mesh, cgltf_data* data, const std::vector& nodes, const Settings& settings) { // mesh is already instanced, skip if (!mesh.instances.empty()) return; // mesh is already world space, skip if (mesh.nodes.empty()) return; // note: when -kn is specified, we keep mesh-node attachment so that named nodes can be transformed if (settings.keep_nodes) return; // we keep skinned meshes or meshes with morph targets as is // in theory we could transform both, but in practice transforming morph target meshes is more involved, // and reparenting skinned meshes leads to incorrect bounding box generated in three.js if (mesh.skin || mesh.targets) return; bool any_animated = false; for (size_t j = 0; j < mesh.nodes.size(); ++j) any_animated |= nodes[mesh.nodes[j] - data->nodes].animated; // animated meshes will be anchored to the same node that they used to be in to retain the animation if (any_animated) return; int scene = nodes[mesh.nodes[0] - data->nodes].scene; bool any_other_scene = false; for (size_t j = 0; j < mesh.nodes.size(); ++j) any_other_scene |= scene != nodes[mesh.nodes[j] - data->nodes].scene; // we only merge instances when all nodes have a single consistent scene if (scene < 0 || any_other_scene) return; // we only merge multiple instances together if requested // this often makes the scenes faster to render by reducing the draw call count, but can result in larger files if (mesh.nodes.size() > 1 && !settings.mesh_merge && !settings.mesh_instancing) return; // mesh has duplicate geometry; detaching it would increase the size due to unique world-space transforms if (mesh.nodes.size() == 1 && mesh.geometry_duplicate && !settings.mesh_merge) return; // prefer instancing if possible, use merging otherwise if (mesh.nodes.size() > 1 && settings.mesh_instancing) { mesh.instances.resize(mesh.nodes.size()); for (size_t j = 0; j < mesh.nodes.size(); ++j) { Instance& obj = mesh.instances[j]; cgltf_node_transform_world(mesh.nodes[j], obj.transform); obj.color[0] = obj.color[1] = obj.color[2] = obj.color[3] = 1.0f; } mesh.nodes.clear(); mesh.scene = scene; } else if (canTransformMesh(mesh)) { mergeMeshInstances(mesh); assert(mesh.nodes.empty()); mesh.scene = scene; } } static bool isExtensionSupported(const ExtensionInfo* extensions, size_t count, const char* name) { for (size_t i = 0; i < count; ++i) if (strcmp(extensions[i].name, name) == 0) return true; return false; } namespace std { template <> struct hash > { size_t operator()(const std::pair& x) const { return std::hash()(x.first ^ x.second); } }; } // namespace std static size_t process(cgltf_data* data, const char* input_path, const char* output_path, const char* report_path, std::vector& meshes, std::vector& animations, const Settings& settings, std::string& json, std::string& bin, std::string& fallback, size_t& fallback_size, const char* meshopt_ext) { if (settings.verbose) { printf("input: %d nodes, %d meshes (%d primitives), %d materials, %d skins, %d animations, %d images\n", int(data->nodes_count), int(data->meshes_count), int(meshes.size()), int(data->materials_count), int(data->skins_count), int(animations.size()), int(data->images_count)); printMeshStats(meshes, "input"); } for (size_t i = 0; i < animations.size(); ++i) processAnimation(animations[i], settings); std::vector nodes(data->nodes_count); markScenes(data, nodes); markAnimated(data, nodes, animations); mergeMeshMaterials(data, meshes, settings); if (settings.mesh_dedup) dedupMeshes(meshes, settings); for (size_t i = 0; i < meshes.size(); ++i) detachMesh(meshes[i], data, nodes, settings); // material information is required for mesh and image processing std::vector materials(data->materials_count); std::vector textures(data->textures_count); std::vector images(data->images_count); // mark materials that need to keep blending due to mesh vertex colors for (size_t i = 0; i < meshes.size(); ++i) { Mesh& mesh = meshes[i]; // skip hasAlpha check unless it's required if ((((mesh.material && mesh.material->alpha_mode != cgltf_alpha_mode_opaque) || mesh.variants.size()) && hasVertexAlpha(mesh)) || hasInstanceAlpha(mesh.instances)) { if (mesh.material) materials[mesh.material - data->materials].mesh_alpha = true; for (size_t j = 0; j < mesh.variants.size(); ++j) materials[mesh.variants[j].material - data->materials].mesh_alpha = true; } } analyzeMaterials(data, materials, textures, images); mergeTextures(data, textures); optimizeMaterials(data, materials, images, input_path); // streams need to be filtered before mesh merging (or processing) to make sure we can merge meshes with redundant streams for (size_t i = 0; i < meshes.size(); ++i) { Mesh& mesh = meshes[i]; MaterialInfo mi = mesh.material ? materials[mesh.material - data->materials] : MaterialInfo(); // merge material requirements across all variants for (size_t j = 0; j < mesh.variants.size(); ++j) { MaterialInfo vi = materials[mesh.variants[j].material - data->materials]; mi.needs_tangents |= vi.needs_tangents; mi.texture_set_mask |= vi.texture_set_mask; mi.unlit &= vi.unlit; } if (!settings.keep_attributes) filterStreams(mesh, mi); } mergeMeshes(meshes, settings); filterEmptyMeshes(meshes); markNeededNodes(data, nodes, meshes, animations, settings); markNeededMaterials(data, materials, meshes, settings); if (settings.simplify_scaled && settings.simplify_ratio < 1) computeMeshQuality(meshes); for (size_t i = 0; i < meshes.size(); ++i) { Mesh& mesh = meshes[i]; processMesh(mesh, settings); if (mesh.geometry_duplicate) hashMesh(mesh); } filterEmptyMeshes(meshes); // some meshes may become empty after processing QuantizationPosition qp = prepareQuantizationPosition(meshes, settings); std::vector qt_materials(materials.size()); std::vector qt_meshes(meshes.size(), size_t(-1)); prepareQuantizationTexture(data, qt_materials, qt_meshes, meshes, settings); QuantizationTexture qt_dummy = {}; qt_dummy.bits = settings.tex_bits; std::string json_images; std::string json_samplers; std::string json_textures; std::string json_materials; std::string json_accessors; std::string json_meshes; std::string json_nodes; std::string json_skins; std::vector json_roots(data->scenes_count); std::string json_animations; std::string json_cameras; std::string json_extensions; std::vector views; bool ext_pbr_specular_glossiness = false; bool ext_clearcoat = false; bool ext_transmission = false; bool ext_ior = false; bool ext_specular = false; bool ext_sheen = false; bool ext_volume = false; bool ext_emissive_strength = false; bool ext_iridescence = false; bool ext_anisotropy = false; bool ext_dispersion = false; bool ext_diffuse_transmission = false; bool ext_unlit = false; bool ext_instancing = false; bool ext_texture_transform = false; bool ext_texture_basisu = false; bool ext_texture_webp = false; size_t accr_offset = 0; size_t node_offset = 0; size_t mesh_offset = 0; size_t texture_offset = 0; size_t material_offset = 0; for (size_t i = 0; i < data->samplers_count; ++i) { const cgltf_sampler& sampler = data->samplers[i]; comma(json_samplers); append(json_samplers, "{"); writeSampler(json_samplers, sampler); append(json_samplers, "}"); } std::vector encoded_images(data->images_count); #ifdef WITH_BASISU if (data->images_count && settings.texture_ktx2) encodeImagesBasis(encoded_images.data(), data, images, input_path, settings); #endif #ifdef WITH_LIBWEBP if (data->images_count && settings.texture_webp) encodeImagesWebP(encoded_images.data(), data, images, input_path, settings); #endif for (size_t i = 0; i < data->images_count; ++i) { const cgltf_image& image = data->images[i]; std::string* encoded = !encoded_images[i].empty() ? &encoded_images[i] : NULL; comma(json_images); append(json_images, "{"); writeImage(json_images, views, image, images[i], encoded, i, input_path, output_path, settings); append(json_images, "}"); if (encoded) *encoded = std::string(); // reclaim memory early } for (size_t i = 0; i < data->textures_count; ++i) { const cgltf_texture& texture = data->textures[i]; if (!textures[i].keep) continue; comma(json_textures); append(json_textures, "{"); writeTexture(json_textures, texture, texture.image ? &images[texture.image - data->images] : NULL, data, settings); append(json_textures, "}"); assert(textures[i].remap == int(texture_offset)); texture_offset++; ext_texture_basisu = ext_texture_basisu || texture.has_basisu; ext_texture_webp = ext_texture_webp || texture.has_webp; } for (size_t i = 0; i < data->materials_count; ++i) { MaterialInfo& mi = materials[i]; if (!mi.keep) continue; const cgltf_material& material = data->materials[i]; comma(json_materials); append(json_materials, "{"); writeMaterial(json_materials, data, material, settings.quantize && !settings.pos_float ? &qp : NULL, settings.quantize && !settings.tex_float ? &qt_materials[i] : NULL, textures); if (settings.keep_extras) writeExtras(json_materials, material.extras); append(json_materials, "}"); mi.remap = int(material_offset); material_offset++; ext_pbr_specular_glossiness = ext_pbr_specular_glossiness || material.has_pbr_specular_glossiness; ext_clearcoat = ext_clearcoat || material.has_clearcoat; ext_transmission = ext_transmission || material.has_transmission; ext_ior = ext_ior || material.has_ior; ext_specular = ext_specular || material.has_specular; ext_sheen = ext_sheen || material.has_sheen; ext_volume = ext_volume || material.has_volume; ext_emissive_strength = ext_emissive_strength || material.has_emissive_strength; ext_iridescence = ext_iridescence || material.has_iridescence; ext_diffuse_transmission = ext_diffuse_transmission || material.has_diffuse_transmission; ext_anisotropy = ext_anisotropy || material.has_anisotropy; ext_dispersion = ext_dispersion || material.has_dispersion; ext_unlit = ext_unlit || material.unlit; ext_texture_transform = ext_texture_transform || mi.uses_texture_transform; } std::unordered_map, std::pair > primitive_cache; for (size_t i = 0; i < meshes.size(); ++i) { const Mesh& mesh = meshes[i]; comma(json_meshes); append(json_meshes, "{\"primitives\":["); size_t pi = i; for (; pi < meshes.size(); ++pi) { const Mesh& prim = meshes[pi]; if (prim.scene != mesh.scene || prim.skin != mesh.skin || prim.targets != mesh.targets) break; if (pi > i && (mesh.instances.size() || prim.instances.size())) break; if (!compareMeshNodes(mesh, prim)) break; if (!compareMeshTargets(mesh, prim)) break; const QuantizationTexture& qt = qt_meshes[pi] == size_t(-1) ? qt_dummy : qt_materials[qt_meshes[pi]]; comma(json_meshes); if (prim.geometry_duplicate) { std::pair& primitive_json = primitive_cache[std::make_pair(prim.geometry_hash[0], prim.geometry_hash[1])]; if (primitive_json.second) { // reuse previously written accessors json_meshes.append(json_meshes, primitive_json.first, primitive_json.second); } else { primitive_json.first = json_meshes.size(); writeMeshGeometry(json_meshes, views, json_accessors, accr_offset, prim, qp, qt, settings); primitive_json.second = json_meshes.size() - primitive_json.first; } } else { writeMeshGeometry(json_meshes, views, json_accessors, accr_offset, prim, qp, qt, settings); } if (prim.material) { MaterialInfo& mi = materials[prim.material - data->materials]; assert(mi.keep); append(json_meshes, ",\"material\":"); append(json_meshes, size_t(mi.remap)); } if (prim.variants.size()) { append(json_meshes, ",\"extensions\":{\"KHR_materials_variants\":{\"mappings\":["); for (size_t j = 0; j < prim.variants.size(); ++j) { const cgltf_material_mapping& variant = prim.variants[j]; MaterialInfo& mi = materials[variant.material - data->materials]; assert(mi.keep); comma(json_meshes); append(json_meshes, "{\"material\":"); append(json_meshes, size_t(mi.remap)); append(json_meshes, ",\"variants\":["); append(json_meshes, size_t(variant.variant)); append(json_meshes, "]}"); } append(json_meshes, "]}}"); } if (settings.keep_extras) writeExtras(json_meshes, prim.extras); append(json_meshes, "}"); } append(json_meshes, "]"); if (mesh.target_weights.size()) { append(json_meshes, ",\"weights\":"); append(json_meshes, mesh.target_weights.data(), mesh.target_weights.size()); } if (mesh.target_names.size()) { append(json_meshes, ",\"extras\":{\"targetNames\":["); for (size_t j = 0; j < mesh.target_names.size(); ++j) { comma(json_meshes); append(json_meshes, "\""); append(json_meshes, mesh.target_names[j]); append(json_meshes, "\""); } append(json_meshes, "]}"); } append(json_meshes, "}"); if (mesh.nodes.size()) { for (size_t j = 0; j < mesh.nodes.size(); ++j) { NodeInfo& ni = nodes[mesh.nodes[j] - data->nodes]; assert(ni.keep); // if we don't use position quantization, prefer attaching the mesh to its node directly if (!ni.has_mesh && (!settings.quantize || settings.pos_float || (qp.offset[0] == 0.f && qp.offset[1] == 0.f && qp.offset[2] == 0 && qp.node_scale == 1.f))) { ni.has_mesh = true; ni.mesh_index = mesh_offset; ni.mesh_skin = mesh.skin; } else { ni.mesh_nodes.push_back(node_offset); writeMeshNode(json_nodes, mesh_offset, mesh.nodes[j], mesh.skin, data, settings.quantize && !settings.pos_float ? &qp : NULL); node_offset++; } } } if (mesh.instances.size()) { assert(mesh.scene >= 0); comma(json_roots[mesh.scene]); append(json_roots[mesh.scene], node_offset); bool has_color = false; for (const Instance& instance : mesh.instances) has_color |= (instance.color[0] != 1.f || instance.color[1] != 1.f || instance.color[2] != 1.f || instance.color[3] != 1.f); size_t instance_accr = writeInstances(views, json_accessors, accr_offset, mesh.instances, qp, has_color, settings); assert(!mesh.skin); writeMeshNodeInstanced(json_nodes, mesh_offset, instance_accr, has_color); node_offset++; } if (mesh.nodes.empty() && mesh.instances.empty()) { assert(mesh.scene >= 0); comma(json_roots[mesh.scene]); append(json_roots[mesh.scene], node_offset); writeMeshNode(json_nodes, mesh_offset, NULL, mesh.skin, data, settings.quantize && !settings.pos_float ? &qp : NULL); node_offset++; } mesh_offset++; ext_instancing = ext_instancing || !mesh.instances.empty(); // skip all meshes that we've written in this iteration assert(pi > i); i = pi - 1; } remapNodes(data, nodes, node_offset); for (size_t i = 0; i < data->nodes_count; ++i) { NodeInfo& ni = nodes[i]; if (!ni.keep) continue; const cgltf_node& node = data->nodes[i]; comma(json_nodes); append(json_nodes, "{"); writeNode(json_nodes, node, nodes, data); if (settings.keep_extras) writeExtras(json_nodes, node.extras); append(json_nodes, "}"); } for (size_t i = 0; i < data->scenes_count; ++i) { for (size_t j = 0; j < data->scenes[i].nodes_count; ++j) { NodeInfo& ni = nodes[data->scenes[i].nodes[j] - data->nodes]; if (ni.keep) { comma(json_roots[i]); append(json_roots[i], size_t(ni.remap)); } } } for (size_t i = 0; i < data->skins_count; ++i) { const cgltf_skin& skin = data->skins[i]; size_t matrix_accr = writeJointBindMatrices(views, json_accessors, accr_offset, skin, qp, settings); writeSkin(json_skins, skin, matrix_accr, nodes, data); } for (size_t i = 0; i < animations.size(); ++i) { const Animation& animation = animations[i]; writeAnimation(json_animations, views, json_accessors, accr_offset, animation, i, data, nodes, settings); } for (size_t i = 0; i < data->cameras_count; ++i) { const cgltf_camera& camera = data->cameras[i]; writeCamera(json_cameras, camera); } if (data->lights_count > 0) { comma(json_extensions); append(json_extensions, "\"KHR_lights_punctual\":{\"lights\":["); for (size_t i = 0; i < data->lights_count; ++i) { const cgltf_light& light = data->lights[i]; writeLight(json_extensions, light); } append(json_extensions, "]}"); } if (data->variants_count > 0) { comma(json_extensions); append(json_extensions, "\"KHR_materials_variants\":{\"variants\":["); for (size_t i = 0; i < data->variants_count; ++i) { const cgltf_material_variant& variant = data->variants[i]; comma(json_extensions); append(json_extensions, "{\"name\":\""); append(json_extensions, variant.name); append(json_extensions, "\"}"); } append(json_extensions, "]}"); } append(json, "\"asset\":{"); append(json, "\"version\":\"2.0\",\"generator\":\"gltfpack "); append(json, getVersion()); append(json, "\""); writeExtras(json, data->asset.extras); append(json, "}"); const ExtensionInfo extensions[] = { {"KHR_mesh_quantization", settings.quantize, true}, {meshopt_ext, settings.compress, !settings.fallback}, {"KHR_texture_transform", (settings.quantize && !settings.tex_float && !json_textures.empty()) || ext_texture_transform, false}, {"KHR_materials_pbrSpecularGlossiness", ext_pbr_specular_glossiness, false}, {"KHR_materials_clearcoat", ext_clearcoat, false}, {"KHR_materials_transmission", ext_transmission, false}, {"KHR_materials_ior", ext_ior, false}, {"KHR_materials_specular", ext_specular, false}, {"KHR_materials_sheen", ext_sheen, false}, {"KHR_materials_volume", ext_volume, false}, {"KHR_materials_emissive_strength", ext_emissive_strength, false}, {"KHR_materials_iridescence", ext_iridescence, false}, {"KHR_materials_anisotropy", ext_anisotropy, false}, {"KHR_materials_dispersion", ext_dispersion, false}, {"KHR_materials_diffuse_transmission", ext_diffuse_transmission, false}, {"KHR_materials_unlit", ext_unlit, false}, {"KHR_materials_variants", data->variants_count > 0, false}, {"KHR_lights_punctual", data->lights_count > 0, false}, {"KHR_texture_basisu", (!json_textures.empty() && settings.texture_ktx2) || ext_texture_basisu, true}, {"EXT_texture_webp", (!json_textures.empty() && settings.texture_webp) || ext_texture_webp, true}, {"EXT_mesh_gpu_instancing", ext_instancing, true}, }; for (size_t i = 0; i < data->extensions_required_count; ++i) { const char* ext = data->extensions_required[i]; if (!isExtensionSupported(extensions, sizeof(extensions) / sizeof(extensions[0]), ext) && strstr(ext, "_meshopt_compression") == NULL) fprintf(stderr, "Warning: required extension %s is not supported and will be skipped\n", ext); } writeExtensions(json, extensions, sizeof(extensions) / sizeof(extensions[0])); // buffers[] array to be inserted by the caller size_t bufferspec_pos = json.size(); std::string json_views; finalizeBufferViews(json_views, views, bin, settings.fallback ? &fallback : NULL, fallback_size, meshopt_ext, settings.compresskhr ? (settings.compressmore ? 3 : 2) : 0); writeArray(json, "bufferViews", json_views); writeArray(json, "accessors", json_accessors); writeArray(json, "samplers", json_samplers); writeArray(json, "images", json_images); writeArray(json, "textures", json_textures); writeArray(json, "materials", json_materials); writeArray(json, "meshes", json_meshes); writeArray(json, "skins", json_skins); writeArray(json, "animations", json_animations); writeArray(json, "nodes", json_nodes); if (!json_roots.empty()) { append(json, ",\"scenes\":["); for (size_t i = 0; i < data->scenes_count; ++i) writeScene(json, data->scenes[i], json_roots[i], settings); append(json, "]"); } writeArray(json, "cameras", json_cameras); if (data->scene) { append(json, ",\"scene\":"); append(json, size_t(data->scene - data->scenes)); } if (!json_extensions.empty()) { append(json, ",\"extensions\":{"); append(json, json_extensions); append(json, "}"); } if (settings.verbose) { printMeshStats(meshes, "output"); printSceneStats(views, meshes, node_offset, mesh_offset, material_offset, json.size(), bin.size()); } if (settings.verbose > 1) { printAttributeStats(views, BufferView::Kind_Vertex, "vertex"); printAttributeStats(views, BufferView::Kind_Index, "index"); printAttributeStats(views, BufferView::Kind_Keyframe, "keyframe"); printAttributeStats(views, BufferView::Kind_Instance, "instance"); printImageStats(views, TextureKind_Generic, "generic"); printImageStats(views, TextureKind_Color, "color"); printImageStats(views, TextureKind_Normal, "normal"); printImageStats(views, TextureKind_Attrib, "attrib"); } if (report_path) { if (!printReport(report_path, views, meshes, node_offset, mesh_offset, texture_offset, material_offset, animations.size(), json.size(), bin.size())) { fprintf(stderr, "Warning: cannot save report to %s\n", report_path); } } return bufferspec_pos; } static void writeU32(FILE* out, uint32_t data) { fwrite(&data, 4, 1, out); } static const char* getBaseName(const char* path) { const char* slash = strrchr(path, '/'); const char* backslash = strrchr(path, '\\'); const char* rs = slash ? slash + 1 : path; const char* bs = backslash ? backslash + 1 : path; return std::max(rs, bs); } static std::string getBufferSpec(const char* bin_path, size_t bin_size, const char* fallback_path, size_t fallback_size, bool fallback_ref, const char* meshopt_ext) { std::string json; append(json, "\"buffers\":["); append(json, "{"); if (bin_path) { append(json, "\"uri\":\""); append(json, bin_path); append(json, "\""); } comma(json); append(json, "\"byteLength\":"); append(json, bin_size); append(json, "}"); if (fallback_ref) { comma(json); append(json, "{"); if (fallback_path) { append(json, "\"uri\":\""); append(json, fallback_path); append(json, "\""); } comma(json); append(json, "\"byteLength\":"); append(json, fallback_size); append(json, ",\"extensions\":{"); append(json, "\""); append(json, meshopt_ext); append(json, "\":{"); append(json, "\"fallback\":true"); append(json, "}}"); append(json, "}"); } append(json, "]"); return json; } int gltfpack(const char* input, const char* output, const char* report, Settings settings) { cgltf_data* data = NULL; std::vector meshes; std::vector animations; std::string iext = getExtension(input); std::string oext = output ? getExtension(output) : ""; if (output) { if (oext != ".gltf" && oext != ".glb") { fprintf(stderr, "Error: unsupported output extension '%s' (expected .gltf or .glb)\n", oext.c_str()); return 4; } } if (iext == ".gltf" || iext == ".glb") { const char* error = NULL; data = parseGltf(input, meshes, animations, &error); if (error) { fprintf(stderr, "Error loading %s: %s\n", input, error); return 2; } } else if (iext == ".obj") { const char* error = NULL; data = parseObj(input, meshes, &error); if (!data) { fprintf(stderr, "Error loading %s: %s\n", input, error); return 2; } } else { fprintf(stderr, "Error loading %s: unknown extension (expected .gltf or .glb or .obj)\n", input); return 2; } #ifndef WITH_BASISU if (data->images_count && settings.texture_ktx2) { fprintf(stderr, "Error: gltfpack was built without BasisU support, texture compression is not available\n"); #ifdef __wasi__ fprintf(stderr, "Note: node.js builds do not support BasisU due to lack of platform features; download a native build from https://github.com/zeux/meshoptimizer/releases\n"); #endif return 3; } #endif #ifndef WITH_LIBWEBP if (data->images_count && settings.texture_webp) { fprintf(stderr, "Error: gltfpack was built without WebP support, texture compression is not available\n"); #ifdef __wasi__ fprintf(stderr, "Note: node.js builds do not support WebP due to lack of platform features; download a native build from https://github.com/zeux/meshoptimizer/releases\n"); #endif return 3; } #endif if (oext == ".glb") { settings.texture_embed = true; } if (data->images_count && !settings.texture_ref && !settings.texture_embed) { for (size_t i = 0; i < data->images_count; ++i) { const char* uri = data->images[i].uri; if (!uri || strncmp(uri, "data:", 5) == 0) continue; for (size_t j = 0; j < i; ++j) { const char* urj = data->images[j].uri; if (!urj || strncmp(urj, "data:", 5) == 0) continue; if (strcmp(uri, urj) != 0 && strcmp(getBaseName(uri), getBaseName(urj)) == 0) { fprintf(stderr, "Warning: images %s and %s share the same base name and will overwrite each other\n", uri, urj); break; } } } } const char* meshopt_ext = settings.compresskhr ? "KHR_meshopt_compression" : "EXT_meshopt_compression"; std::string json, bin, fallback; size_t fallback_size = 0; json += '{'; size_t bufferspec_pos = process(data, input, output, report, meshes, animations, settings, json, bin, fallback, fallback_size, meshopt_ext); json += '}'; cgltf_free(data); if (!output) { return 0; } if (oext == ".gltf") { std::string binpath = output; binpath.replace(binpath.size() - 5, 5, ".bin"); std::string fbpath = output; fbpath.replace(fbpath.size() - 5, 5, ".fallback.bin"); FILE* outjson = fopen(output, "wb"); FILE* outbin = fopen(binpath.c_str(), "wb"); FILE* outfb = settings.fallback ? fopen(fbpath.c_str(), "wb") : NULL; if (!outjson || !outbin || (!outfb && settings.fallback)) { fprintf(stderr, "Error saving %s\n", output); return 4; } std::string bufferspec = getBufferSpec(getBaseName(binpath.c_str()), bin.size(), settings.fallback ? getBaseName(fbpath.c_str()) : NULL, fallback_size, settings.compress, meshopt_ext); json.insert(bufferspec_pos, "," + bufferspec); fwrite(json.c_str(), json.size(), 1, outjson); fwrite(bin.c_str(), bin.size(), 1, outbin); if (settings.fallback) fwrite(fallback.c_str(), fallback.size(), 1, outfb); int rc = 0; rc |= fclose(outjson); rc |= fclose(outbin); if (outfb) rc |= fclose(outfb); if (rc) { fprintf(stderr, "Error saving %s\n", output); return 4; } } else if (oext == ".glb") { std::string fbpath = output; fbpath.replace(fbpath.size() - 4, 4, ".fallback.bin"); FILE* out = fopen(output, "wb"); FILE* outfb = settings.fallback ? fopen(fbpath.c_str(), "wb") : NULL; if (!out || (!outfb && settings.fallback)) { fprintf(stderr, "Error saving %s\n", output); return 4; } std::string bufferspec = getBufferSpec(NULL, bin.size(), settings.fallback ? getBaseName(fbpath.c_str()) : NULL, fallback_size, settings.compress, meshopt_ext); json.insert(bufferspec_pos, "," + bufferspec); while (json.size() % 4) json.push_back(' '); while (bin.size() % 4) bin.push_back('\0'); writeU32(out, 0x46546C67); writeU32(out, 2); writeU32(out, uint32_t(12 + 8 + json.size() + 8 + bin.size())); writeU32(out, uint32_t(json.size())); writeU32(out, 0x4E4F534A); fwrite(json.c_str(), json.size(), 1, out); writeU32(out, uint32_t(bin.size())); writeU32(out, 0x004E4942); fwrite(bin.c_str(), bin.size(), 1, out); if (settings.fallback) fwrite(fallback.c_str(), fallback.size(), 1, outfb); int rc = 0; rc |= fclose(out); if (outfb) rc |= fclose(outfb); if (rc) { fprintf(stderr, "Error saving %s\n", output); return 4; } } else { fprintf(stderr, "Error saving %s: unknown extension (expected .gltf or .glb)\n", output); return 4; } return 0; } Settings defaults() { Settings settings = {}; settings.quantize = true; settings.pos_bits = 14; settings.tex_bits = 12; settings.nrm_bits = 8; settings.col_bits = 8; settings.trn_bits = 16; settings.rot_bits = 12; settings.scl_bits = 16; settings.anim_freq = 30; settings.mesh_dedup = true; settings.simplify_ratio = 1.f; settings.simplify_error = 1e-2f; settings.simplify_attributes = true; settings.simplify_scaled = true; for (int kind = 0; kind < TextureKind__Count; ++kind) { settings.texture_mode[kind] = TextureMode_Raw; settings.texture_scale[kind] = 1.f; settings.texture_quality[kind] = 8; } return settings; } template T clamp(T v, T min, T max) { return v < min ? min : (v > max ? max : v); } unsigned int textureMask(const char* arg) { unsigned int result = 0; while (arg) { const char* comma = strchr(arg, ','); size_t seg = comma ? comma - arg - 1 : strlen(arg); if (strncmp(arg, "color", seg) == 0) result |= 1 << TextureKind_Color; else if (strncmp(arg, "normal", seg) == 0) result |= 1 << TextureKind_Normal; else if (strncmp(arg, "attrib", seg) == 0) result |= 1 << TextureKind_Attrib; else fprintf(stderr, "Warning: unrecognized texture class %.*s\n", int(seg), arg); arg = comma ? comma + 1 : NULL; } return result; } template void applySetting(T (&data)[TextureKind__Count], T value, unsigned int mask = ~0u) { for (int kind = 0; kind < TextureKind__Count; ++kind) if (mask & (1 << kind)) data[kind] = value; } #ifndef GLTFFUZZ int main(int argc, char** argv) { #ifndef __wasi__ setlocale(LC_ALL, "C"); // disable locale specific convention for number parsing/printing #endif meshopt_encodeVertexVersion(0); meshopt_encodeIndexVersion(1); Settings settings = defaults(); const char* input = NULL; const char* output = NULL; const char* report = NULL; bool help = false; bool test = false; bool require_texc = false; std::vector testinputs; for (int i = 1; i < argc; ++i) { const char* arg = argv[i]; if (strcmp(arg, "-vp") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { settings.pos_bits = clamp(atoi(argv[++i]), 1, 16); } else if (strcmp(arg, "-vt") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { settings.tex_bits = clamp(atoi(argv[++i]), 1, 16); } else if (strcmp(arg, "-vn") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { settings.nrm_bits = clamp(atoi(argv[++i]), 1, 16); } else if (strcmp(arg, "-vc") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { settings.col_bits = clamp(atoi(argv[++i]), 1, 16); } else if (strcmp(arg, "-vpi") == 0) { settings.pos_float = false; settings.pos_normalized = false; } else if (strcmp(arg, "-vpn") == 0) { settings.pos_float = false; settings.pos_normalized = true; } else if (strcmp(arg, "-vpf") == 0) { settings.pos_float = true; } else if (strcmp(arg, "-vtf") == 0) { settings.tex_float = true; } else if (strcmp(arg, "-vnf") == 0) { settings.nrm_float = true; } else if (strcmp(arg, "-vi") == 0) { settings.mesh_interleaved = true; } else if (strcmp(arg, "-at") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { settings.trn_bits = clamp(atoi(argv[++i]), 1, 24); } else if (strcmp(arg, "-ar") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { settings.rot_bits = clamp(atoi(argv[++i]), 4, 16); } else if (strcmp(arg, "-as") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { settings.scl_bits = clamp(atoi(argv[++i]), 1, 24); } else if (strcmp(arg, "-af") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { settings.anim_freq = clamp(atoi(argv[++i]), 0, 100); } else if (strcmp(arg, "-ac") == 0) { settings.anim_const = true; } else if (strcmp(arg, "-kn") == 0) { settings.keep_nodes = true; } else if (strcmp(arg, "-km") == 0) { settings.keep_materials = true; } else if (strcmp(arg, "-ke") == 0) { settings.keep_extras = true; } else if (strcmp(arg, "-kv") == 0) { settings.keep_attributes = true; } else if (strcmp(arg, "-mdd") == 0) { fprintf(stderr, "Warning: option -mdd disables mesh deduplication and is temporary; avoid production usage\n"); settings.mesh_dedup = false; } else if (strcmp(arg, "-mm") == 0) { settings.mesh_merge = true; } else if (strcmp(arg, "-mi") == 0) { settings.mesh_instancing = true; } else if (strcmp(arg, "-si") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { settings.simplify_ratio = clamp(float(atof(argv[++i])), 0.f, 1.f); } else if (strcmp(arg, "-se") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { settings.simplify_error = clamp(float(atof(argv[++i])), 0.f, 1.f); } else if (strcmp(arg, "-sa") == 0) { settings.simplify_aggressive = true; } else if (strcmp(arg, "-slb") == 0) { settings.simplify_lock_borders = true; } else if (strcmp(arg, "-sv") == 0) { fprintf(stderr, "Warning: attribute aware simplification is enabled by default; option -sv is only provided for compatibility and may be removed in the future\n"); } else if (strcmp(arg, "-svd") == 0) { fprintf(stderr, "Warning: option -svd disables attribute aware simplification and is temporary; avoid production usage\n"); settings.simplify_attributes = false; } else if (strcmp(arg, "-ssd") == 0) { fprintf(stderr, "Warning: option -ssd disables scaled simplification error and is temporary; avoid production usage\n"); settings.simplify_scaled = false; } else if (strcmp(arg, "-sp") == 0) { settings.simplify_permissive = true; } else if (strcmp(arg, "-tu") == 0) { settings.texture_ktx2 = true; unsigned int mask = ~0u; if (i + 1 < argc && isalpha(argv[i + 1][0])) mask = textureMask(argv[++i]); applySetting(settings.texture_mode, TextureMode_UASTC, mask); } else if (strcmp(arg, "-tc") == 0) { settings.texture_ktx2 = true; unsigned int mask = ~0u; if (i + 1 < argc && isalpha(argv[i + 1][0])) mask = textureMask(argv[++i]); applySetting(settings.texture_mode, TextureMode_ETC1S, mask); } else if (strcmp(arg, "-tw") == 0) { settings.texture_webp = true; unsigned int mask = ~0u; if (i + 1 < argc && isalpha(argv[i + 1][0])) mask = textureMask(argv[++i]); applySetting(settings.texture_mode, TextureMode_WebP, mask); } else if (strcmp(arg, "-tq") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { require_texc = true; int quality = clamp(atoi(argv[++i]), 1, 10); applySetting(settings.texture_quality, quality); } else if (strcmp(arg, "-tq") == 0 && i + 2 < argc && isalpha(argv[i + 1][0]) && isdigit(argv[i + 2][0])) { require_texc = true; unsigned int mask = textureMask(argv[++i]); int quality = clamp(atoi(argv[++i]), 1, 10); applySetting(settings.texture_quality, quality, mask); } else if (strcmp(arg, "-ts") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { require_texc = true; float scale = clamp(float(atof(argv[++i])), 0.f, 1.f); applySetting(settings.texture_scale, scale); } else if (strcmp(arg, "-ts") == 0 && i + 2 < argc && isalpha(argv[i + 1][0]) && isdigit(argv[i + 2][0])) { require_texc = true; unsigned int mask = textureMask(argv[++i]); float scale = clamp(float(atof(argv[++i])), 0.f, 1.f); applySetting(settings.texture_scale, scale, mask); } else if (strcmp(arg, "-tl") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { require_texc = true; int limit = atoi(argv[++i]); applySetting(settings.texture_limit, limit); } else if (strcmp(arg, "-tl") == 0 && i + 2 < argc && isalpha(argv[i + 1][0]) && isdigit(argv[i + 2][0])) { require_texc = true; unsigned int mask = textureMask(argv[++i]); int limit = atoi(argv[++i]); applySetting(settings.texture_limit, limit, mask); } else if (strcmp(arg, "-tp") == 0) { require_texc = true; settings.texture_pow2 = true; } else if (strcmp(arg, "-tfy") == 0) { require_texc = true; settings.texture_flipy = true; } else if (strcmp(arg, "-tr") == 0) { settings.texture_ref = true; } else if (strcmp(arg, "-tj") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { settings.texture_jobs = clamp(atoi(argv[++i]), 0, 128); } else if (strcmp(arg, "-noq") == 0) { // TODO: Warn if -noq is used and suggest -vpf instead; use -noqq to silence settings.quantize = false; } else if (strcmp(arg, "-i") == 0 && i + 1 < argc && !input) { input = argv[++i]; } else if (strcmp(arg, "-o") == 0 && i + 1 < argc && !output) { output = argv[++i]; } else if (strcmp(arg, "-r") == 0 && i + 1 < argc && !report) { report = argv[++i]; } else if (strcmp(arg, "-c") == 0) { settings.compress = true; } else if (strcmp(arg, "-cc") == 0) { settings.compress = true; settings.compressmore = true; } else if (strcmp(arg, "-cf") == 0) { settings.compress = true; settings.fallback = true; } else if (strcmp(arg, "-cz") == 0) { settings.compress = true; settings.compressmore = true; settings.compresskhr = true; } else if (strcmp(arg, "-ce") == 0 && i + 1 < argc && (strcmp(argv[i + 1], "khr") == 0 || strcmp(argv[i + 1], "ext") == 0)) { settings.compress = true; settings.compresskhr = strcmp(argv[++i], "khr") == 0; } else if (strcmp(arg, "-v") == 0) { settings.verbose = 1; } else if (strcmp(arg, "-vv") == 0) { settings.verbose = 2; } else if (strcmp(arg, "-h") == 0) { help = true; } else if (strcmp(arg, "-test") == 0) { test = true; } else if (arg[0] == '-') { fprintf(stderr, "Unrecognized option %s\n", arg); return 1; } else if (test) { testinputs.push_back(arg); } else { fprintf(stderr, "Expected option, got %s instead\n", arg); return 1; } } // shortcut for gltfpack -v if (settings.verbose && argc == 2) { printf("gltfpack %s\n", getVersion().c_str()); return 0; } if (test) { for (size_t i = 0; i < testinputs.size(); ++i) { const char* path = testinputs[i]; printf("%s\n", path); gltfpack(path, NULL, NULL, settings); } return 0; } if (!input || !output || help) { fprintf(stderr, "gltfpack %s\n", getVersion().c_str()); fprintf(stderr, "Usage: gltfpack [options] -i input -o output\n"); if (help) { fprintf(stderr, "\nBasics:\n"); fprintf(stderr, "\t-i file: input file to process, .obj/.gltf/.glb\n"); fprintf(stderr, "\t-o file: output file path, .gltf/.glb\n"); fprintf(stderr, "\t-c: produce compressed gltf/glb files (-cc/-cz for higher compression ratio)\n"); fprintf(stderr, "\nTextures:\n"); fprintf(stderr, "\t-tc: convert all textures to KTX2 with BasisU supercompression\n"); fprintf(stderr, "\t-tu: use UASTC when encoding textures (much higher quality and much larger size)\n"); fprintf(stderr, "\t-tw: convert all textures to WebP\n"); fprintf(stderr, "\t-tq N: set texture encoding quality (default: 8; N should be between 1 and 10)\n"); fprintf(stderr, "\t-ts R: scale texture dimensions by the ratio R (default: 1; R should be between 0 and 1)\n"); fprintf(stderr, "\t-tl N: limit texture dimensions to N pixels (default: 0 = no limit)\n"); fprintf(stderr, "\t-tp: resize textures to nearest power of 2 to conform to WebGL1 restrictions\n"); fprintf(stderr, "\t-tfy: flip textures along Y axis during BasisU supercompression\n"); fprintf(stderr, "\t-tj N: use N threads when compressing textures\n"); fprintf(stderr, "\t-tr: keep referring to original texture paths instead of copying/embedding images\n"); fprintf(stderr, "\tTexture classes:\n"); fprintf(stderr, "\t-tc C: use ETC1S when encoding textures of class C\n"); fprintf(stderr, "\t-tu C: use UASTC when encoding textures of class C\n"); fprintf(stderr, "\t-tw C: use WebP when encoding textures of class C\n"); fprintf(stderr, "\t-tq C N: set texture encoding quality for class C\n"); fprintf(stderr, "\t-ts C R: scale texture dimensions for class C\n"); fprintf(stderr, "\t-tl C N: limit texture dimensions for class C\n"); fprintf(stderr, "\t... where C is a comma-separated list (no spaces) with valid values color,normal,attrib\n"); fprintf(stderr, "\nSimplification:\n"); fprintf(stderr, "\t-si R: simplify meshes targeting triangle/point count ratio R (default: 1; R should be between 0 and 1)\n"); fprintf(stderr, "\t-se E: limit simplification error to E (default: 0.01 = 1%% deviation; E should be between 0 and 1)\n"); fprintf(stderr, "\t-sp: use permissive simplification mode to allow simplification across attribute discontinuities\n"); fprintf(stderr, "\t-sa: aggressively simplify to the target ratio disregarding quality\n"); fprintf(stderr, "\t-slb: lock border vertices during simplification to avoid gaps on connected meshes\n"); fprintf(stderr, "\nVertex precision:\n"); fprintf(stderr, "\t-vp N: use N-bit quantization for positions (default: 14; N should be between 1 and 16)\n"); fprintf(stderr, "\t-vt N: use N-bit quantization for texture coordinates (default: 12; N should be between 1 and 16)\n"); fprintf(stderr, "\t-vn N: use N-bit quantization for normals (default: 8; N should be between 1 and 16) and tangents (up to 8-bit)\n"); fprintf(stderr, "\t-vc N: use N-bit quantization for colors (default: 8; N should be between 1 and 16)\n"); fprintf(stderr, "\nVertex positions:\n"); fprintf(stderr, "\t-vpi: use integer attributes for positions (default)\n"); fprintf(stderr, "\t-vpn: use normalized attributes for positions\n"); fprintf(stderr, "\t-vpf: use floating point attributes for positions\n"); fprintf(stderr, "\nVertex attributes:\n"); fprintf(stderr, "\t-vtf: use floating point attributes for texture coordinates\n"); fprintf(stderr, "\t-vnf: use floating point attributes for normals\n"); fprintf(stderr, "\t-vi: use interleaved vertex attributes (reduces compression efficiency)\n"); fprintf(stderr, "\t-kv: keep source vertex attributes even if they aren't used\n"); fprintf(stderr, "\nAnimations:\n"); fprintf(stderr, "\t-at N: use N-bit quantization for translations (default: 16; N should be between 1 and 24)\n"); fprintf(stderr, "\t-ar N: use N-bit quantization for rotations (default: 12; N should be between 4 and 16)\n"); fprintf(stderr, "\t-as N: use N-bit quantization for scale (default: 16; N should be between 1 and 24)\n"); fprintf(stderr, "\t-af N: resample animations at N Hz (default: 30; use 0 to disable)\n"); fprintf(stderr, "\t-ac: keep constant animation tracks even if they don't modify the node transform\n"); fprintf(stderr, "\nScene:\n"); fprintf(stderr, "\t-kn: keep named nodes and meshes attached to named nodes so that named nodes can be transformed externally\n"); fprintf(stderr, "\t-km: keep named materials and disable named material merging\n"); fprintf(stderr, "\t-ke: keep extras data\n"); fprintf(stderr, "\t-mm: merge instances of the same mesh together when possible\n"); fprintf(stderr, "\t-mi: use EXT_mesh_gpu_instancing when serializing multiple mesh instances\n"); fprintf(stderr, "\nMiscellaneous:\n"); fprintf(stderr, "\t-cf: produce compressed gltf/glb files with fallback for loaders that don't support compression\n"); fprintf(stderr, "\t-ce ext|khr: use EXT or KHR version of meshopt compression extension for compression\n"); fprintf(stderr, "\t-noq: disable quantization; produces much larger glTF files with no extensions\n"); fprintf(stderr, "\t-v: verbose output (when used with other options)\n"); fprintf(stderr, "\t-v: print version (when used without other options)\n"); fprintf(stderr, "\t-r file: output a JSON report to file\n"); fprintf(stderr, "\t-h: display this help and exit\n"); } else { fprintf(stderr, "\nBasics:\n"); fprintf(stderr, "\t-i file: input file to process, .obj/.gltf/.glb\n"); fprintf(stderr, "\t-o file: output file path, .gltf/.glb\n"); fprintf(stderr, "\t-c: produce compressed gltf/glb files (-cc/-cz for higher compression ratio)\n"); fprintf(stderr, "\t-tc: convert all textures to KTX2 with BasisU supercompression\n"); fprintf(stderr, "\t-tw: convert all textures to WebP\n"); fprintf(stderr, "\t-si R: simplify meshes targeting triangle/point count ratio R (between 0 and 1)\n"); fprintf(stderr, "\nRun gltfpack -h to display a full list of options\n"); } return 1; } if (require_texc && !settings.texture_ktx2 && !settings.texture_webp) { fprintf(stderr, "Texture processing is only supported when texture compression is enabled via -tc/-tu/-tw\n"); return 1; } if (settings.texture_ref && (settings.texture_ktx2 || settings.texture_webp)) { fprintf(stderr, "Option -tr currently can not be used together with texture compression\n"); return 1; } if (settings.fallback && settings.compressmore) { fprintf(stderr, "Option -cf can not be used together with -cc\n"); return 1; } if (settings.fallback && (settings.pos_float || settings.tex_float || settings.nrm_float)) { fprintf(stderr, "Option -cf can not be used together with -vpf, -vtf or -vnf\n"); return 1; } if (settings.keep_nodes && (settings.mesh_merge || settings.mesh_instancing)) fprintf(stderr, "Warning: option -kn disables mesh merge (-mm) and mesh instancing (-mi) optimizations\n"); return gltfpack(input, output, report, settings); } #endif #ifdef __wasi__ extern "C" int pack(int argc, char** argv) { chdir("/gltfpack-$pwd"); int result = main(argc, argv); fflush(NULL); return result; } #endif #ifdef GLTFFUZZ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* buffer, size_t size) { Settings settings = defaults(); settings.texture_embed = true; std::vector meshes; std::vector animations; const char* error = NULL; cgltf_data* data = parseGlb(buffer, size, meshes, animations, &error); // this is a difficult tradeoff // returning 0 on files that fail to parse means that fuzzing is more incremental: files with errors are put into the corpus, // and the subsequent mutations may lead to discovering more interesting inputs, including valid ones that are difficult to find otherwise. // however, this leads to most of the corpus being invalid, and we very rarely get useful coverage for actual gltfpack processing. // for now we just focus on valid files, as we expect cgltf parser itself to be bulletproof as it's fuzzed separately. if (error) return -1; std::string json, bin, fallback; size_t fallback_size = 0; process(data, NULL, NULL, NULL, meshes, animations, settings, json, bin, fallback, fallback_size, NULL); cgltf_free(data); return 0; } #endif ================================================ FILE: gltf/gltfpack.h ================================================ /** * gltfpack - version 1.0 * * Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://github.com/zeux/meshoptimizer * * This application is distributed under the MIT License. See notice at the end of this file. */ #pragma once #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #ifndef _CRT_NONSTDC_NO_WARNINGS #define _CRT_NONSTDC_NO_WARNINGS #endif #include "../extern/cgltf.h" #include #include #include struct Attr { float f[4]; }; struct Stream { cgltf_attribute_type type; int index; int target; // 0 = base mesh, 1+ = morph target const char* custom_name; // only valid for cgltf_attribute_type_custom std::vector data; }; struct Instance { float transform[16]; float color[4]; }; struct Mesh { int scene; std::vector nodes; std::vector instances; cgltf_material* material; cgltf_skin* skin; cgltf_extras extras; cgltf_primitive_type type; std::vector streams; std::vector indices; bool geometry_duplicate; uint64_t geometry_hash[2]; size_t targets; std::vector target_weights; std::vector target_names; std::vector variants; float quality; }; struct Track { cgltf_node* node; cgltf_animation_path_type path; bool constant; bool dummy; size_t components; // 1 unless path is cgltf_animation_path_type_weights cgltf_interpolation_type interpolation; std::vector time; // empty for resampled or constant animations std::vector data; }; struct Animation { const char* name; float start; int frames; std::vector tracks; }; enum TextureKind { TextureKind_Generic, TextureKind_Color, TextureKind_Normal, TextureKind_Attrib, TextureKind__Count }; enum TextureMode { TextureMode_Raw, TextureMode_ETC1S, TextureMode_UASTC, TextureMode_WebP, }; struct Settings { int pos_bits; int tex_bits; int nrm_bits; int col_bits; bool pos_normalized; bool pos_float; bool tex_float; bool nrm_float; int trn_bits; int rot_bits; int scl_bits; int anim_freq; bool anim_const; bool keep_nodes; bool keep_materials; bool keep_extras; bool keep_attributes; bool mesh_dedup; bool mesh_merge; bool mesh_instancing; bool mesh_interleaved; float simplify_ratio; float simplify_error; bool simplify_aggressive; bool simplify_lock_borders; bool simplify_attributes; bool simplify_scaled; bool simplify_permissive; bool texture_ktx2; bool texture_webp; bool texture_embed; bool texture_ref; bool texture_pow2; bool texture_flipy; float texture_scale[TextureKind__Count]; int texture_limit[TextureKind__Count]; TextureMode texture_mode[TextureKind__Count]; int texture_quality[TextureKind__Count]; int texture_jobs; bool quantize; bool compress; bool compressmore; bool compresskhr; bool fallback; int verbose; }; struct QuantizationPosition { float offset[3]; float scale; int bits; bool normalized; float node_scale; // computed from scale/bits/normalized }; struct QuantizationTexture { float offset[2]; float scale[2]; int bits; bool normalized; }; struct StreamFormat { enum Filter { Filter_None, Filter_Oct, Filter_Quat, Filter_Exp, Filter_Color, }; cgltf_type type; cgltf_component_type component_type; bool normalized; size_t stride; Filter filter; }; struct NodeInfo { int scene; bool keep; bool animated; unsigned int animated_path_mask; int remap; std::vector mesh_nodes; bool has_mesh; size_t mesh_index; cgltf_skin* mesh_skin; }; struct MaterialInfo { bool keep; bool uses_texture_transform; bool needs_tangents; bool unlit; bool mesh_alpha; unsigned int texture_set_mask; int remap; }; struct TextureInfo { bool keep; int remap; }; struct ImageInfo { TextureKind kind; bool normal_map; bool srgb; int channels; }; struct ExtensionInfo { const char* name; bool used; bool required; }; struct BufferView { enum Kind { Kind_Vertex, Kind_Index, Kind_Skin, Kind_Time, Kind_Keyframe, Kind_Instance, Kind_Image, Kind_Count }; enum Compression { Compression_None = -1, Compression_Attribute, Compression_Index, Compression_IndexSequence, }; Kind kind; StreamFormat::Filter filter; Compression compression; size_t stride; int variant; std::string data; size_t bytes; }; std::string getTempPrefix(); std::string getFullPath(const char* path, const char* base_path); std::string getFileName(const char* path); std::string getExtension(const char* path); bool readFile(const char* path, std::string& data); bool writeFile(const char* path, const std::string& data); void removeFile(const char* path); cgltf_data* parseObj(const char* path, std::vector& meshes, const char** error); cgltf_data* parseGltf(const char* path, std::vector& meshes, std::vector& animations, const char** error); cgltf_data* parseGlb(const void* buffer, size_t size, std::vector& meshes, std::vector& animations, const char** error); bool areExtrasEqual(const cgltf_extras& lhs, const cgltf_extras& rhs); void processAnimation(Animation& animation, const Settings& settings); void processMesh(Mesh& mesh, const Settings& settings); bool compareMeshTargets(const Mesh& lhs, const Mesh& rhs); bool compareMeshVariants(const Mesh& lhs, const Mesh& rhs); bool compareMeshNodes(const Mesh& lhs, const Mesh& rhs); void hashMesh(Mesh& mesh); void dedupMeshes(std::vector& meshes, const Settings& settings); void mergeMeshInstances(Mesh& mesh); void mergeMeshes(std::vector& meshes, const Settings& settings); void filterEmptyMeshes(std::vector& meshes); void filterStreams(Mesh& mesh, const MaterialInfo& mi); void mergeMeshMaterials(cgltf_data* data, std::vector& meshes, const Settings& settings); void markNeededMaterials(cgltf_data* data, std::vector& materials, const std::vector& meshes, const Settings& settings); void mergeTextures(cgltf_data* data, std::vector& textures); bool hasValidTransform(const cgltf_texture_view& view); void analyzeMaterials(cgltf_data* data, std::vector& materials, std::vector& textures, std::vector& images); void optimizeMaterials(cgltf_data* data, std::vector& materials, std::vector& images, const char* input_path); bool readImage(const cgltf_image& image, const char* input_path, std::string& data, std::string& mime_type); bool hasAlpha(const std::string& data, const char* mime_type); bool getDimensions(const std::string& data, const char* mime_type, int& width, int& height); void adjustDimensions(int& width, int& height, float scale, int limit, bool pow2); const char* mimeExtension(const char* mime_type); #ifdef WITH_BASISU void encodeImagesBasis(std::string* encoded, const cgltf_data* data, const std::vector& images, const char* input_path, const Settings& settings); #endif #ifdef WITH_LIBWEBP void encodeImagesWebP(std::string* encoded, const cgltf_data* data, const std::vector& images, const char* input_path, const Settings& settings); #endif void markScenes(cgltf_data* data, std::vector& nodes); void markAnimated(cgltf_data* data, std::vector& nodes, const std::vector& animations); void markNeededNodes(cgltf_data* data, std::vector& nodes, const std::vector& meshes, const std::vector& animations, const Settings& settings); void remapNodes(cgltf_data* data, std::vector& nodes, size_t& node_offset); void decomposeTransform(float translation[3], float rotation[4], float scale[3], const float* transform); void computeMeshQuality(std::vector& meshes); bool hasVertexAlpha(const Mesh& mesh); bool hasInstanceAlpha(const std::vector& instances); QuantizationPosition prepareQuantizationPosition(const std::vector& meshes, const Settings& settings); void prepareQuantizationTexture(cgltf_data* data, std::vector& result, std::vector& indices, const std::vector& meshes, const Settings& settings); void getPositionBounds(float min[3], float max[3], const Stream& stream, const QuantizationPosition& qp, const Settings& settings); StreamFormat writeVertexStream(std::string& bin, const Stream& stream, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings, bool filters = true); StreamFormat writeIndexStream(std::string& bin, const std::vector& stream); StreamFormat writeTimeStream(std::string& bin, const std::vector& data); StreamFormat writeKeyframeStream(std::string& bin, cgltf_animation_path_type type, const std::vector& data, const Settings& settings, bool has_tangents = false); void compressVertexStream(std::string& bin, const std::string& data, size_t count, size_t stride, int level); void compressIndexStream(std::string& bin, const std::string& data, size_t count, size_t stride); void compressIndexSequence(std::string& bin, const std::string& data, size_t count, size_t stride); size_t getBufferView(std::vector& views, BufferView::Kind kind, StreamFormat::Filter filter, BufferView::Compression compression, size_t stride, int variant = 0); void comma(std::string& s); void append(std::string& s, size_t v); void append(std::string& s, float v); void append(std::string& s, const char* v); void append(std::string& s, const std::string& v); void append(std::string& s, const float* data, size_t count); void appendJson(std::string& s, const char* data); const char* attributeType(cgltf_attribute_type type); const char* animationPath(cgltf_animation_path_type type); void writeMaterial(std::string& json, const cgltf_data* data, const cgltf_material& material, const QuantizationPosition* qp, const QuantizationTexture* qt, std::vector& textures); void writeBufferView(std::string& json, BufferView::Kind kind, StreamFormat::Filter filter, size_t count, size_t stride, size_t bin_offset, size_t bin_size, BufferView::Compression compression, size_t compressed_offset, size_t compressed_size, const char* meshopt_ext); void writeSampler(std::string& json, const cgltf_sampler& sampler); void writeImage(std::string& json, std::vector& views, const cgltf_image& image, const ImageInfo& info, const std::string* encoded, size_t index, const char* input_path, const char* output_path, const Settings& settings); void writeTexture(std::string& json, const cgltf_texture& texture, const ImageInfo* info, cgltf_data* data, const Settings& settings); void writeMeshAttributes(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, int target, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings); size_t writeMeshIndices(std::vector& views, std::string& json_accessors, size_t& accr_offset, const std::vector& indices, cgltf_primitive_type type, const Settings& settings); void writeMeshGeometry(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings); size_t writeJointBindMatrices(std::vector& views, std::string& json_accessors, size_t& accr_offset, const cgltf_skin& skin, const QuantizationPosition& qp, const Settings& settings); size_t writeInstances(std::vector& views, std::string& json_accessors, size_t& accr_offset, const std::vector& instances, const QuantizationPosition& qp, bool has_color, const Settings& settings); void writeMeshNode(std::string& json, size_t mesh_offset, cgltf_node* node, cgltf_skin* skin, cgltf_data* data, const QuantizationPosition* qp); void writeMeshNodeInstanced(std::string& json, size_t mesh_offset, size_t accr_offset, bool has_color); void writeSkin(std::string& json, const cgltf_skin& skin, size_t matrix_accr, const std::vector& nodes, cgltf_data* data); void writeNode(std::string& json, const cgltf_node& node, const std::vector& nodes, cgltf_data* data); void writeAnimation(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Animation& animation, size_t i, cgltf_data* data, const std::vector& nodes, const Settings& settings); void writeCamera(std::string& json, const cgltf_camera& camera); void writeLight(std::string& json, const cgltf_light& light); void writeArray(std::string& json, const char* name, const std::string& contents); void writeExtensions(std::string& json, const ExtensionInfo* extensions, size_t count); void writeExtras(std::string& json, const cgltf_extras& extras); void writeScene(std::string& json, const cgltf_scene& scene, const std::string& roots, const Settings& settings); /** * Copyright (c) 2016-2026 Arseny Kapoulkine * * 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. */ ================================================ FILE: gltf/gltfpack.manifest ================================================ UTF-8 ================================================ FILE: gltf/image.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include #include static const char* kMimeTypes[][2] = { {"image/jpeg", ".jpg"}, {"image/jpeg", ".jpeg"}, {"image/png", ".png"}, {"image/ktx2", ".ktx2"}, {"image/webp", ".webp"}, }; static const char* inferMimeType(const char* path) { std::string ext = getExtension(path); for (size_t i = 0; i < sizeof(kMimeTypes) / sizeof(kMimeTypes[0]); ++i) if (ext == kMimeTypes[i][1]) return kMimeTypes[i][0]; return ""; } static bool parseDataUri(const char* uri, std::string& mime_type, std::string& result) { if (strncmp(uri, "data:", 5) == 0) { const char* comma = strchr(uri, ','); if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0) { const char* base64 = comma + 1; size_t base64_size = strlen(base64); size_t size = base64_size - base64_size / 4; if (base64_size >= 2) { size -= base64[base64_size - 2] == '='; size -= base64[base64_size - 1] == '='; } void* data = NULL; cgltf_options options = {}; cgltf_result res = cgltf_load_buffer_base64(&options, size, base64, &data); if (res != cgltf_result_success) return false; mime_type = std::string(uri + 5, comma - 7); result = std::string(static_cast(data), size); free(data); return true; } } return false; } static std::string fixupMimeType(const std::string& data, const std::string& mime_type) { if (mime_type == "image/jpeg" && data.compare(0, 8, "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a") == 0) return "image/png"; if (mime_type == "image/png" && data.compare(0, 3, "\xff\xd8\xff") == 0) return "image/jpeg"; return mime_type; } bool readImage(const cgltf_image& image, const char* input_path, std::string& data, std::string& mime_type) { if (image.uri && parseDataUri(image.uri, mime_type, data)) { mime_type = fixupMimeType(data, mime_type); return true; } else if (image.buffer_view && image.buffer_view->buffer->data && image.mime_type) { const cgltf_buffer_view* view = image.buffer_view; data.assign(static_cast(view->buffer->data) + view->offset, view->size); mime_type = image.mime_type; mime_type = fixupMimeType(data, image.mime_type); return true; } else if (image.uri && *image.uri && input_path) { std::string path = image.uri; cgltf_decode_uri(&path[0]); path.resize(strlen(&path[0])); if (!readFile(getFullPath(path.c_str(), input_path).c_str(), data)) return false; mime_type = image.mime_type ? image.mime_type : inferMimeType(path.c_str()); mime_type = fixupMimeType(data, mime_type); return true; } else { return false; } } static int readInt16BE(const std::string& data, size_t offset) { return (unsigned char)data[offset] * 256 + (unsigned char)data[offset + 1]; } static int readInt32BE(const std::string& data, size_t offset) { return (unsigned((unsigned char)data[offset]) << 24) | (unsigned((unsigned char)data[offset + 1]) << 16) | (unsigned((unsigned char)data[offset + 2]) << 8) | unsigned((unsigned char)data[offset + 3]); } static int readInt32LE(const std::string& data, size_t offset) { return unsigned((unsigned char)data[offset]) | (unsigned((unsigned char)data[offset + 1]) << 8) | (unsigned((unsigned char)data[offset + 2]) << 16) | (unsigned((unsigned char)data[offset + 3]) << 24); } // https://en.wikipedia.org/wiki/PNG#File_format static bool getDimensionsPng(const std::string& data, int& width, int& height) { if (data.size() < 8 + 8 + 13 + 4) return false; const char* signature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"; if (data.compare(0, 8, signature) != 0) return false; if (data.compare(12, 4, "IHDR") != 0) return false; width = readInt32BE(data, 16); height = readInt32BE(data, 20); return true; } // https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#File_format_structure static bool getDimensionsJpeg(const std::string& data, int& width, int& height) { size_t offset = 0; // note, this can stop parsing before reaching the end but we stop at SOF anyway while (offset + 4 <= data.size()) { if (data[offset] != '\xff') return false; char marker = data[offset + 1]; if (marker == '\xff') { offset++; continue; // padding } // d0..d9 correspond to SOI, RSTn, EOI if (marker == 0 || unsigned(marker - '\xd0') <= 9) { offset += 2; continue; // no payload } // c0..c1 correspond to SOF0, SOF1 if (marker == '\xc0' || marker == '\xc2') { if (offset + 10 > data.size()) return false; width = readInt16BE(data, offset + 7); height = readInt16BE(data, offset + 5); return true; } offset += 2 + readInt16BE(data, offset + 2); } return false; } // https://en.wikipedia.org/wiki/PNG#File_format static bool hasTransparencyPng(const std::string& data) { if (data.size() < 8 + 8 + 13 + 4) return false; const char* signature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"; if (data.compare(0, 8, signature) != 0) return false; if (data.compare(12, 4, "IHDR") != 0) return false; int ctype = data[25]; if (ctype != 3) return ctype == 4 || ctype == 6; size_t offset = 8; // reparse IHDR chunk for simplicity while (offset + 12 <= data.size()) { int length = readInt32BE(data, offset); if (length < 0) return false; if (data.compare(offset + 4, 4, "tRNS") == 0) return true; offset += 12 + length; } return false; } // https://github.khronos.org/KTX-Specification/ktxspec.v2.html static bool hasTransparencyKtx2(const std::string& data) { if (data.size() < 12 + 17 * 4) return false; const char* signature = "\xabKTX 20\xbb\r\n\x1a\n"; if (data.compare(0, 12, signature) != 0) return false; int dfdOffset = readInt32LE(data, 48); int dfdLength = readInt32LE(data, 52); if (dfdLength < 4 + 24 + 16 || unsigned(dfdOffset) > data.size() || unsigned(dfdLength) > data.size() - dfdOffset) return false; const int KDF_DF_MODEL_ETC1S = 163; const int KDF_DF_MODEL_UASTC = 166; const int KHR_DF_CHANNEL_UASTC_RGBA = 3; const int KHR_DF_CHANNEL_ETC1S_RGB = 0; const int KHR_DF_CHANNEL_ETC1S_AAA = 15; int colorModel = readInt32LE(data, dfdOffset + 12) & 0xff; int channelType1 = (readInt32LE(data, dfdOffset + 28) >> 24) & 0xf; int channelType2 = dfdLength >= 4 + 24 + 16 * 2 ? (readInt32LE(data, dfdOffset + 44) >> 24) & 0xf : channelType1; if (colorModel == KDF_DF_MODEL_ETC1S) { return channelType1 == KHR_DF_CHANNEL_ETC1S_RGB && channelType2 == KHR_DF_CHANNEL_ETC1S_AAA; } else if (colorModel == KDF_DF_MODEL_UASTC) { return channelType1 == KHR_DF_CHANNEL_UASTC_RGBA; } return false; } // https://developers.google.com/speed/webp/docs/riff_container static bool hasTransparencyWebP(const std::string& data) { if (data.size() < 12 + 4 + 12) return false; if (data.compare(0, 4, "RIFF") != 0) return false; if (data.compare(8, 4, "WEBP") != 0) return false; // WebP data may use VP8L, VP8X or VP8 format, but VP8 does not support transparency if (data.compare(12, 4, "VP8L") == 0) { if (unsigned(data[20]) != 0x2f) return false; // width (14) | height (14) | alpha_is_used (1) | version_number(3) unsigned int header = readInt32LE(data, 21); return (header & (1 << 28)) != 0; } else if (data.compare(12, 4, "VP8X") == 0) { // zero (2) | icc (1) | alpha (1) | exif (1) | xmp (1) | animation (1) | zero (1) unsigned char header = data[20]; return (header & (1 << 4)) != 0; } return false; } bool hasAlpha(const std::string& data, const char* mime_type) { if (strcmp(mime_type, "image/png") == 0) return hasTransparencyPng(data); else if (strcmp(mime_type, "image/ktx2") == 0) return hasTransparencyKtx2(data); else if (strcmp(mime_type, "image/webp") == 0) return hasTransparencyWebP(data); else return false; } bool getDimensions(const std::string& data, const char* mime_type, int& width, int& height) { if (strcmp(mime_type, "image/png") == 0) return getDimensionsPng(data, width, height); if (strcmp(mime_type, "image/jpeg") == 0) return getDimensionsJpeg(data, width, height); return false; } static int roundPow2(int value) { int result = 1; while (result < value) result <<= 1; // to prevent odd texture sizes from increasing the size too much, we round to nearest power of 2 above a certain size if (value > 128 && result * 3 / 4 > value) result >>= 1; return result; } static int roundBlock(int value, bool pow2) { if (value == 0) return 4; if (pow2 && value > 4) return roundPow2(value); return (value + 3) & ~3; } void adjustDimensions(int& width, int& height, float scale, int limit, bool pow2) { width = int(width * scale); height = int(height * scale); if (limit && (width > limit || height > limit)) { float limit_scale = float(limit) / float(width > height ? width : height); width = int(width * limit_scale); height = int(height * limit_scale); } width = roundBlock(width, pow2); height = roundBlock(height, pow2); } const char* mimeExtension(const char* mime_type) { for (size_t i = 0; i < sizeof(kMimeTypes) / sizeof(kMimeTypes[0]); ++i) if (strcmp(kMimeTypes[i][0], mime_type) == 0) return kMimeTypes[i][1]; return ".raw"; } ================================================ FILE: gltf/json.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include #include #include void comma(std::string& s) { char ch = s.empty() ? 0 : s[s.size() - 1]; if (ch != 0 && ch != '[' && ch != '{') s += ","; } void append(std::string& s, size_t v) { char buf[32]; snprintf(buf, sizeof(buf), "%zu", v); s += buf; } void append(std::string& s, float v) { // sanitize +-inf to +-FLT_MAX and NaN to FLT_MAX // it would be more consistent to use null for NaN but that makes JSON invalid, and 0 makes it hard to distinguish from valid values float sv = fabsf(v) < FLT_MAX ? v : (v < 0 ? -FLT_MAX : FLT_MAX); char buf[64]; snprintf(buf, sizeof(buf), "%.9g", sv); s += buf; } void append(std::string& s, const char* v) { s += v; } void append(std::string& s, const std::string& v) { s += v; } void append(std::string& s, const float* data, size_t count) { s += '['; for (size_t i = 0; i < count; ++i) { if (i != 0) s += ','; append(s, data[i]); } s += ']'; } void appendJson(std::string& s, const char* data) { enum State { None, Escape, Quoted } state = None; for (const char* it = data; *it; ++it) { char ch = *it; // whitespace outside of quoted strings can be ignored if (state != None || !isspace(ch)) s += ch; // the finite automata tracks whether we're inside a quoted string switch (state) { case None: state = (ch == '"') ? Quoted : None; break; case Quoted: state = (ch == '"') ? None : (ch == '\\' ? Escape : Quoted); break; case Escape: state = Quoted; break; default: assert(!"Unexpected parsing state"); } } } ================================================ FILE: gltf/library.js ================================================ // This file is part of gltfpack and is distributed under the terms of MIT License. /** * Initialize the library with the Wasm module (library.wasm) * * @param wasm Promise with contents of library.wasm * * Note: this is called automatically in node.js */ function init(wasm) { if (ready) { throw new Error('init must be called once'); } ready = Promise.resolve(wasm) .then(function (buffer) { return WebAssembly.instantiate(buffer, { wasi_snapshot_preview1: wasi }); }) .then(function (result) { instance = result.instance; instance.exports.__wasm_call_ctors(); }); } /** * Pack the requested glTF data using the requested command line and access interface. * * @param args An array of strings with the input arguments; the paths for input and output files are interpreted by the interface * @param iface An interface to the system that will be used to service file requests and other system calls * @return Promise that indicates completion of the operation * * iface should contain the following methods: * read(path): Given a path, return a Uint8Array with the contents of that path * write(path, data): Write the specified Uint8Array to the provided path */ function pack(args, iface) { if (!ready) { throw new Error('init must be called before pack'); } var argv = args.slice(); argv.unshift('gltfpack'); return ready.then(function () { var buf = uploadArgv(argv); output.position = 0; output.size = 0; fs_interface = iface; var result = instance.exports.pack(argv.length, buf); fs_interface = undefined; instance.exports.free(buf); var log = getString(output.data.buffer, 0, output.size); if (result != 0) { throw new Error(log); } else { return log; } }); } // Library implementation (here be dragons) var WASI_EBADF = 8; var WASI_EINVAL = 28; var WASI_EIO = 29; var WASI_ENOSYS = 52; var ready; var instance; var fs_interface; var output = { data: new Uint8Array(), position: 0, size: 0 }; var fds = { 1: output, 2: output, 3: { mount: '/', path: '/' }, 4: { mount: '/gltfpack-$pwd', path: '' } }; var wasi = { proc_exit: function (rval) {}, fd_close: function (fd) { if (!fds[fd]) { return WASI_EBADF; } try { if (fds[fd].close) { fds[fd].close(); } fds[fd] = undefined; return 0; } catch (err) { fds[fd] = undefined; return WASI_EIO; } }, fd_fdstat_get: function (fd, stat) { if (!fds[fd]) { return WASI_EBADF; } var heap = getHeap(); heap.setUint8(stat + 0, fds[fd].path !== undefined ? 3 : 4); heap.setUint16(stat + 2, 0, true); heap.setUint32(stat + 8, 0, true); heap.setUint32(stat + 12, 0, true); heap.setUint32(stat + 16, 0, true); heap.setUint32(stat + 20, 0, true); return 0; }, path_open32: function (parent_fd, dirflags, path, path_len, oflags, fs_rights_base, fs_rights_inheriting, fdflags, opened_fd) { if (!fds[parent_fd] || fds[parent_fd].path === undefined) { return WASI_EBADF; } var heap = getHeap(); var file = {}; file.name = fds[parent_fd].path + getString(heap.buffer, path, path_len); file.position = 0; if (oflags & 1) { file.data = new Uint8Array(4096); file.size = 0; file.close = function () { fs_interface.write(file.name, new Uint8Array(file.data.buffer, 0, file.size)); }; } else { try { file.data = fs_interface.read(file.name); if (!file.data) { return WASI_EIO; } file.size = file.data.length; } catch (err) { return WASI_EIO; } } var fd = nextFd(); fds[fd] = file; heap.setUint32(opened_fd, fd, true); return 0; }, path_filestat_get: function (parent_fd, flags, path, path_len, buf) { if (!fds[parent_fd] || fds[parent_fd].path === undefined) { return WASI_EBADF; } var heap = getHeap(); var name = getString(heap.buffer, path, path_len); var heap = getHeap(); for (var i = 0; i < 64; ++i) heap.setUint8(buf + i, 0); heap.setUint8(buf + 16, name == '.' ? 3 : 4); return 0; }, fd_prestat_get: function (fd, buf) { if (!fds[fd] || fds[fd].path === undefined) { return WASI_EBADF; } var path_buf = stringBuffer(fds[fd].mount); var heap = getHeap(); heap.setUint8(buf, 0); heap.setUint32(buf + 4, path_buf.length, true); return 0; }, fd_prestat_dir_name: function (fd, path, path_len) { if (!fds[fd] || fds[fd].path === undefined) { return WASI_EBADF; } var path_buf = stringBuffer(fds[fd].mount); if (path_len != path_buf.length) { return WASI_EINVAL; } var heap = getHeap(); new Uint8Array(heap.buffer).set(path_buf, path); return 0; }, path_remove_directory: function (parent_fd, path, path_len) { return WASI_EINVAL; }, fd_fdstat_set_flags: function (fd, flags) { return WASI_ENOSYS; }, fd_seek32: function (fd, offset, whence, newoffset) { if (!fds[fd]) { return WASI_EBADF; } var newposition; switch (whence) { case 0: newposition = offset; break; case 1: newposition = fds[fd].position + offset; break; case 2: newposition = fds[fd].size; break; default: return WASI_EINVAL; } if (newposition > fds[fd].size) { return WASI_EINVAL; } fds[fd].position = newposition; var heap = getHeap(); heap.setUint32(newoffset, newposition, true); return 0; }, fd_read: function (fd, iovs, iovs_len, nread) { if (!fds[fd]) { return WASI_EBADF; } var heap = getHeap(); var read = 0; for (var i = 0; i < iovs_len; ++i) { var buf = heap.getUint32(iovs + 8 * i + 0, true); var buf_len = heap.getUint32(iovs + 8 * i + 4, true); var readi = Math.min(fds[fd].size - fds[fd].position, buf_len); new Uint8Array(heap.buffer).set(fds[fd].data.subarray(fds[fd].position, fds[fd].position + readi), buf); fds[fd].position += readi; read += readi; } heap.setUint32(nread, read, true); return 0; }, fd_write: function (fd, iovs, iovs_len, nwritten) { if (!fds[fd]) { return WASI_EBADF; } var heap = getHeap(); var written = 0; for (var i = 0; i < iovs_len; ++i) { var buf = heap.getUint32(iovs + 8 * i + 0, true); var buf_len = heap.getUint32(iovs + 8 * i + 4, true); if (fds[fd].position + buf_len > fds[fd].data.length) { fds[fd].data = growArray(fds[fd].data, fds[fd].position + buf_len); } fds[fd].data.set(new Uint8Array(heap.buffer, buf, buf_len), fds[fd].position); fds[fd].position += buf_len; fds[fd].size = Math.max(fds[fd].position, fds[fd].size); written += buf_len; } heap.setUint32(nwritten, written, true); return 0; }, }; function nextFd() { for (var i = 1; ; ++i) { if (fds[i] === undefined) { return i; } } } function getHeap() { return new DataView(instance.exports.memory.buffer); } function getString(buffer, offset, length) { return new TextDecoder().decode(new Uint8Array(buffer, offset, length)); } function stringBuffer(string) { return new TextEncoder().encode(string); } function growArray(data, len) { var new_length = Math.max(1, data.length); while (new_length < len) { new_length *= 2; } var new_data = new Uint8Array(new_length); new_data.set(data); return new_data; } function uploadArgv(argv) { var buf_size = argv.length * 4; for (var i = 0; i < argv.length; ++i) { buf_size += stringBuffer(argv[i]).length + 1; } var buf = instance.exports.malloc(buf_size); var argp = buf + argv.length * 4; var heap = getHeap(); for (var i = 0; i < argv.length; ++i) { var item = stringBuffer(argv[i]); heap.setUint32(buf + i * 4, argp, true); new Uint8Array(heap.buffer).set(item, argp); heap.setUint8(argp + item.length, 0); argp += item.length + 1; } return buf; } // Automatic initialization for node.js if (typeof process !== 'undefined' && process.release && process.release.name === 'node') { init( import('node:fs').then(function (fs) { return fs.readFileSync(new URL('./library.wasm', import.meta.url)); }) ); } export { init, pack }; ================================================ FILE: gltf/material.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include static bool areTexturesEqual(const cgltf_texture& lhs, const cgltf_texture& rhs) { if (lhs.image != rhs.image) return false; if (lhs.sampler != rhs.sampler) return false; if (lhs.basisu_image != rhs.basisu_image) return false; if (lhs.webp_image != rhs.webp_image) return false; return true; } static bool areTextureViewsEqual(const cgltf_texture_view& lhs, const cgltf_texture_view& rhs) { if (lhs.has_transform != rhs.has_transform) return false; if (lhs.has_transform) { const cgltf_texture_transform& lt = lhs.transform; const cgltf_texture_transform& rt = rhs.transform; if (memcmp(lt.offset, rt.offset, sizeof(cgltf_float) * 2) != 0) return false; if (lt.rotation != rt.rotation) return false; if (memcmp(lt.scale, rt.scale, sizeof(cgltf_float) * 2) != 0) return false; if (lt.texcoord != rt.texcoord) return false; } if (lhs.texture != rhs.texture && (!lhs.texture || !rhs.texture || !areTexturesEqual(*lhs.texture, *rhs.texture))) return false; if (lhs.texcoord != rhs.texcoord) return false; if (lhs.scale != rhs.scale) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_pbr_metallic_roughness& lhs, const cgltf_pbr_metallic_roughness& rhs) { if (!areTextureViewsEqual(lhs.base_color_texture, rhs.base_color_texture)) return false; if (!areTextureViewsEqual(lhs.metallic_roughness_texture, rhs.metallic_roughness_texture)) return false; if (memcmp(lhs.base_color_factor, rhs.base_color_factor, sizeof(cgltf_float) * 4) != 0) return false; if (lhs.metallic_factor != rhs.metallic_factor) return false; if (lhs.roughness_factor != rhs.roughness_factor) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_pbr_specular_glossiness& lhs, const cgltf_pbr_specular_glossiness& rhs) { if (!areTextureViewsEqual(lhs.diffuse_texture, rhs.diffuse_texture)) return false; if (!areTextureViewsEqual(lhs.specular_glossiness_texture, rhs.specular_glossiness_texture)) return false; if (memcmp(lhs.diffuse_factor, rhs.diffuse_factor, sizeof(cgltf_float) * 4) != 0) return false; if (memcmp(lhs.specular_factor, rhs.specular_factor, sizeof(cgltf_float) * 3) != 0) return false; if (lhs.glossiness_factor != rhs.glossiness_factor) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_clearcoat& lhs, const cgltf_clearcoat& rhs) { if (!areTextureViewsEqual(lhs.clearcoat_texture, rhs.clearcoat_texture)) return false; if (!areTextureViewsEqual(lhs.clearcoat_roughness_texture, rhs.clearcoat_roughness_texture)) return false; if (!areTextureViewsEqual(lhs.clearcoat_normal_texture, rhs.clearcoat_normal_texture)) return false; if (lhs.clearcoat_factor != rhs.clearcoat_factor) return false; if (lhs.clearcoat_roughness_factor != rhs.clearcoat_roughness_factor) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_transmission& lhs, const cgltf_transmission& rhs) { if (!areTextureViewsEqual(lhs.transmission_texture, rhs.transmission_texture)) return false; if (lhs.transmission_factor != rhs.transmission_factor) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_ior& lhs, const cgltf_ior& rhs) { if (lhs.ior != rhs.ior) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_specular& lhs, const cgltf_specular& rhs) { if (!areTextureViewsEqual(lhs.specular_texture, rhs.specular_texture)) return false; if (!areTextureViewsEqual(lhs.specular_color_texture, rhs.specular_color_texture)) return false; if (lhs.specular_factor != rhs.specular_factor) return false; if (memcmp(lhs.specular_color_factor, rhs.specular_color_factor, sizeof(cgltf_float) * 3) != 0) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_sheen& lhs, const cgltf_sheen& rhs) { if (!areTextureViewsEqual(lhs.sheen_color_texture, rhs.sheen_color_texture)) return false; if (memcmp(lhs.sheen_color_factor, rhs.sheen_color_factor, sizeof(cgltf_float) * 3) != 0) return false; if (!areTextureViewsEqual(lhs.sheen_roughness_texture, rhs.sheen_roughness_texture)) return false; if (lhs.sheen_roughness_factor != rhs.sheen_roughness_factor) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_volume& lhs, const cgltf_volume& rhs) { if (!areTextureViewsEqual(lhs.thickness_texture, rhs.thickness_texture)) return false; if (lhs.thickness_factor != rhs.thickness_factor) return false; if (memcmp(lhs.attenuation_color, rhs.attenuation_color, sizeof(cgltf_float) * 3) != 0) return false; if (lhs.attenuation_distance != rhs.attenuation_distance) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_emissive_strength& lhs, const cgltf_emissive_strength& rhs) { if (lhs.emissive_strength != rhs.emissive_strength) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_iridescence& lhs, const cgltf_iridescence& rhs) { if (lhs.iridescence_factor != rhs.iridescence_factor) return false; if (!areTextureViewsEqual(lhs.iridescence_texture, rhs.iridescence_texture)) return false; if (lhs.iridescence_ior != rhs.iridescence_ior) return false; if (lhs.iridescence_thickness_min != rhs.iridescence_thickness_min) return false; if (lhs.iridescence_thickness_max != rhs.iridescence_thickness_max) return false; if (!areTextureViewsEqual(lhs.iridescence_thickness_texture, rhs.iridescence_thickness_texture)) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_anisotropy& lhs, const cgltf_anisotropy& rhs) { if (lhs.anisotropy_strength != rhs.anisotropy_strength) return false; if (lhs.anisotropy_rotation != rhs.anisotropy_rotation) return false; if (!areTextureViewsEqual(lhs.anisotropy_texture, rhs.anisotropy_texture)) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_dispersion& lhs, const cgltf_dispersion& rhs) { if (lhs.dispersion != rhs.dispersion) return false; return true; } static bool areMaterialComponentsEqual(const cgltf_diffuse_transmission& lhs, const cgltf_diffuse_transmission& rhs) { if (lhs.diffuse_transmission_factor != rhs.diffuse_transmission_factor) return false; if (memcmp(lhs.diffuse_transmission_color_factor, rhs.diffuse_transmission_color_factor, sizeof(cgltf_float) * 3) != 0) return false; if (!areTextureViewsEqual(lhs.diffuse_transmission_texture, rhs.diffuse_transmission_texture)) return false; if (!areTextureViewsEqual(lhs.diffuse_transmission_color_texture, rhs.diffuse_transmission_color_texture)) return false; return true; } static bool areMaterialsEqual(const cgltf_material& lhs, const cgltf_material& rhs, const Settings& settings) { if (lhs.has_pbr_metallic_roughness != rhs.has_pbr_metallic_roughness) return false; if (lhs.has_pbr_metallic_roughness && !areMaterialComponentsEqual(lhs.pbr_metallic_roughness, rhs.pbr_metallic_roughness)) return false; if (lhs.has_pbr_specular_glossiness != rhs.has_pbr_specular_glossiness) return false; if (lhs.has_pbr_specular_glossiness && !areMaterialComponentsEqual(lhs.pbr_specular_glossiness, rhs.pbr_specular_glossiness)) return false; if (lhs.has_clearcoat != rhs.has_clearcoat) return false; if (lhs.has_clearcoat && !areMaterialComponentsEqual(lhs.clearcoat, rhs.clearcoat)) return false; if (lhs.has_transmission != rhs.has_transmission) return false; if (lhs.has_transmission && !areMaterialComponentsEqual(lhs.transmission, rhs.transmission)) return false; if (lhs.has_ior != rhs.has_ior) return false; if (lhs.has_ior && !areMaterialComponentsEqual(lhs.ior, rhs.ior)) return false; if (lhs.has_specular != rhs.has_specular) return false; if (lhs.has_specular && !areMaterialComponentsEqual(lhs.specular, rhs.specular)) return false; if (lhs.has_sheen != rhs.has_sheen) return false; if (lhs.has_sheen && !areMaterialComponentsEqual(lhs.sheen, rhs.sheen)) return false; if (lhs.has_volume != rhs.has_volume) return false; if (lhs.has_volume && !areMaterialComponentsEqual(lhs.volume, rhs.volume)) return false; if (lhs.has_emissive_strength != rhs.has_emissive_strength) return false; if (lhs.has_emissive_strength && !areMaterialComponentsEqual(lhs.emissive_strength, rhs.emissive_strength)) return false; if (lhs.has_iridescence != rhs.has_iridescence) return false; if (lhs.has_iridescence && !areMaterialComponentsEqual(lhs.iridescence, rhs.iridescence)) return false; if (lhs.has_anisotropy != rhs.has_anisotropy) return false; if (lhs.has_anisotropy && !areMaterialComponentsEqual(lhs.anisotropy, rhs.anisotropy)) return false; if (lhs.has_dispersion != rhs.has_dispersion) return false; if (lhs.has_dispersion && !areMaterialComponentsEqual(lhs.dispersion, rhs.dispersion)) return false; if (lhs.has_diffuse_transmission != rhs.has_diffuse_transmission) return false; if (lhs.has_diffuse_transmission && !areMaterialComponentsEqual(lhs.diffuse_transmission, rhs.diffuse_transmission)) return false; if (!areTextureViewsEqual(lhs.normal_texture, rhs.normal_texture)) return false; if (!areTextureViewsEqual(lhs.occlusion_texture, rhs.occlusion_texture)) return false; if (!areTextureViewsEqual(lhs.emissive_texture, rhs.emissive_texture)) return false; if (memcmp(lhs.emissive_factor, rhs.emissive_factor, sizeof(cgltf_float) * 3) != 0) return false; if (lhs.alpha_mode != rhs.alpha_mode) return false; if (lhs.alpha_cutoff != rhs.alpha_cutoff) return false; if (lhs.double_sided != rhs.double_sided) return false; if (lhs.unlit != rhs.unlit) return false; if (settings.keep_extras && !areExtrasEqual(lhs.extras, rhs.extras)) return false; return true; } void mergeMeshMaterials(cgltf_data* data, std::vector& meshes, const Settings& settings) { std::vector material_remap(data->materials_count); for (size_t i = 0; i < data->materials_count; ++i) { material_remap[i] = &data->materials[i]; if (settings.keep_materials && data->materials[i].name && *data->materials[i].name) continue; assert(areMaterialsEqual(data->materials[i], data->materials[i], settings)); for (size_t j = 0; j < i; ++j) { if (settings.keep_materials && data->materials[j].name && *data->materials[j].name) continue; if (areMaterialsEqual(data->materials[i], data->materials[j], settings)) { material_remap[i] = &data->materials[j]; break; } } } for (size_t i = 0; i < meshes.size(); ++i) { Mesh& mesh = meshes[i]; if (mesh.material) mesh.material = material_remap[mesh.material - data->materials]; for (size_t j = 0; j < mesh.variants.size(); ++j) mesh.variants[j].material = material_remap[mesh.variants[j].material - data->materials]; } } void markNeededMaterials(cgltf_data* data, std::vector& materials, const std::vector& meshes, const Settings& settings) { // mark all used materials as kept for (size_t i = 0; i < meshes.size(); ++i) { const Mesh& mesh = meshes[i]; if (mesh.material) { MaterialInfo& mi = materials[mesh.material - data->materials]; mi.keep = true; } for (size_t j = 0; j < mesh.variants.size(); ++j) { MaterialInfo& mi = materials[mesh.variants[j].material - data->materials]; mi.keep = true; } } // mark all named materials as kept if requested if (settings.keep_materials) { for (size_t i = 0; i < data->materials_count; ++i) { cgltf_material& material = data->materials[i]; if (material.name && *material.name) { materials[i].keep = true; } } } } bool hasValidTransform(const cgltf_texture_view& view) { if (view.has_transform) { if (view.transform.offset[0] != 0.0f || view.transform.offset[1] != 0.0f || view.transform.scale[0] != 1.0f || view.transform.scale[1] != 1.0f || view.transform.rotation != 0.0f) return true; if (view.transform.has_texcoord && view.transform.texcoord != view.texcoord) return true; } return false; } static const cgltf_image* getTextureImage(const cgltf_texture* texture) { if (texture && texture->image) return texture->image; if (texture && texture->basisu_image) return texture->basisu_image; if (texture && texture->webp_image) return texture->webp_image; return NULL; } static void analyzeMaterialTexture(const cgltf_texture_view& view, TextureKind kind, MaterialInfo& mi, cgltf_data* data, std::vector& textures, std::vector& images) { mi.uses_texture_transform |= hasValidTransform(view); if (view.texture) { textures[view.texture - data->textures].keep = true; mi.texture_set_mask |= 1u << view.texcoord; mi.needs_tangents |= (kind == TextureKind_Normal); } if (const cgltf_image* image = getTextureImage(view.texture)) { ImageInfo& info = images[image - data->images]; if (info.kind == TextureKind_Generic) info.kind = kind; else if (info.kind > kind) // this is useful to keep color textures that have attrib data in alpha tagged as color info.kind = kind; info.normal_map |= (kind == TextureKind_Normal); info.srgb |= (kind == TextureKind_Color); } } static void analyzeMaterial(const cgltf_material& material, MaterialInfo& mi, cgltf_data* data, std::vector& textures, std::vector& images) { if (material.has_pbr_metallic_roughness) { analyzeMaterialTexture(material.pbr_metallic_roughness.base_color_texture, TextureKind_Color, mi, data, textures, images); analyzeMaterialTexture(material.pbr_metallic_roughness.metallic_roughness_texture, TextureKind_Attrib, mi, data, textures, images); } if (material.has_pbr_specular_glossiness) { analyzeMaterialTexture(material.pbr_specular_glossiness.diffuse_texture, TextureKind_Color, mi, data, textures, images); analyzeMaterialTexture(material.pbr_specular_glossiness.specular_glossiness_texture, TextureKind_Attrib, mi, data, textures, images); } if (material.has_clearcoat) { analyzeMaterialTexture(material.clearcoat.clearcoat_texture, TextureKind_Attrib, mi, data, textures, images); analyzeMaterialTexture(material.clearcoat.clearcoat_roughness_texture, TextureKind_Attrib, mi, data, textures, images); analyzeMaterialTexture(material.clearcoat.clearcoat_normal_texture, TextureKind_Normal, mi, data, textures, images); } if (material.has_transmission) { analyzeMaterialTexture(material.transmission.transmission_texture, TextureKind_Attrib, mi, data, textures, images); } if (material.has_specular) { analyzeMaterialTexture(material.specular.specular_texture, TextureKind_Attrib, mi, data, textures, images); analyzeMaterialTexture(material.specular.specular_color_texture, TextureKind_Color, mi, data, textures, images); } if (material.has_sheen) { analyzeMaterialTexture(material.sheen.sheen_color_texture, TextureKind_Color, mi, data, textures, images); analyzeMaterialTexture(material.sheen.sheen_roughness_texture, TextureKind_Attrib, mi, data, textures, images); } if (material.has_volume) { analyzeMaterialTexture(material.volume.thickness_texture, TextureKind_Attrib, mi, data, textures, images); } if (material.has_iridescence) { analyzeMaterialTexture(material.iridescence.iridescence_texture, TextureKind_Attrib, mi, data, textures, images); analyzeMaterialTexture(material.iridescence.iridescence_thickness_texture, TextureKind_Attrib, mi, data, textures, images); } if (material.has_anisotropy) { analyzeMaterialTexture(material.anisotropy.anisotropy_texture, TextureKind_Normal, mi, data, textures, images); } if (material.has_diffuse_transmission) { analyzeMaterialTexture(material.diffuse_transmission.diffuse_transmission_texture, TextureKind_Attrib, mi, data, textures, images); analyzeMaterialTexture(material.diffuse_transmission.diffuse_transmission_color_texture, TextureKind_Color, mi, data, textures, images); } analyzeMaterialTexture(material.normal_texture, TextureKind_Normal, mi, data, textures, images); analyzeMaterialTexture(material.occlusion_texture, TextureKind_Attrib, mi, data, textures, images); analyzeMaterialTexture(material.emissive_texture, TextureKind_Color, mi, data, textures, images); if (material.unlit) mi.unlit = true; } void analyzeMaterials(cgltf_data* data, std::vector& materials, std::vector& textures, std::vector& images) { for (size_t i = 0; i < data->materials_count; ++i) { analyzeMaterial(data->materials[i], materials[i], data, textures, images); } } static int getChannels(const cgltf_image& image, ImageInfo& info, const char* input_path) { if (info.channels) return info.channels; std::string img_data; std::string mime_type; if (readImage(image, input_path, img_data, mime_type)) info.channels = hasAlpha(img_data, mime_type.c_str()) ? 4 : 3; else info.channels = -1; return info.channels; } static bool shouldKeepAlpha(const cgltf_texture_view& color, float alpha, cgltf_data* data, const char* input_path, std::vector& images) { if (alpha != 1.f) return true; const cgltf_image* image = getTextureImage(color.texture); return image && getChannels(*image, images[image - data->images], input_path) == 4; } void optimizeMaterials(cgltf_data* data, std::vector& materials, std::vector& images, const char* input_path) { for (size_t i = 0; i < data->materials_count; ++i) { // remove BLEND/MASK from materials that don't have alpha information cgltf_material& material = data->materials[i]; if (material.alpha_mode != cgltf_alpha_mode_opaque && !materials[i].mesh_alpha) { if (material.has_pbr_metallic_roughness && shouldKeepAlpha(material.pbr_metallic_roughness.base_color_texture, material.pbr_metallic_roughness.base_color_factor[3], data, input_path, images)) continue; if (material.has_pbr_specular_glossiness && shouldKeepAlpha(material.pbr_specular_glossiness.diffuse_texture, material.pbr_specular_glossiness.diffuse_factor[3], data, input_path, images)) continue; material.alpha_mode = cgltf_alpha_mode_opaque; material.alpha_cutoff = 0.5f; // reset to default to avoid writing it to output } } } void mergeTextures(cgltf_data* data, std::vector& textures) { size_t offset = 0; for (size_t i = 0; i < textures.size(); ++i) { TextureInfo& info = textures[i]; if (!info.keep) continue; for (size_t j = 0; j < i; ++j) if (textures[j].keep && areTexturesEqual(data->textures[i], data->textures[j])) { info.keep = false; info.remap = textures[j].remap; break; } if (info.keep) { info.remap = int(offset); offset++; } } } ================================================ FILE: gltf/mesh.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include #include #include #include #include #include "../src/meshoptimizer.h" static float inverseTranspose(float* result, const float* transform) { float m[4][4] = {}; memcpy(m, transform, 16 * sizeof(float)); float det = m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]); float invdet = (det == 0.f) ? 0.f : 1.f / det; float r[4][4] = {}; r[0][0] = (m[1][1] * m[2][2] - m[2][1] * m[1][2]) * invdet; r[1][0] = (m[0][2] * m[2][1] - m[0][1] * m[2][2]) * invdet; r[2][0] = (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * invdet; r[0][1] = (m[1][2] * m[2][0] - m[1][0] * m[2][2]) * invdet; r[1][1] = (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * invdet; r[2][1] = (m[1][0] * m[0][2] - m[0][0] * m[1][2]) * invdet; r[0][2] = (m[1][0] * m[2][1] - m[2][0] * m[1][1]) * invdet; r[1][2] = (m[2][0] * m[0][1] - m[0][0] * m[2][1]) * invdet; r[2][2] = (m[0][0] * m[1][1] - m[1][0] * m[0][1]) * invdet; r[3][3] = 1.f; memcpy(result, r, 16 * sizeof(float)); return det; } static void transformPosition(float* res, const float* ptr, const float* transform) { float x = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8] + transform[12]; float y = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9] + transform[13]; float z = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10] + transform[14]; res[0] = x; res[1] = y; res[2] = z; } static void transformNormal(float* res, const float* ptr, const float* transform) { float x = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8]; float y = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9]; float z = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10]; float l = sqrtf(x * x + y * y + z * z); float s = (l == 0.f) ? 0.f : 1 / l; res[0] = x * s; res[1] = y * s; res[2] = z * s; } static Stream* getStream(Mesh& mesh, cgltf_attribute_type type, int index = 0) { for (size_t i = 0; i < mesh.streams.size(); ++i) if (mesh.streams[i].type == type && mesh.streams[i].index == index) return &mesh.streams[i]; return NULL; } // assumes mesh & target are structurally identical static void transformMesh(Mesh& target, const Mesh& mesh, const cgltf_node* node) { assert(target.streams.size() == mesh.streams.size()); assert(target.indices.size() == mesh.indices.size()); float transform[16]; cgltf_node_transform_world(node, transform); float transforminvt[16]; float det = inverseTranspose(transforminvt, transform); for (size_t si = 0; si < mesh.streams.size(); ++si) { const Stream& source = mesh.streams[si]; Stream& stream = target.streams[si]; assert(source.type == stream.type); assert(source.data.size() == stream.data.size()); if (stream.type == cgltf_attribute_type_position) { for (size_t i = 0; i < stream.data.size(); ++i) transformPosition(stream.data[i].f, source.data[i].f, transform); } else if (stream.type == cgltf_attribute_type_normal) { for (size_t i = 0; i < stream.data.size(); ++i) transformNormal(stream.data[i].f, source.data[i].f, transforminvt); } else if (stream.type == cgltf_attribute_type_tangent) { for (size_t i = 0; i < stream.data.size(); ++i) transformNormal(stream.data[i].f, source.data[i].f, transform); } } // copy indices so that we can modify them below target.indices = mesh.indices; if (det < 0 && mesh.type == cgltf_primitive_type_triangles) { // negative scale means we need to flip face winding for (size_t i = 0; i < target.indices.size(); i += 3) std::swap(target.indices[i + 0], target.indices[i + 1]); } } bool compareMeshTargets(const Mesh& lhs, const Mesh& rhs) { if (lhs.targets != rhs.targets) return false; if (lhs.target_weights.size() != rhs.target_weights.size()) return false; for (size_t i = 0; i < lhs.target_weights.size(); ++i) if (lhs.target_weights[i] != rhs.target_weights[i]) return false; if (lhs.target_names.size() != rhs.target_names.size()) return false; for (size_t i = 0; i < lhs.target_names.size(); ++i) if (strcmp(lhs.target_names[i], rhs.target_names[i]) != 0) return false; return true; } bool compareMeshVariants(const Mesh& lhs, const Mesh& rhs) { if (lhs.variants.size() != rhs.variants.size()) return false; for (size_t i = 0; i < lhs.variants.size(); ++i) { if (lhs.variants[i].variant != rhs.variants[i].variant) return false; if (lhs.variants[i].material != rhs.variants[i].material) return false; } return true; } bool compareMeshNodes(const Mesh& lhs, const Mesh& rhs) { if (lhs.nodes.size() != rhs.nodes.size()) return false; for (size_t i = 0; i < lhs.nodes.size(); ++i) if (lhs.nodes[i] != rhs.nodes[i]) return false; return true; } static bool compareInstances(const Instance& lhs, const Instance& rhs) { return memcmp(&lhs, &rhs, sizeof(Instance)) == 0; } static bool canMergeMeshNodes(cgltf_node* lhs, cgltf_node* rhs, const Settings& settings) { if (lhs == rhs) return true; if (lhs->parent != rhs->parent) return false; bool lhs_transform = lhs->has_translation | lhs->has_rotation | lhs->has_scale | lhs->has_matrix | (!!lhs->weights); bool rhs_transform = rhs->has_translation | rhs->has_rotation | rhs->has_scale | rhs->has_matrix | (!!rhs->weights); if (lhs_transform || rhs_transform) return false; if (settings.keep_nodes) { if (lhs->name && *lhs->name) return false; if (rhs->name && *rhs->name) return false; } // we can merge nodes that don't have transforms of their own and have the same parent // this is helpful when instead of splitting mesh into primitives, DCCs split mesh into mesh nodes return true; } static bool canMergeMeshes(const Mesh& lhs, const Mesh& rhs, const Settings& settings) { if (lhs.scene != rhs.scene) return false; if (lhs.nodes.size() != rhs.nodes.size()) return false; for (size_t i = 0; i < lhs.nodes.size(); ++i) if (!canMergeMeshNodes(lhs.nodes[i], rhs.nodes[i], settings)) return false; if (lhs.instances.size() != rhs.instances.size()) return false; for (size_t i = 0; i < lhs.instances.size(); ++i) if (!compareInstances(lhs.instances[i], rhs.instances[i])) return false; if (lhs.material != rhs.material) return false; if (lhs.skin != rhs.skin) return false; if (lhs.type != rhs.type) return false; if (!compareMeshTargets(lhs, rhs)) return false; if (!compareMeshVariants(lhs, rhs)) return false; if (lhs.indices.empty() != rhs.indices.empty()) return false; if (lhs.streams.size() != rhs.streams.size()) return false; if (settings.keep_extras && !areExtrasEqual(lhs.extras, rhs.extras)) return false; for (size_t i = 0; i < lhs.streams.size(); ++i) if (lhs.streams[i].type != rhs.streams[i].type || lhs.streams[i].index != rhs.streams[i].index || lhs.streams[i].target != rhs.streams[i].target) return false; return true; } static void mergeMeshes(Mesh& target, const Mesh& mesh) { assert(target.streams.size() == mesh.streams.size()); size_t vertex_offset = target.streams[0].data.size(); size_t index_offset = target.indices.size(); for (size_t i = 0; i < target.streams.size(); ++i) target.streams[i].data.insert(target.streams[i].data.end(), mesh.streams[i].data.begin(), mesh.streams[i].data.end()); target.indices.resize(target.indices.size() + mesh.indices.size()); size_t index_count = mesh.indices.size(); for (size_t i = 0; i < index_count; ++i) target.indices[index_offset + i] = unsigned(vertex_offset + mesh.indices[i]); } static void hashUpdate(uint64_t hash[2], const void* data, size_t size) { #define ROTL64(x, r) (((x) << (r)) | ((x) >> (64 - (r)))) // MurMurHash3 128-bit const uint64_t c1 = 0x87c37b91114253d5ull; const uint64_t c2 = 0x4cf5ad432745937full; uint64_t h1 = hash[0], h2 = hash[1]; size_t offset = 0; // body for (; offset + 16 <= size; offset += 16) { uint64_t k1, k2; memcpy(&k1, static_cast(data) + offset + 0, 8); memcpy(&k2, static_cast(data) + offset + 8, 8); k1 *= c1, k1 = ROTL64(k1, 31), k1 *= c2; h1 ^= k1, h1 = ROTL64(h1, 27), h1 += h2; h1 = h1 * 5 + 0x52dce729; k2 *= c2, k2 = ROTL64(k2, 33), k2 *= c1; h2 ^= k2, h2 = ROTL64(h2, 31), h2 += h1; h2 = h2 * 5 + 0x38495ab5; } // tail if (offset < size) { uint64_t tail[2] = {}; memcpy(tail, static_cast(data) + offset, size - offset); uint64_t k1 = tail[0], k2 = tail[1]; k1 *= c1, k1 = ROTL64(k1, 31), k1 *= c2; h1 ^= k1; k2 *= c2, k2 = ROTL64(k2, 33), k2 *= c1; h2 ^= k2; } h1 ^= size; h2 ^= size; hash[0] = h1; hash[1] = h2; #undef ROTL64 } void hashMesh(Mesh& mesh) { mesh.geometry_hash[0] = mesh.geometry_hash[1] = 41; for (size_t i = 0; i < mesh.streams.size(); ++i) { const Stream& stream = mesh.streams[i]; int meta[3] = {stream.type, stream.index, stream.target}; hashUpdate(mesh.geometry_hash, meta, sizeof(meta)); if (stream.custom_name) hashUpdate(mesh.geometry_hash, stream.custom_name, strlen(stream.custom_name)); hashUpdate(mesh.geometry_hash, stream.data.data(), stream.data.size() * sizeof(Attr)); } if (!mesh.indices.empty()) hashUpdate(mesh.geometry_hash, mesh.indices.data(), mesh.indices.size() * sizeof(unsigned int)); int meta[4] = {int(mesh.streams.size()), mesh.streams.empty() ? 0 : int(mesh.streams[0].data.size()), int(mesh.indices.size()), mesh.type}; hashUpdate(mesh.geometry_hash, meta, sizeof(meta)); } static bool canDedupMesh(const Mesh& mesh, const Settings& settings) { // empty mesh if (mesh.streams.empty()) return false; // world-space mesh if (mesh.nodes.empty() && mesh.instances.empty()) return false; // has extras if (settings.keep_extras && mesh.extras.data) return false; // to simplify dedup we ignore complex target setups for now if (!mesh.target_weights.empty() || !mesh.target_names.empty() || !mesh.variants.empty()) return false; return true; } void dedupMeshes(std::vector& meshes, const Settings& settings) { std::unordered_map hashes; for (size_t i = 0; i < meshes.size(); ++i) { Mesh& mesh = meshes[i]; hashMesh(mesh); hashes[mesh.geometry_hash[0] ^ mesh.geometry_hash[1]]++; } for (size_t i = 0; i < meshes.size(); ++i) { Mesh& target = meshes[i]; if (!canDedupMesh(target, settings)) continue; if (hashes[target.geometry_hash[0] ^ target.geometry_hash[1]] <= 1) continue; for (size_t j = i + 1; j < meshes.size(); ++j) { Mesh& mesh = meshes[j]; if (mesh.geometry_hash[0] != target.geometry_hash[0] || mesh.geometry_hash[1] != target.geometry_hash[1]) continue; if (!canDedupMesh(mesh, settings)) continue; if (mesh.scene != target.scene || mesh.material != target.material || mesh.skin != target.skin) { // mark both meshes as having duplicate geometry; we don't use this in dedupMeshes but it's useful later in the pipeline target.geometry_duplicate = true; mesh.geometry_duplicate = true; continue; } // basic sanity test; these should be included in geometry hash assert(mesh.streams.size() == target.streams.size()); assert(mesh.streams[0].data.size() == target.streams[0].data.size()); assert(mesh.indices.size() == target.indices.size()); target.nodes.insert(target.nodes.end(), mesh.nodes.begin(), mesh.nodes.end()); target.instances.insert(target.instances.end(), mesh.instances.begin(), mesh.instances.end()); mesh.streams.clear(); mesh.indices.clear(); mesh.nodes.clear(); mesh.instances.clear(); } } for (size_t i = 0; i < meshes.size(); ++i) { Mesh& target = meshes[i]; if (target.nodes.size() <= 1) continue; std::sort(target.nodes.begin(), target.nodes.end()); target.nodes.erase(std::unique(target.nodes.begin(), target.nodes.end()), target.nodes.end()); } } void mergeMeshInstances(Mesh& mesh) { if (mesh.nodes.empty()) return; // fast-path: for single instance meshes we transform in-place if (mesh.nodes.size() == 1) { transformMesh(mesh, mesh, mesh.nodes[0]); mesh.nodes.clear(); return; } Mesh base = mesh; Mesh transformed = base; for (size_t i = 0; i < mesh.streams.size(); ++i) { mesh.streams[i].data.clear(); mesh.streams[i].data.reserve(base.streams[i].data.size() * mesh.nodes.size()); } mesh.indices.clear(); mesh.indices.reserve(base.indices.size() * mesh.nodes.size()); for (size_t i = 0; i < mesh.nodes.size(); ++i) { transformMesh(transformed, base, mesh.nodes[i]); mergeMeshes(mesh, transformed); } mesh.nodes.clear(); } void mergeMeshes(std::vector& meshes, const Settings& settings) { for (size_t i = 0; i < meshes.size(); ++i) { Mesh& target = meshes[i]; if (target.streams.empty()) continue; size_t target_vertices = target.streams[0].data.size(); size_t target_indices = target.indices.size(); size_t last_merged = i; for (size_t j = i + 1; j < meshes.size(); ++j) { Mesh& mesh = meshes[j]; if (!mesh.streams.empty() && canMergeMeshes(target, mesh, settings)) { target_vertices += mesh.streams[0].data.size(); target_indices += mesh.indices.size(); last_merged = j; } } for (size_t j = 0; j < target.streams.size(); ++j) target.streams[j].data.reserve(target_vertices); target.indices.reserve(target_indices); for (size_t j = i + 1; j <= last_merged; ++j) { Mesh& mesh = meshes[j]; if (!mesh.streams.empty() && canMergeMeshes(target, mesh, settings)) { mergeMeshes(target, mesh); mesh.streams.clear(); mesh.indices.clear(); mesh.nodes.clear(); mesh.instances.clear(); } } assert(target.streams[0].data.size() == target_vertices); assert(target.indices.size() == target_indices); } } void filterEmptyMeshes(std::vector& meshes) { size_t write = 0; for (size_t i = 0; i < meshes.size(); ++i) { Mesh& mesh = meshes[i]; if (mesh.streams.empty()) continue; if (mesh.streams[0].data.empty()) continue; if (mesh.type != cgltf_primitive_type_points && mesh.indices.empty()) continue; // the following code is roughly equivalent to meshes[write] = std::move(mesh) std::vector streams; streams.swap(mesh.streams); std::vector indices; indices.swap(mesh.indices); meshes[write] = mesh; meshes[write].streams.swap(streams); meshes[write].indices.swap(indices); write++; } meshes.resize(write); } static bool isConstant(const std::vector& data, const Attr& value, float tolerance = 0.01f) { for (size_t i = 0; i < data.size(); ++i) { const Attr& a = data[i]; if (fabsf(a.f[0] - value.f[0]) > tolerance || fabsf(a.f[1] - value.f[1]) > tolerance || fabsf(a.f[2] - value.f[2]) > tolerance || fabsf(a.f[3] - value.f[3]) > tolerance) return false; } return true; } void filterStreams(Mesh& mesh, const MaterialInfo& mi) { bool morph_normal = false; bool morph_tangent = false; int keep_texture_set = -1; for (size_t i = 0; i < mesh.streams.size(); ++i) { Stream& stream = mesh.streams[i]; if (stream.target) { morph_normal = morph_normal || (stream.type == cgltf_attribute_type_normal && !isConstant(stream.data, {0, 0, 0, 0})); morph_tangent = morph_tangent || (stream.type == cgltf_attribute_type_tangent && !isConstant(stream.data, {0, 0, 0, 0})); } if (stream.type == cgltf_attribute_type_texcoord && stream.index < 32 && (mi.texture_set_mask & (1u << stream.index)) != 0) { keep_texture_set = std::max(keep_texture_set, stream.index); } } size_t write = 0; for (size_t i = 0; i < mesh.streams.size(); ++i) { Stream& stream = mesh.streams[i]; if (stream.type == cgltf_attribute_type_texcoord && stream.index > keep_texture_set) continue; if (stream.type == cgltf_attribute_type_normal && mi.unlit) continue; if (stream.type == cgltf_attribute_type_tangent && !mi.needs_tangents) continue; if ((stream.type == cgltf_attribute_type_joints || stream.type == cgltf_attribute_type_weights) && !mesh.skin) continue; if (stream.type == cgltf_attribute_type_color && isConstant(stream.data, {1, 1, 1, 1})) continue; if (stream.target && stream.type == cgltf_attribute_type_normal && !morph_normal) continue; if (stream.target && stream.type == cgltf_attribute_type_tangent && !morph_tangent) continue; if (mesh.type == cgltf_primitive_type_points && stream.type == cgltf_attribute_type_normal && !stream.data.empty() && isConstant(stream.data, stream.data[0])) continue; // the following code is roughly equivalent to streams[write] = std::move(stream) std::vector data; data.swap(stream.data); mesh.streams[write] = stream; mesh.streams[write].data.swap(data); write++; } mesh.streams.resize(write); } struct QuantizedTBN { int8_t nx, ny, nz, nw; int8_t tx, ty, tz, tw; }; static void quantizeTBN(QuantizedTBN* target, size_t offset, const Attr* source, size_t size, int bits) { int8_t* target8 = reinterpret_cast(target) + offset; for (size_t i = 0; i < size; ++i) { target8[i * sizeof(QuantizedTBN) + 0] = int8_t(meshopt_quantizeSnorm(source[i].f[0], bits)); target8[i * sizeof(QuantizedTBN) + 1] = int8_t(meshopt_quantizeSnorm(source[i].f[1], bits)); target8[i * sizeof(QuantizedTBN) + 2] = int8_t(meshopt_quantizeSnorm(source[i].f[2], bits)); target8[i * sizeof(QuantizedTBN) + 3] = int8_t(meshopt_quantizeSnorm(source[i].f[3], bits)); } } static void reindexMesh(Mesh& mesh, bool quantize_tbn) { size_t total_vertices = mesh.streams[0].data.size(); size_t total_indices = mesh.indices.size(); std::vector qtbn; std::vector streams; for (size_t i = 0; i < mesh.streams.size(); ++i) { const Stream& attr = mesh.streams[i]; if (attr.target) continue; assert(attr.data.size() == total_vertices); if (quantize_tbn && (attr.type == cgltf_attribute_type_normal || attr.type == cgltf_attribute_type_tangent)) { if (qtbn.empty()) { qtbn.resize(total_vertices); meshopt_Stream stream = {&qtbn[0], sizeof(QuantizedTBN), sizeof(QuantizedTBN)}; streams.push_back(stream); } size_t offset = attr.type == cgltf_attribute_type_normal ? offsetof(QuantizedTBN, nx) : offsetof(QuantizedTBN, tx); quantizeTBN(&qtbn[0], offset, &attr.data[0], total_vertices, /* bits= */ 8); } else { meshopt_Stream stream = {&attr.data[0], sizeof(Attr), sizeof(Attr)}; streams.push_back(stream); } } if (streams.empty()) return; std::vector remap(total_vertices); size_t unique_vertices = meshopt_generateVertexRemapMulti(&remap[0], &mesh.indices[0], total_indices, total_vertices, &streams[0], streams.size()); assert(unique_vertices <= total_vertices); meshopt_remapIndexBuffer(&mesh.indices[0], &mesh.indices[0], total_indices, &remap[0]); for (size_t i = 0; i < mesh.streams.size(); ++i) { assert(mesh.streams[i].data.size() == total_vertices); meshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], total_vertices, sizeof(Attr), &remap[0]); mesh.streams[i].data.resize(unique_vertices); } } static void filterTriangles(Mesh& mesh) { assert(mesh.type == cgltf_primitive_type_triangles); unsigned int* indices = &mesh.indices[0]; size_t total_indices = mesh.indices.size(); size_t write = 0; for (size_t i = 0; i < total_indices; i += 3) { unsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2]; if (a != b && a != c && b != c) { indices[write + 0] = a; indices[write + 1] = b; indices[write + 2] = c; write += 3; } } mesh.indices.resize(write); } static void simplifyAttributes(std::vector& attrs, float* attrw, size_t stride, Mesh& mesh) { assert(stride >= 6); // normal + color size_t vertex_count = mesh.streams[0].data.size(); attrs.resize(vertex_count * stride); float* data = attrs.data(); if (const Stream* attr = getStream(mesh, cgltf_attribute_type_normal)) { const Attr* a = attr->data.data(); for (size_t i = 0; i < vertex_count; ++i) { data[i * stride + 0] = a[i].f[0]; data[i * stride + 1] = a[i].f[1]; data[i * stride + 2] = a[i].f[2]; } attrw[0] = attrw[1] = attrw[2] = 0.5f; } if (const Stream* attr = getStream(mesh, cgltf_attribute_type_color)) { const Attr* a = attr->data.data(); for (size_t i = 0; i < vertex_count; ++i) { data[i * stride + 3] = a[i].f[0] * a[i].f[3]; data[i * stride + 4] = a[i].f[1] * a[i].f[3]; data[i * stride + 5] = a[i].f[2] * a[i].f[3]; } attrw[3] = attrw[4] = attrw[5] = 1.0f; } } static void simplifyProtect(std::vector& locks, Mesh& mesh, size_t presplit_vertices) { const Stream* positions = getStream(mesh, cgltf_attribute_type_position); assert(positions); size_t vertex_count = positions->data.size(); locks.resize(vertex_count); unsigned char* data = locks.data(); std::vector remap(vertex_count); meshopt_generatePositionRemap(&remap[0], positions->data[0].f, vertex_count, sizeof(Attr)); // protect UV discontinuities if (Stream* attr = getStream(mesh, cgltf_attribute_type_texcoord)) { Attr* a = attr->data.data(); for (size_t i = 0; i < vertex_count; ++i) { unsigned int r = remap[i]; if (r != i && (a[i].f[0] != a[r].f[0] || a[i].f[1] != a[r].f[1])) data[i] |= meshopt_SimplifyVertex_Protect; } } // protect all vertices that were artificially split on the UV mirror edges for (size_t i = presplit_vertices; i < vertex_count; ++i) data[i] |= meshopt_SimplifyVertex_Protect; } static void simplifyUvSplit(Mesh& mesh, std::vector& remap) { assert(mesh.type == cgltf_primitive_type_triangles); assert(!mesh.indices.empty()); const Stream* uv = getStream(mesh, cgltf_attribute_type_texcoord); if (!uv) return; size_t vertex_count = uv->data.size(); std::vector uvsign(mesh.indices.size() / 3); std::vector flipseam(vertex_count); for (size_t i = 0; i < mesh.indices.size(); i += 3) { unsigned int a = mesh.indices[i + 0]; unsigned int b = mesh.indices[i + 1]; unsigned int c = mesh.indices[i + 2]; const Attr& va = uv->data[a]; const Attr& vb = uv->data[b]; const Attr& vc = uv->data[c]; float uvarea = (vb.f[0] - va.f[0]) * (vc.f[1] - va.f[1]) - (vc.f[0] - va.f[0]) * (vb.f[1] - va.f[1]); unsigned char flag = uvarea > 0 ? 1 : (uvarea < 0 ? 2 : 0); uvsign[i / 3] = flag; flipseam[a] |= flag; flipseam[b] |= flag; flipseam[c] |= flag; } std::vector split(vertex_count); size_t splits = 0; remap.resize(vertex_count); for (size_t i = 0; i < vertex_count; ++i) { remap[i] = unsigned(i); if (flipseam[i] == 3) { assert(remap.size() == vertex_count + splits); remap.push_back(unsigned(i)); split[i] = unsigned(vertex_count + splits); splits++; for (size_t k = 0; k < mesh.streams.size(); ++k) mesh.streams[k].data.push_back(mesh.streams[k].data[i]); } } for (size_t i = 0; i < mesh.indices.size(); i += 3) { unsigned int a = mesh.indices[i + 0]; unsigned int b = mesh.indices[i + 1]; unsigned int c = mesh.indices[i + 2]; unsigned char sign = uvsign[i / 3]; if (flipseam[a] == 3 && sign == 2) mesh.indices[i + 0] = split[a]; if (flipseam[b] == 3 && sign == 2) mesh.indices[i + 1] = split[b]; if (flipseam[c] == 3 && sign == 2) mesh.indices[i + 2] = split[c]; } } static void simplifyMesh(Mesh& mesh, float threshold, float error, bool attributes, bool aggressive, bool lock_borders, bool permissive) { assert(mesh.type == cgltf_primitive_type_triangles); if (mesh.indices.empty()) return; const Stream* positions = getStream(mesh, cgltf_attribute_type_position); if (!positions) return; size_t presplit_vertices = positions->data.size(); std::vector uvremap; if (attributes) simplifyUvSplit(mesh, uvremap); size_t vertex_count = positions->data.size(); size_t target_index_count = size_t(double(size_t(mesh.indices.size() / 3)) * threshold) * 3; float target_error = error; float target_error_aggressive = 1e-1f; unsigned int options = 0; if (lock_borders) options |= meshopt_SimplifyLockBorder; else options |= meshopt_SimplifyPrune; if (permissive) options |= meshopt_SimplifyPermissive; if (mesh.targets || getStream(mesh, cgltf_attribute_type_weights)) options |= meshopt_SimplifyRegularize; std::vector indices(mesh.indices.size()); float attrw[6] = {}; std::vector attrs; if (attributes) simplifyAttributes(attrs, attrw, sizeof(attrw) / sizeof(attrw[0]), mesh); std::vector locks; if (attributes && permissive) simplifyProtect(locks, mesh, presplit_vertices); if (attributes) indices.resize(meshopt_simplifyWithAttributes(&indices[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, vertex_count, sizeof(Attr), attrs.data(), sizeof(attrw), attrw, sizeof(attrw) / sizeof(attrw[0]), permissive ? locks.data() : NULL, target_index_count, target_error, options)); else indices.resize(meshopt_simplify(&indices[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, vertex_count, sizeof(Attr), target_index_count, target_error, options)); mesh.indices.swap(indices); // Note: if the simplifier got stuck, we can try to reindex without normals/tangents and retry // For now we simply fall back to aggressive simplifier instead // if the precise simplifier got "stuck", we'll try to simplify using the sloppy simplifier; this is only used when aggressive simplification is enabled as it breaks attribute discontinuities if (aggressive && mesh.indices.size() > target_index_count) { indices.resize(meshopt_simplifySloppy(&indices[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, vertex_count, sizeof(Attr), target_index_count, target_error_aggressive)); mesh.indices.swap(indices); } if (uvremap.size() && mesh.indices.size()) meshopt_remapIndexBuffer(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), &uvremap[0]); } static void optimizeMesh(Mesh& mesh, bool compressmore) { assert(mesh.type == cgltf_primitive_type_triangles); if (mesh.indices.empty()) return; size_t vertex_count = mesh.streams[0].data.size(); if (compressmore) meshopt_optimizeVertexCacheStrip(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), vertex_count); else meshopt_optimizeVertexCache(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), vertex_count); std::vector remap(vertex_count); size_t unique_vertices = meshopt_optimizeVertexFetchRemap(&remap[0], &mesh.indices[0], mesh.indices.size(), vertex_count); assert(unique_vertices <= vertex_count); meshopt_remapIndexBuffer(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), &remap[0]); for (size_t i = 0; i < mesh.streams.size(); ++i) { assert(mesh.streams[i].data.size() == vertex_count); meshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], vertex_count, sizeof(Attr), &remap[0]); mesh.streams[i].data.resize(unique_vertices); } } struct BoneInfluence { float i; float w; }; struct BoneInfluenceWeightPredicate { bool operator()(const BoneInfluence& lhs, const BoneInfluence& rhs) const { return lhs.w > rhs.w; } }; static void filterBones(Mesh& mesh) { const int kMaxGroups = 8; std::pair groups[kMaxGroups]; int group_count = 0; // gather all joint/weight groups; each group contains 4 bone influences for (int i = 0; i < kMaxGroups; ++i) { Stream* jg = getStream(mesh, cgltf_attribute_type_joints, int(i)); Stream* wg = getStream(mesh, cgltf_attribute_type_weights, int(i)); if (!jg || !wg) break; groups[group_count++] = std::make_pair(jg, wg); } if (group_count == 0) return; // weights below cutoff can't be represented in quantized 8-bit storage const float weight_cutoff = 0.5f / 255.f; size_t vertex_count = mesh.streams[0].data.size(); BoneInfluence inf[kMaxGroups * 4] = {}; for (size_t i = 0; i < vertex_count; ++i) { int count = 0; // gather all bone influences for this vertex for (int j = 0; j < group_count; ++j) { const Attr& ja = groups[j].first->data[i]; const Attr& wa = groups[j].second->data[i]; for (int k = 0; k < 4; ++k) if (wa.f[k] > weight_cutoff) { inf[count].i = ja.f[k]; inf[count].w = wa.f[k]; count++; } } // pick top 4 influences; this also sorts resulting influences by weight which helps renderers that use influence subset in shader LODs std::sort(inf, inf + count, BoneInfluenceWeightPredicate()); // copy the top 4 influences back into stream 0 - we will remove other streams at the end Attr& ja = groups[0].first->data[i]; Attr& wa = groups[0].second->data[i]; for (int k = 0; k < 4; ++k) { if (k < count) { ja.f[k] = inf[k].i; wa.f[k] = inf[k].w; } else { ja.f[k] = 0.f; wa.f[k] = 0.f; } } } // remove redundant weight/joint streams for (size_t i = 0; i < mesh.streams.size();) { Stream& s = mesh.streams[i]; if ((s.type == cgltf_attribute_type_joints || s.type == cgltf_attribute_type_weights) && s.index > 0) mesh.streams.erase(mesh.streams.begin() + i); else ++i; } } static void simplifyPointMesh(Mesh& mesh, float threshold) { assert(mesh.type == cgltf_primitive_type_points); if (threshold >= 1) return; const Stream* positions = getStream(mesh, cgltf_attribute_type_position); if (!positions) return; const Stream* colors = getStream(mesh, cgltf_attribute_type_color); size_t vertex_count = mesh.streams[0].data.size(); size_t target_vertex_count = size_t(double(vertex_count) * threshold); const float color_weight = 1; std::vector indices(target_vertex_count); if (target_vertex_count) indices.resize(meshopt_simplifyPoints(&indices[0], positions->data[0].f, vertex_count, sizeof(Attr), colors ? colors->data[0].f : NULL, sizeof(Attr), color_weight, target_vertex_count)); std::vector scratch(indices.size()); for (size_t i = 0; i < mesh.streams.size(); ++i) { std::vector& data = mesh.streams[i].data; assert(data.size() == vertex_count); for (size_t j = 0; j < indices.size(); ++j) scratch[j] = data[indices[j]]; data = scratch; } } static void sortPointMesh(Mesh& mesh) { assert(mesh.type == cgltf_primitive_type_points); const Stream* positions = getStream(mesh, cgltf_attribute_type_position); if (!positions) return; // skip spatial sort in presence of custom attributes, since when they refer to mesh features their order is more important to preserve for compression efficiency if (getStream(mesh, cgltf_attribute_type_custom)) return; size_t vertex_count = mesh.streams[0].data.size(); std::vector remap(vertex_count); meshopt_spatialSortRemap(&remap[0], positions->data[0].f, vertex_count, sizeof(Attr)); for (size_t i = 0; i < mesh.streams.size(); ++i) { assert(mesh.streams[i].data.size() == vertex_count); meshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], vertex_count, sizeof(Attr), &remap[0]); } } void processMesh(Mesh& mesh, const Settings& settings) { switch (mesh.type) { case cgltf_primitive_type_points: assert(mesh.indices.empty()); simplifyPointMesh(mesh, settings.simplify_ratio); sortPointMesh(mesh); break; case cgltf_primitive_type_lines: break; case cgltf_primitive_type_triangles: filterBones(mesh); reindexMesh(mesh, settings.quantize && !settings.nrm_float); filterTriangles(mesh); if (settings.simplify_ratio < 1) { float error = settings.simplify_scaled ? settings.simplify_error / mesh.quality : settings.simplify_error; simplifyMesh(mesh, settings.simplify_ratio, error, settings.simplify_attributes, settings.simplify_aggressive, settings.simplify_lock_borders, settings.simplify_permissive); } optimizeMesh(mesh, settings.compressmore); break; default: assert(!"Unknown primitive type"); } } static float getScale(const float* transform) { float translation[3], rotation[4], scale[3]; decomposeTransform(translation, rotation, scale, transform); float sx = fabsf(scale[0]), sy = fabsf(scale[1]), sz = fabsf(scale[2]); return std::max(std::max(sx, sy), sz); } void computeMeshQuality(std::vector& meshes) { std::vector scales(meshes.size(), 1.f); float maxscale = 0.f; for (size_t i = 0; i < meshes.size(); ++i) { Mesh& mesh = meshes[i]; const Stream* positions = getStream(mesh, cgltf_attribute_type_position); if (!positions) continue; float geometry_scale = meshopt_simplifyScale(positions->data[0].f, positions->data.size(), sizeof(Attr)); float node_maxscale = 0.f; for (cgltf_node* node : mesh.nodes) { float transform[16]; cgltf_node_transform_world(node, transform); node_maxscale = std::max(node_maxscale, getScale(transform)); } for (const Instance& xf : mesh.instances) node_maxscale = std::max(node_maxscale, getScale(xf.transform)); scales[i] = node_maxscale == 0.f ? geometry_scale : node_maxscale * geometry_scale; maxscale = std::max(maxscale, scales[i]); } for (size_t i = 0; i < meshes.size(); ++i) meshes[i].quality = (scales[i] == 0.f || maxscale == 0.f) ? 1.f : scales[i] / maxscale; } bool hasVertexAlpha(const Mesh& mesh) { const Stream* color = getStream(const_cast(mesh), cgltf_attribute_type_color); if (!color) return false; for (size_t i = 0; i < color->data.size(); ++i) if (color->data[i].f[3] < 1.f) return true; return false; } bool hasInstanceAlpha(const std::vector& instances) { for (const Instance& instance : instances) if (instance.color[3] < 1.f) return true; return false; } ================================================ FILE: gltf/node.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include #include void markScenes(cgltf_data* data, std::vector& nodes) { for (size_t i = 0; i < nodes.size(); ++i) nodes[i].scene = -1; for (size_t i = 0; i < data->scenes_count; ++i) for (size_t j = 0; j < data->scenes[i].nodes_count; ++j) { NodeInfo& ni = nodes[data->scenes[i].nodes[j] - data->nodes]; if (ni.scene >= 0) ni.scene = -2; // multiple scenes else ni.scene = int(i); } for (size_t i = 0; i < data->nodes_count; ++i) { cgltf_node* root = &data->nodes[i]; while (root->parent) root = root->parent; nodes[i].scene = nodes[root - data->nodes].scene; } } void markAnimated(cgltf_data* data, std::vector& nodes, const std::vector& animations) { for (size_t i = 0; i < animations.size(); ++i) { const Animation& animation = animations[i]; for (size_t j = 0; j < animation.tracks.size(); ++j) { const Track& track = animation.tracks[j]; // mark nodes that have animation tracks that change their base transform as animated if (!track.dummy) { NodeInfo& ni = nodes[track.node - data->nodes]; ni.animated_path_mask |= (1 << track.path); } } } for (size_t i = 0; i < data->nodes_count; ++i) { NodeInfo& ni = nodes[i]; for (cgltf_node* node = &data->nodes[i]; node; node = node->parent) ni.animated |= nodes[node - data->nodes].animated_path_mask != 0; } } void markNeededNodes(cgltf_data* data, std::vector& nodes, const std::vector& meshes, const std::vector& animations, const Settings& settings) { // mark all joints as kept for (size_t i = 0; i < data->skins_count; ++i) { const cgltf_skin& skin = data->skins[i]; // for now we keep all joints directly referenced by the skin and the entire ancestry tree; we keep names for joints as well for (size_t j = 0; j < skin.joints_count; ++j) { NodeInfo& ni = nodes[skin.joints[j] - data->nodes]; ni.keep = true; } } // mark all animated nodes as kept for (size_t i = 0; i < animations.size(); ++i) { const Animation& animation = animations[i]; for (size_t j = 0; j < animation.tracks.size(); ++j) { const Track& track = animation.tracks[j]; if (settings.anim_const || !track.dummy) { NodeInfo& ni = nodes[track.node - data->nodes]; ni.keep = true; } } } // mark all mesh nodes as kept for (size_t i = 0; i < meshes.size(); ++i) { const Mesh& mesh = meshes[i]; for (size_t j = 0; j < mesh.nodes.size(); ++j) { NodeInfo& ni = nodes[mesh.nodes[j] - data->nodes]; ni.keep = true; } } // mark all light/camera nodes as kept for (size_t i = 0; i < data->nodes_count; ++i) { const cgltf_node& node = data->nodes[i]; if (node.light || node.camera) { nodes[i].keep = true; } } // mark all named nodes as needed (if -kn is specified) if (settings.keep_nodes) { for (size_t i = 0; i < data->nodes_count; ++i) { const cgltf_node& node = data->nodes[i]; if (node.name && *node.name) { nodes[i].keep = true; } } } } void remapNodes(cgltf_data* data, std::vector& nodes, size_t& node_offset) { // to keep a node, we currently need to keep the entire ancestry chain for (size_t i = 0; i < data->nodes_count; ++i) { if (!nodes[i].keep) continue; for (cgltf_node* node = &data->nodes[i]; node; node = node->parent) nodes[node - data->nodes].keep = true; } // generate sequential indices for all nodes; they aren't sorted topologically for (size_t i = 0; i < data->nodes_count; ++i) { NodeInfo& ni = nodes[i]; if (ni.keep) { ni.remap = int(node_offset); node_offset++; } } } void decomposeTransform(float translation[3], float rotation[4], float scale[3], const float* transform) { float m[4][4] = {}; memcpy(m, transform, 16 * sizeof(float)); // extract translation from last row translation[0] = m[3][0]; translation[1] = m[3][1]; translation[2] = m[3][2]; // compute determinant to determine handedness float det = m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]); float sign = (det < 0.f) ? -1.f : 1.f; // recover scale from axis lengths scale[0] = sqrtf(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]) * sign; scale[1] = sqrtf(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]) * sign; scale[2] = sqrtf(m[2][0] * m[2][0] + m[2][1] * m[2][1] + m[2][2] * m[2][2]) * sign; // normalize axes to get a pure rotation matrix float rsx = (scale[0] == 0.f) ? 0.f : 1.f / scale[0]; float rsy = (scale[1] == 0.f) ? 0.f : 1.f / scale[1]; float rsz = (scale[2] == 0.f) ? 0.f : 1.f / scale[2]; float r00 = m[0][0] * rsx, r10 = m[1][0] * rsy, r20 = m[2][0] * rsz; float r01 = m[0][1] * rsx, r11 = m[1][1] * rsy, r21 = m[2][1] * rsz; float r02 = m[0][2] * rsx, r12 = m[1][2] * rsy, r22 = m[2][2] * rsz; // "branchless" version of Mike Day's matrix to quaternion conversion int qc = r22 < 0 ? (r00 > r11 ? 0 : 1) : (r00 < -r11 ? 2 : 3); float qs1 = qc & 2 ? -1.f : 1.f; float qs2 = qc & 1 ? -1.f : 1.f; float qs3 = (qc - 1) & 2 ? -1.f : 1.f; float qt = 1.f - qs3 * r00 - qs2 * r11 - qs1 * r22; float qs = 0.5f / sqrtf(qt); rotation[qc ^ 0] = qs * qt; rotation[qc ^ 1] = qs * (r01 + qs1 * r10); rotation[qc ^ 2] = qs * (r20 + qs2 * r02); rotation[qc ^ 3] = qs * (r12 + qs3 * r21); } ================================================ FILE: gltf/package.json ================================================ { "name": "gltfpack", "version": "1.0.0", "description": "A command-line tool that can optimize glTF files for size and speed", "author": "Arseny Kapoulkine", "license": "MIT", "bugs": "https://github.com/zeux/meshoptimizer/issues", "homepage": "https://github.com/zeux/meshoptimizer", "keywords": [ "gltf" ], "repository": { "type": "git", "url": "https://github.com/zeux/meshoptimizer" }, "type": "module", "bin": "./cli.js", "main": "./library.js", "files": [ "*.js", "*.wasm" ], "scripts": { "prepublishOnly": "node cli.js -v" }, "engines": { "node": ">=18" } } ================================================ FILE: gltf/parsegltf.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include #include #include #include "../src/meshoptimizer.h" static const size_t kMaxStreams = 16; static const char* getError(cgltf_result result, cgltf_data* data) { switch (result) { case cgltf_result_file_not_found: return data ? "resource not found" : "file not found"; case cgltf_result_io_error: return "I/O error"; case cgltf_result_invalid_json: return "invalid JSON"; case cgltf_result_invalid_gltf: return "invalid GLTF"; case cgltf_result_out_of_memory: return "out of memory"; case cgltf_result_legacy_gltf: return "legacy GLTF"; case cgltf_result_data_too_short: return data ? "buffer too short" : "not a GLTF file"; case cgltf_result_unknown_format: return data ? "unknown resource format" : "not a GLTF file"; default: return "unknown error"; } } static void readAccessor(std::vector& data, const cgltf_accessor* accessor) { assert(accessor->type == cgltf_type_scalar); data.resize(accessor->count); cgltf_accessor_unpack_floats(accessor, &data[0], data.size()); } static void readAccessor(std::vector& data, const cgltf_accessor* accessor) { size_t components = cgltf_num_components(accessor->type); std::vector temp(accessor->count * components); cgltf_accessor_unpack_floats(accessor, &temp[0], temp.size()); data.resize(accessor->count); for (size_t i = 0; i < accessor->count; ++i) { for (size_t k = 0; k < components && k < 4; ++k) data[i].f[k] = temp[i * components + k]; } } static void readAccessor(std::vector& data, const cgltf_accessor* accessor, const std::vector& sparse) { data.resize(sparse.size()); for (size_t i = 0; i < sparse.size(); ++i) cgltf_accessor_read_float(accessor, sparse[i], &data[i].f[0], 4); } static void fixupIndices(std::vector& indices, cgltf_primitive_type& type) { if (type == cgltf_primitive_type_line_loop) { std::vector result; result.reserve(indices.size() * 2 + 2); for (size_t i = 1; i <= indices.size(); ++i) { result.push_back(indices[i - 1]); result.push_back(indices[i % indices.size()]); } indices.swap(result); type = cgltf_primitive_type_lines; } else if (type == cgltf_primitive_type_line_strip) { std::vector result; result.reserve(indices.size() * 2); for (size_t i = 1; i < indices.size(); ++i) { result.push_back(indices[i - 1]); result.push_back(indices[i]); } indices.swap(result); type = cgltf_primitive_type_lines; } else if (type == cgltf_primitive_type_triangle_strip) { std::vector result; result.reserve(indices.size() * 3); for (size_t i = 2; i < indices.size(); ++i) { int flip = i & 1; result.push_back(indices[i - 2 + flip]); result.push_back(indices[i - 1 - flip]); result.push_back(indices[i]); } indices.swap(result); type = cgltf_primitive_type_triangles; } else if (type == cgltf_primitive_type_triangle_fan) { std::vector result; result.reserve(indices.size() * 3); for (size_t i = 2; i < indices.size(); ++i) { result.push_back(indices[0]); result.push_back(indices[i - 1]); result.push_back(indices[i]); } indices.swap(result); type = cgltf_primitive_type_triangles; } else if (type == cgltf_primitive_type_lines) { // glTF files don't require that line index count is divisible by 2, but it is obviously critical for scenes to render indices.resize(indices.size() / 2 * 2); } else if (type == cgltf_primitive_type_triangles) { // glTF files don't require that triangle index count is divisible by 3, but it is obviously critical for scenes to render indices.resize(indices.size() / 3 * 3); } } static bool isIdAttribute(const char* name) { return strcmp(name, "_ID") == 0 || strcmp(name, "_BATCHID") == 0 || strncmp(name, "_FEATURE_ID_", 12) == 0; } static void parseMeshesGltf(cgltf_data* data, std::vector& meshes, std::vector >& mesh_remap) { size_t total_primitives = 0; for (size_t mi = 0; mi < data->meshes_count; ++mi) total_primitives += data->meshes[mi].primitives_count; meshes.reserve(total_primitives); mesh_remap.resize(data->meshes_count); for (size_t mi = 0; mi < data->meshes_count; ++mi) { const cgltf_mesh& mesh = data->meshes[mi]; size_t remap_offset = meshes.size(); for (size_t pi = 0; pi < mesh.primitives_count; ++pi) { const cgltf_primitive& primitive = mesh.primitives[pi]; if (primitive.type == cgltf_primitive_type_points && primitive.indices) { fprintf(stderr, "Warning: ignoring primitive %d of mesh %d because indexed points are not supported\n", int(pi), int(mi)); continue; } meshes.push_back(Mesh()); Mesh& result = meshes.back(); result.scene = -1; result.material = primitive.material; result.extras = primitive.extras; result.type = primitive.type; result.streams.reserve(primitive.attributes_count); size_t vertex_count = primitive.attributes_count ? primitive.attributes[0].data->count : 0; if (primitive.indices) { result.indices.resize(primitive.indices->count); if (!result.indices.empty()) cgltf_accessor_unpack_indices(primitive.indices, &result.indices[0], sizeof(unsigned int), result.indices.size()); for (size_t i = 0; i < result.indices.size(); ++i) assert(result.indices[i] < vertex_count); } else if (primitive.type != cgltf_primitive_type_points) { // note, while we could generate a good index buffer here, mesh will be reindexed during processing result.indices.resize(vertex_count); for (size_t i = 0; i < vertex_count; ++i) result.indices[i] = unsigned(i); } // convert line loops and line/triangle strips to lists fixupIndices(result.indices, result.type); std::vector sparse; // if the index data is very sparse, switch to deindexing on the fly to avoid the excessive cost of reading large accessors if (!result.indices.empty() && result.indices.size() < vertex_count / 2) { sparse = result.indices; // mesh will be reindexed during processing for (size_t i = 0; i < result.indices.size(); ++i) result.indices[i] = unsigned(i); } for (size_t ai = 0; ai < primitive.attributes_count; ++ai) { const cgltf_attribute& attr = primitive.attributes[ai]; if (attr.type == cgltf_attribute_type_invalid || (attr.type == cgltf_attribute_type_custom && !isIdAttribute(attr.name))) { fprintf(stderr, "Warning: ignoring %s attribute %s in primitive %d of mesh %d\n", attr.type == cgltf_attribute_type_invalid ? "unknown" : "custom", attr.name, int(pi), int(mi)); continue; } if (result.streams.size() == kMaxStreams) { fprintf(stderr, "Warning: ignoring attribute %s in primitive %d of mesh %d (limit %d reached)\n", attr.name, int(pi), int(mi), int(kMaxStreams)); continue; } result.streams.push_back(Stream()); Stream& s = result.streams.back(); s.type = attr.type; s.index = attr.index; if (attr.type == cgltf_attribute_type_custom) s.custom_name = attr.name; if (sparse.empty()) readAccessor(s.data, attr.data); else readAccessor(s.data, attr.data, sparse); if (attr.type == cgltf_attribute_type_color && attr.data->type == cgltf_type_vec3) { for (size_t i = 0; i < s.data.size(); ++i) s.data[i].f[3] = 1.0f; } } for (size_t ti = 0; ti < primitive.targets_count; ++ti) { const cgltf_morph_target& target = primitive.targets[ti]; for (size_t ai = 0; ai < target.attributes_count; ++ai) { const cgltf_attribute& attr = target.attributes[ai]; if (attr.type == cgltf_attribute_type_invalid || attr.type == cgltf_attribute_type_custom) { fprintf(stderr, "Warning: ignoring %s attribute %s in morph target %d of primitive %d of mesh %d\n", attr.type == cgltf_attribute_type_invalid ? "unknown" : "custom", attr.name, int(ti), int(pi), int(mi)); continue; } result.streams.push_back(Stream()); Stream& s = result.streams.back(); s.type = attr.type; s.index = attr.index; s.target = int(ti + 1); if (sparse.empty()) readAccessor(s.data, attr.data); else readAccessor(s.data, attr.data, sparse); } } result.targets = primitive.targets_count; result.target_weights.assign(mesh.weights, mesh.weights + mesh.weights_count); result.target_names.assign(mesh.target_names, mesh.target_names + mesh.target_names_count); result.variants.assign(primitive.mappings, primitive.mappings + primitive.mappings_count); } mesh_remap[mi] = std::make_pair(remap_offset, meshes.size()); } } static void parseMeshInstancesGltf(std::vector& instances, cgltf_node* node, size_t ni) { cgltf_accessor* translation = NULL; cgltf_accessor* rotation = NULL; cgltf_accessor* scale = NULL; cgltf_accessor* color = NULL; for (size_t i = 0; i < node->mesh_gpu_instancing.attributes_count; ++i) { const cgltf_attribute& attr = node->mesh_gpu_instancing.attributes[i]; if (strcmp(attr.name, "TRANSLATION") == 0 && attr.data->type == cgltf_type_vec3) translation = attr.data; else if (strcmp(attr.name, "ROTATION") == 0 && attr.data->type == cgltf_type_vec4) rotation = attr.data; else if (strcmp(attr.name, "SCALE") == 0 && attr.data->type == cgltf_type_vec3) scale = attr.data; else if (strcmp(attr.name, "_COLOR_0") == 0 && (attr.data->type == cgltf_type_vec3 || attr.data->type == cgltf_type_vec4)) color = attr.data; else fprintf(stderr, "Warning: ignoring %s instance attribute %s in node %d\n", *attr.name == '_' ? "custom" : "unknown", attr.name, int(ni)); } size_t count = node->mesh_gpu_instancing.attributes[0].data->count; instances.reserve(instances.size() + count); cgltf_node instance = {}; instance.parent = node; instance.has_translation = translation != NULL; instance.has_rotation = rotation != NULL; instance.has_scale = scale != NULL; instance.rotation[3] = 1.f; instance.scale[0] = 1.f; instance.scale[1] = 1.f; instance.scale[2] = 1.f; for (size_t i = 0; i < count; ++i) { if (translation) cgltf_accessor_read_float(translation, i, instance.translation, 4); if (rotation) cgltf_accessor_read_float(rotation, i, instance.rotation, 4); if (scale) cgltf_accessor_read_float(scale, i, instance.scale, 4); Instance obj = {}; cgltf_node_transform_world(&instance, obj.transform); obj.color[0] = obj.color[1] = obj.color[2] = obj.color[3] = 1.0f; if (color) cgltf_accessor_read_float(color, i, obj.color, 4); instances.push_back(obj); } } static void parseMeshNodesGltf(cgltf_data* data, std::vector& meshes, const std::vector >& mesh_remap) { for (size_t i = 0; i < data->nodes_count; ++i) { cgltf_node& node = data->nodes[i]; if (!node.mesh) continue; std::pair range = mesh_remap[node.mesh - data->meshes]; for (size_t mi = range.first; mi < range.second; ++mi) { Mesh* mesh = &meshes[mi]; if (mesh->skin != node.skin && (!mesh->nodes.empty() || !mesh->instances.empty())) { // this should be extremely rare - if the same mesh is used with different skins, we need to duplicate it // in this case we don't spend any effort on keeping the number of duplicates to the minimum, because this // should really never happen. meshes.push_back(*mesh); mesh = &meshes.back(); } if (node.has_mesh_gpu_instancing) { mesh->scene = 0; // we need to assign scene index since instances are attached to a scene; for now we assume 0 parseMeshInstancesGltf(mesh->instances, &node, i); } else { mesh->skin = node.skin; mesh->nodes.push_back(&node); } } } for (size_t i = 0; i < meshes.size(); ++i) { Mesh& mesh = meshes[i]; // because the rest of gltfpack assumes that empty nodes array = world-space mesh, we need to filter unused meshes if (mesh.nodes.empty() && mesh.instances.empty()) { mesh.streams.clear(); mesh.indices.clear(); } } } static void parseAnimationsGltf(cgltf_data* data, std::vector& animations) { animations.reserve(data->animations_count); for (size_t i = 0; i < data->animations_count; ++i) { const cgltf_animation& animation = data->animations[i]; animations.push_back(Animation()); Animation& result = animations.back(); result.name = animation.name; result.tracks.reserve(animation.channels_count); for (size_t j = 0; j < animation.channels_count; ++j) { const cgltf_animation_channel& channel = animation.channels[j]; if (!channel.target_node) { fprintf(stderr, "Warning: ignoring channel %d of animation %d (%s) because it has no target node\n", int(j), int(i), animation.name ? animation.name : ""); continue; } result.tracks.push_back(Track()); Track& track = result.tracks.back(); track.node = channel.target_node; track.path = channel.target_path; track.components = (channel.target_path == cgltf_animation_path_type_weights) ? track.node->mesh->primitives[0].targets_count : 1; track.interpolation = channel.sampler->interpolation; readAccessor(track.time, channel.sampler->input); readAccessor(track.data, channel.sampler->output); } if (result.tracks.empty()) { fprintf(stderr, "Warning: ignoring animation %d (%s) because it has no valid tracks\n", int(i), animation.name ? animation.name : ""); animations.pop_back(); } } } static bool requiresExtension(cgltf_data* data, const char* name) { for (size_t i = 0; i < data->extensions_required_count; ++i) if (strcmp(data->extensions_required[i], name) == 0) return true; return false; } static bool needsDummyBuffers(cgltf_data* data) { for (size_t i = 0; i < data->accessors_count; ++i) { cgltf_accessor* accessor = &data->accessors[i]; if (accessor->buffer_view && accessor->buffer_view->data == NULL && accessor->buffer_view->buffer->data == NULL) return true; if (accessor->is_sparse) { cgltf_accessor_sparse* sparse = &accessor->sparse; if (sparse->indices_buffer_view->buffer->data == NULL) return true; if (sparse->values_buffer_view->buffer->data == NULL) return true; } } for (size_t i = 0; i < data->images_count; ++i) { cgltf_image* image = &data->images[i]; if (image->buffer_view && image->buffer_view->buffer->data == NULL) return true; } return false; } static void freeFile(cgltf_data* data) { data->json = NULL; data->bin = NULL; free(data->file_data); data->file_data = NULL; } static bool freeUnusedBuffers(cgltf_data* data) { std::vector used(data->buffers_count); for (size_t i = 0; i < data->skins_count; ++i) { const cgltf_skin& skin = data->skins[i]; if (skin.inverse_bind_matrices && skin.inverse_bind_matrices->buffer_view) { assert(skin.inverse_bind_matrices->buffer_view->buffer); used[skin.inverse_bind_matrices->buffer_view->buffer - data->buffers] = 1; } } for (size_t i = 0; i < data->images_count; ++i) { const cgltf_image& image = data->images[i]; if (image.buffer_view) { assert(image.buffer_view->buffer); used[image.buffer_view->buffer - data->buffers] = 1; } } bool free_bin = false; for (size_t i = 0; i < data->buffers_count; ++i) { cgltf_buffer& buffer = data->buffers[i]; if (!used[i] && buffer.data) { if (buffer.data != data->bin) free(buffer.data); else free_bin = true; buffer.data = NULL; } } return free_bin; } static cgltf_result decompressMeshopt(cgltf_data* data) { for (size_t i = 0; i < data->buffer_views_count; ++i) { if (!data->buffer_views[i].has_meshopt_compression) continue; cgltf_meshopt_compression* mc = &data->buffer_views[i].meshopt_compression; const unsigned char* source = (const unsigned char*)mc->buffer->data; if (!source) return cgltf_result_invalid_gltf; source += mc->offset; void* result = malloc(mc->count * mc->stride); if (!result) return cgltf_result_out_of_memory; data->buffer_views[i].data = result; int rc = -1; switch (mc->mode) { case cgltf_meshopt_compression_mode_attributes: rc = meshopt_decodeVertexBuffer(result, mc->count, mc->stride, source, mc->size); break; case cgltf_meshopt_compression_mode_triangles: rc = meshopt_decodeIndexBuffer(result, mc->count, mc->stride, source, mc->size); break; case cgltf_meshopt_compression_mode_indices: rc = meshopt_decodeIndexSequence(result, mc->count, mc->stride, source, mc->size); break; default: return cgltf_result_invalid_gltf; } if (rc != 0) return cgltf_result_io_error; switch (mc->filter) { case cgltf_meshopt_compression_filter_octahedral: meshopt_decodeFilterOct(result, mc->count, mc->stride); break; case cgltf_meshopt_compression_filter_quaternion: meshopt_decodeFilterQuat(result, mc->count, mc->stride); break; case cgltf_meshopt_compression_filter_exponential: meshopt_decodeFilterExp(result, mc->count, mc->stride); break; case cgltf_meshopt_compression_filter_color: meshopt_decodeFilterColor(result, mc->count, mc->stride); break; default: break; } } return cgltf_result_success; } static cgltf_data* parseGltf(cgltf_data* data, cgltf_result result, std::vector& meshes, std::vector& animations, const char** error) { *error = NULL; if (result != cgltf_result_success) *error = getError(result, data); else if (requiresExtension(data, "KHR_draco_mesh_compression")) *error = "file requires Draco mesh compression support"; else if (needsDummyBuffers(data)) *error = "buffer has no data"; if (*error) { cgltf_free(data); return NULL; } if (requiresExtension(data, "KHR_mesh_quantization")) fprintf(stderr, "Warning: file uses quantized geometry; repacking may result in increased quantization error\n"); if (requiresExtension(data, "EXT_mesh_gpu_instancing") && data->scenes_count > 1) fprintf(stderr, "Warning: file uses instancing and has more than one scene; results may be incorrect\n"); std::vector > mesh_remap; parseMeshesGltf(data, meshes, mesh_remap); parseMeshNodesGltf(data, meshes, mesh_remap); parseAnimationsGltf(data, animations); bool free_bin = freeUnusedBuffers(data); if (data->bin && free_bin) freeFile(data); return data; } cgltf_data* parseGltf(const char* path, std::vector& meshes, std::vector& animations, const char** error) { cgltf_data* data = NULL; cgltf_options options = {}; cgltf_result result = cgltf_parse_file(&options, path, &data); if (result == cgltf_result_success && !data->bin) freeFile(data); result = (result == cgltf_result_success) ? cgltf_load_buffers(&options, data, path) : result; result = (result == cgltf_result_success) ? cgltf_validate(data) : result; result = (result == cgltf_result_success) ? decompressMeshopt(data) : result; return parseGltf(data, result, meshes, animations, error); } cgltf_data* parseGlb(const void* buffer, size_t size, std::vector& meshes, std::vector& animations, const char** error) { cgltf_data* data = NULL; cgltf_options options = {}; options.type = cgltf_file_type_glb; cgltf_result result = cgltf_parse(&options, buffer, size, &data); result = (result == cgltf_result_success) ? cgltf_load_buffers(&options, data, NULL) : result; result = (result == cgltf_result_success) ? cgltf_validate(data) : result; result = (result == cgltf_result_success) ? decompressMeshopt(data) : result; return parseGltf(data, result, meshes, animations, error); } bool areExtrasEqual(const cgltf_extras& lhs, const cgltf_extras& rhs) { if (lhs.data && rhs.data) return strcmp(lhs.data, rhs.data) == 0; else return lhs.data == rhs.data; } ================================================ FILE: gltf/parselib.cpp ================================================ #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #define CGLTF_IMPLEMENTATION #include "../extern/cgltf.h" #define FAST_OBJ_IMPLEMENTATION #include "../extern/fast_obj.h" #undef CGLTF_IMPLEMENTATION #undef FAST_OBJ_IMPLEMENTATION ================================================ FILE: gltf/parseobj.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include "../extern/fast_obj.h" #include "../src/meshoptimizer.h" #include #include static void defaultFree(void*, void* p) { free(p); } static int textureIndex(const std::vector& textures, unsigned int name) { for (size_t i = 0; i < textures.size(); ++i) if (textures[i] == name) return int(i); return -1; } static void fixupUri(char* uri) { // Some .obj paths come with back slashes, that are invalid as URI separators and won't open on macOS/Linux when embedding textures for (char* s = uri; *s; ++s) if (*s == '\\') *s = '/'; } static void parseMaterialsObj(fastObjMesh* obj, cgltf_data* data) { // every texture in obj has a unique id (1+); we convert it to a 0-based index // this effectively extracts only used textures out of the obj, as we don't remove unused textures later in the processing std::vector textures; for (unsigned int mi = 0; mi < obj->material_count; ++mi) { fastObjMaterial& om = obj->materials[mi]; if (om.map_Kd && textureIndex(textures, om.map_Kd) < 0) textures.push_back(om.map_Kd); } data->images = (cgltf_image*)calloc(textures.size(), sizeof(cgltf_image)); data->images_count = textures.size(); for (size_t i = 0; i < textures.size(); ++i) { unsigned int id = textures[i]; data->images[i].uri = strdup(obj->textures[id].name); fixupUri(data->images[i].uri); } data->textures = (cgltf_texture*)calloc(textures.size(), sizeof(cgltf_texture)); data->textures_count = textures.size(); for (size_t i = 0; i < textures.size(); ++i) { data->textures[i].image = &data->images[i]; } data->materials = (cgltf_material*)calloc(obj->material_count, sizeof(cgltf_material)); data->materials_count = obj->material_count; for (unsigned int mi = 0; mi < obj->material_count; ++mi) { const fastObjMaterial& om = obj->materials[mi]; cgltf_material& gm = data->materials[mi]; if (om.name) gm.name = strdup(om.name); gm.has_pbr_metallic_roughness = true; gm.pbr_metallic_roughness.base_color_factor[0] = 1.0f; gm.pbr_metallic_roughness.base_color_factor[1] = 1.0f; gm.pbr_metallic_roughness.base_color_factor[2] = 1.0f; gm.pbr_metallic_roughness.base_color_factor[3] = 1.0f; gm.pbr_metallic_roughness.metallic_factor = 0.0f; gm.pbr_metallic_roughness.roughness_factor = 1.0f; gm.alpha_cutoff = 0.5f; if (om.map_Kd) { gm.pbr_metallic_roughness.base_color_texture.texture = &data->textures[textureIndex(textures, om.map_Kd)]; gm.pbr_metallic_roughness.base_color_texture.scale = 1.0f; gm.alpha_mode = (om.illum == 4 || om.illum == 6 || om.illum == 7 || om.illum == 9) ? cgltf_alpha_mode_mask : cgltf_alpha_mode_opaque; } else { gm.pbr_metallic_roughness.base_color_factor[0] = om.Kd[0]; gm.pbr_metallic_roughness.base_color_factor[1] = om.Kd[1]; gm.pbr_metallic_roughness.base_color_factor[2] = om.Kd[2]; } if (om.map_d) { if (om.map_Kd && strcmp(obj->textures[om.map_Kd].name, obj->textures[om.map_d].name) != 0) fprintf(stderr, "Warning: material has different diffuse and alpha textures (Kd: %s, d: %s) and might not render correctly\n", obj->textures[om.map_Kd].name, obj->textures[om.map_d].name); gm.alpha_mode = cgltf_alpha_mode_blend; } else if (om.d < 1.0f) { gm.pbr_metallic_roughness.base_color_factor[3] = om.d; gm.alpha_mode = cgltf_alpha_mode_blend; } } } static void parseNodesObj(fastObjMesh* obj, cgltf_data* data) { data->nodes = (cgltf_node*)calloc(obj->object_count, sizeof(cgltf_node)); data->nodes_count = obj->object_count; for (unsigned int oi = 0; oi < obj->object_count; ++oi) { const fastObjGroup& og = obj->objects[oi]; cgltf_node* node = &data->nodes[oi]; if (og.name) node->name = strdup(og.name); node->rotation[3] = 1.0f; node->scale[0] = 1.0f; node->scale[1] = 1.0f; node->scale[2] = 1.0f; } data->scenes = (cgltf_scene*)calloc(1, sizeof(cgltf_scene)); data->scenes_count = 1; data->scene = data->scenes; data->scenes->nodes = (cgltf_node**)calloc(obj->object_count, sizeof(cgltf_node*)); data->scenes->nodes_count = obj->object_count; for (unsigned int oi = 0; oi < obj->object_count; ++oi) data->scenes->nodes[oi] = &data->nodes[oi]; } static void parseMeshObj(fastObjMesh* obj, unsigned int face_offset, unsigned int face_vertex_offset, unsigned int face_count, unsigned int face_vertex_count, unsigned int index_count, Mesh& mesh) { std::vector remap(face_vertex_count); size_t unique_vertices = meshopt_generateVertexRemap(remap.data(), NULL, face_vertex_count, &obj->indices[face_vertex_offset], face_vertex_count, sizeof(fastObjIndex)); int pos_stream = 0; int nrm_stream = obj->normal_count > 1 ? 1 : -1; int tex_stream = obj->texcoord_count > 1 ? 1 + (nrm_stream >= 0) : -1; int col_stream = obj->color_count > 1 ? 1 + (nrm_stream >= 0) + (tex_stream >= 0) : -1; mesh.streams.resize(1 + (nrm_stream >= 0) + (tex_stream >= 0) + (col_stream >= 0)); mesh.streams[pos_stream].type = cgltf_attribute_type_position; mesh.streams[pos_stream].data.resize(unique_vertices); if (nrm_stream >= 0) { mesh.streams[nrm_stream].type = cgltf_attribute_type_normal; mesh.streams[nrm_stream].data.resize(unique_vertices); } if (tex_stream >= 0) { mesh.streams[tex_stream].type = cgltf_attribute_type_texcoord; mesh.streams[tex_stream].data.resize(unique_vertices); } if (col_stream >= 0) { mesh.streams[col_stream].type = cgltf_attribute_type_color; mesh.streams[col_stream].data.resize(unique_vertices); } mesh.indices.resize(index_count); for (unsigned int vi = 0; vi < face_vertex_count; ++vi) { unsigned int target = remap[vi]; // TODO: this fills every target vertex multiple times fastObjIndex ii = obj->indices[face_vertex_offset + vi]; Attr p = {{obj->positions[ii.p * 3 + 0], obj->positions[ii.p * 3 + 1], obj->positions[ii.p * 3 + 2]}}; mesh.streams[pos_stream].data[target] = p; if (nrm_stream >= 0) { Attr n = {{obj->normals[ii.n * 3 + 0], obj->normals[ii.n * 3 + 1], obj->normals[ii.n * 3 + 2]}}; mesh.streams[nrm_stream].data[target] = n; } if (tex_stream >= 0) { Attr t = {{obj->texcoords[ii.t * 2 + 0], 1.f - obj->texcoords[ii.t * 2 + 1]}}; mesh.streams[tex_stream].data[target] = t; } if (col_stream >= 0) { Attr c = {{obj->colors[ii.p * 3 + 0], obj->colors[ii.p * 3 + 1], obj->colors[ii.p * 3 + 2]}}; mesh.streams[col_stream].data[target] = c; } } unsigned int vertex_offset = 0; unsigned int index_offset = 0; for (unsigned int fi = 0; fi < face_count; ++fi) { unsigned int face_vertices = obj->face_vertices[face_offset + fi]; if (mesh.type == cgltf_primitive_type_lines) { for (unsigned int vi = 1; vi < face_vertices; ++vi) { size_t to = index_offset + (vi - 1) * 2; mesh.indices[to + 0] = remap[vertex_offset + vi - 1]; mesh.indices[to + 1] = remap[vertex_offset + vi]; } vertex_offset += face_vertices; index_offset += (face_vertices - 1) * 2; } else { for (unsigned int vi = 2; vi < face_vertices; ++vi) { size_t to = index_offset + (vi - 2) * 3; mesh.indices[to + 0] = remap[vertex_offset]; mesh.indices[to + 1] = remap[vertex_offset + vi - 1]; mesh.indices[to + 2] = remap[vertex_offset + vi]; } vertex_offset += face_vertices; index_offset += (face_vertices - 2) * 3; } } assert(vertex_offset == face_vertex_count); assert(index_offset == index_count); } static void parseMeshGroupObj(fastObjMesh* obj, const fastObjGroup& og, cgltf_data* data, cgltf_node* node, std::vector& meshes) { unsigned int face_vertex_offset = og.index_offset; unsigned int face_end_offset = og.face_offset + og.face_count; for (unsigned int face_offset = og.face_offset; face_offset < face_end_offset;) { unsigned int mi = obj->face_materials[face_offset]; unsigned char isl = obj->face_lines ? obj->face_lines[face_offset] : 0; unsigned int face_count = 0; unsigned int face_vertex_count = 0; unsigned int index_count = 0; for (unsigned int fj = face_offset; fj < face_end_offset && obj->face_materials[fj] == mi && (!obj->face_lines || obj->face_lines[fj] == isl); ++fj) { face_count += 1; face_vertex_count += obj->face_vertices[fj]; index_count += isl ? (obj->face_vertices[fj] - 1) * 2 : (obj->face_vertices[fj] - 2) * 3; } meshes.push_back(Mesh()); Mesh& mesh = meshes.back(); if (data->materials_count) { assert(mi < data->materials_count); mesh.material = &data->materials[mi]; } mesh.type = isl ? cgltf_primitive_type_lines : cgltf_primitive_type_triangles; mesh.targets = 0; if (node) mesh.nodes.push_back(node); parseMeshObj(obj, face_offset, face_vertex_offset, face_count, face_vertex_count, index_count, mesh); face_offset += face_count; face_vertex_offset += face_vertex_count; } } cgltf_data* parseObj(const char* path, std::vector& meshes, const char** error) { fastObjMesh* obj = fast_obj_read(path); if (!obj) { *error = "file not found"; return NULL; } int fallback_materials = 0; for (unsigned int mi = 0; mi < obj->material_count; ++mi) fallback_materials += obj->materials[mi].fallback; if (fallback_materials) fprintf(stderr, "Warning: %d/%d materials could not be loaded from mtllib\n", fallback_materials, obj->material_count); cgltf_data* data = (cgltf_data*)calloc(1, sizeof(cgltf_data)); data->memory.free_func = defaultFree; parseMaterialsObj(obj, data); parseNodesObj(obj, data); assert(data->nodes_count == obj->object_count); for (unsigned int oi = 0; oi < obj->object_count; ++oi) parseMeshGroupObj(obj, obj->objects[oi], data, &data->nodes[oi], meshes); fast_obj_destroy(obj); return data; } ================================================ FILE: gltf/stream.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include #include #include #include #include #include "../src/meshoptimizer.h" struct Bounds { Attr min, max; Bounds() { min.f[0] = min.f[1] = min.f[2] = min.f[3] = +FLT_MAX; max.f[0] = max.f[1] = max.f[2] = max.f[3] = -FLT_MAX; } bool isValid() const { return min.f[0] <= max.f[0] && min.f[1] <= max.f[1] && min.f[2] <= max.f[2] && min.f[3] <= max.f[3]; } float getExtent() const { return std::max(max.f[0] - min.f[0], std::max(max.f[1] - min.f[1], max.f[2] - min.f[2])); } void merge(const Bounds& other) { for (int k = 0; k < 4; ++k) { min.f[k] = std::min(min.f[k], other.min.f[k]); max.f[k] = std::max(max.f[k], other.max.f[k]); } } }; static Bounds computeBounds(const Mesh& mesh, cgltf_attribute_type type) { Bounds b; Attr pad = {}; for (size_t j = 0; j < mesh.streams.size(); ++j) { const Stream& s = mesh.streams[j]; if (s.type == type) { if (s.target == 0) { for (size_t k = 0; k < s.data.size(); ++k) { const Attr& a = s.data[k]; b.min.f[0] = std::min(b.min.f[0], a.f[0]); b.min.f[1] = std::min(b.min.f[1], a.f[1]); b.min.f[2] = std::min(b.min.f[2], a.f[2]); b.min.f[3] = std::min(b.min.f[3], a.f[3]); b.max.f[0] = std::max(b.max.f[0], a.f[0]); b.max.f[1] = std::max(b.max.f[1], a.f[1]); b.max.f[2] = std::max(b.max.f[2], a.f[2]); b.max.f[3] = std::max(b.max.f[3], a.f[3]); } } else { for (size_t k = 0; k < s.data.size(); ++k) { const Attr& a = s.data[k]; pad.f[0] = std::max(pad.f[0], fabsf(a.f[0])); pad.f[1] = std::max(pad.f[1], fabsf(a.f[1])); pad.f[2] = std::max(pad.f[2], fabsf(a.f[2])); pad.f[3] = std::max(pad.f[3], fabsf(a.f[3])); } } } } for (int k = 0; k < 4; ++k) { b.min.f[k] -= pad.f[k]; b.max.f[k] += pad.f[k]; } return b; } static float computeUvArea(const Mesh& mesh) { if (mesh.indices.empty() || mesh.type != cgltf_primitive_type_triangles) return 0.f; float result = 0.f; for (size_t j = 0; j < mesh.streams.size(); ++j) { const Stream& s = mesh.streams[j]; if (s.type != cgltf_attribute_type_texcoord) continue; float uvarea = 0.f; for (size_t i = 0; i < mesh.indices.size(); i += 3) { unsigned int a = mesh.indices[i + 0]; unsigned int b = mesh.indices[i + 1]; unsigned int c = mesh.indices[i + 2]; const Attr& va = s.data[a]; const Attr& vb = s.data[b]; const Attr& vc = s.data[c]; uvarea += fabsf((vb.f[0] - va.f[0]) * (vc.f[1] - va.f[1]) - (vc.f[0] - va.f[0]) * (vb.f[1] - va.f[1])); } result = std::max(result, uvarea / float(mesh.indices.size() / 3)); } return result; } QuantizationPosition prepareQuantizationPosition(const std::vector& meshes, const Settings& settings) { QuantizationPosition result = {}; result.bits = settings.pos_bits; result.normalized = settings.pos_normalized; std::vector bounds(meshes.size()); for (size_t i = 0; i < meshes.size(); ++i) bounds[i] = computeBounds(meshes[i], cgltf_attribute_type_position); Bounds b; for (size_t i = 0; i < meshes.size(); ++i) b.merge(bounds[i]); if (b.isValid()) { result.offset[0] = b.min.f[0]; result.offset[1] = b.min.f[1]; result.offset[2] = b.min.f[2]; result.scale = b.getExtent(); } if (b.isValid() && settings.quantize && !settings.pos_float) { float error = result.scale * 0.5f / (1 << (result.bits - 1)); float max_rel_error = 0; for (size_t i = 0; i < meshes.size(); ++i) if (bounds[i].isValid() && bounds[i].getExtent() > 1e-2f) max_rel_error = std::max(max_rel_error, error / bounds[i].getExtent()); if (max_rel_error > 5e-2f) fprintf(stderr, "Warning: position data has significant error (%.0f%%); consider using floating-point quantization (-vpf) or more bits (-vp N)\n", max_rel_error * 100); } result.node_scale = result.scale / float((1 << result.bits) - 1) * (result.normalized ? 65535.f : 1.f); return result; } static size_t follow(std::vector& parents, size_t index) { while (index != parents[index]) { size_t parent = parents[index]; parents[index] = parents[parent]; index = parent; } return index; } void prepareQuantizationTexture(cgltf_data* data, std::vector& result, std::vector& indices, const std::vector& meshes, const Settings& settings) { // use union-find to associate each material with a canonical material // this is necessary because any set of materials that are used on the same mesh must use the same quantization std::vector parents(result.size()); for (size_t i = 0; i < parents.size(); ++i) parents[i] = i; for (size_t i = 0; i < meshes.size(); ++i) { const Mesh& mesh = meshes[i]; if (!mesh.material && mesh.variants.empty()) continue; size_t root = follow(parents, (mesh.material ? mesh.material : mesh.variants[0].material) - data->materials); for (size_t j = 0; j < mesh.variants.size(); ++j) { size_t var = follow(parents, mesh.variants[j].material - data->materials); parents[var] = root; } indices[i] = root; } // compute canonical material bounds based on meshes that use them std::vector bounds(result.size()); for (size_t i = 0; i < meshes.size(); ++i) { const Mesh& mesh = meshes[i]; if (!mesh.material && mesh.variants.empty()) continue; indices[i] = follow(parents, indices[i]); Bounds mb = computeBounds(mesh, cgltf_attribute_type_texcoord); bounds[indices[i]].merge(mb); } // detect potential precision issues and warn about them if (settings.quantize && !settings.tex_float) { float max_rel_error = 0; for (size_t i = 0; i < meshes.size(); ++i) { const Mesh& mesh = meshes[i]; if (!mesh.material && mesh.variants.empty()) continue; const Bounds& b = bounds[indices[i]]; if (!b.isValid()) continue; float scale = std::max(b.max.f[0] - b.min.f[0], b.max.f[1] - b.min.f[1]); float error = scale * 0.5f / (1 << (settings.tex_bits - 1)); if (error < 1e-3f) continue; float uvarea = computeUvArea(mesh); float rel_error = uvarea > 0 ? error / sqrtf(uvarea) : 0.f; max_rel_error = std::max(max_rel_error, rel_error); } if (max_rel_error > 1e-1f) fprintf(stderr, "Warning: texture coordinate data has significant error (%.0f%%); consider using floating-point quantization (-vtf) or more bits (-vt N)\n", max_rel_error * 100); } // update all material data using canonical bounds for (size_t i = 0; i < result.size(); ++i) { QuantizationTexture& qt = result[i]; qt.bits = settings.tex_bits; qt.normalized = true; const Bounds& b = bounds[follow(parents, i)]; if (b.isValid()) { qt.offset[0] = b.min.f[0]; qt.offset[1] = b.min.f[1]; qt.scale[0] = b.max.f[0] - b.min.f[0]; qt.scale[1] = b.max.f[1] - b.min.f[1]; } } } void getPositionBounds(float min[3], float max[3], const Stream& stream, const QuantizationPosition& qp, const Settings& settings) { assert(stream.type == cgltf_attribute_type_position); assert(stream.data.size() > 0); min[0] = min[1] = min[2] = FLT_MAX; max[0] = max[1] = max[2] = -FLT_MAX; for (size_t i = 0; i < stream.data.size(); ++i) { const Attr& a = stream.data[i]; for (int k = 0; k < 3; ++k) { min[k] = std::min(min[k], a.f[k]); max[k] = std::max(max[k], a.f[k]); } } if (settings.quantize) { if (settings.pos_float) { for (int k = 0; k < 3; ++k) { min[k] = meshopt_quantizeFloat(min[k], qp.bits); max[k] = meshopt_quantizeFloat(max[k], qp.bits); } } else { float pos_rscale = qp.scale == 0.f ? 0.f : 1.f / qp.scale * (stream.target > 0 && qp.normalized ? 32767.f / 65535.f : 1.f); for (int k = 0; k < 3; ++k) { if (stream.target == 0) { min[k] = float(meshopt_quantizeUnorm((min[k] - qp.offset[k]) * pos_rscale, qp.bits)); max[k] = float(meshopt_quantizeUnorm((max[k] - qp.offset[k]) * pos_rscale, qp.bits)); } else { min[k] = (min[k] >= 0.f ? 1.f : -1.f) * float(meshopt_quantizeUnorm(fabsf(min[k]) * pos_rscale, qp.bits)); max[k] = (max[k] >= 0.f ? 1.f : -1.f) * float(meshopt_quantizeUnorm(fabsf(max[k]) * pos_rscale, qp.bits)); } } } } } static void renormalizeWeights(uint8_t (&w)[4]) { int sum = w[0] + w[1] + w[2] + w[3]; if (sum == 255) return; // we assume that the total error is limited to 0.5/component = 2 // this means that it's acceptable to adjust the max. component to compensate for the error int max = 0; for (int k = 1; k < 4; ++k) if (w[k] > w[max]) max = k; w[max] += uint8_t(255 - sum); } static void encodeSnorm(void* destination, size_t count, size_t stride, int bits, const float* data) { assert(stride == 4 || stride == 8); assert(bits >= 1 && bits <= 16); signed char* d8 = static_cast(destination); short* d16 = static_cast(destination); for (size_t i = 0; i < count; ++i) { const float* v = &data[i * 4]; int fx = meshopt_quantizeSnorm(v[0], bits); int fy = meshopt_quantizeSnorm(v[1], bits); int fz = meshopt_quantizeSnorm(v[2], bits); int fw = meshopt_quantizeSnorm(v[3], bits); if (stride == 4) { d8[i * 4 + 0] = (signed char)(fx); d8[i * 4 + 1] = (signed char)(fy); d8[i * 4 + 2] = (signed char)(fz); d8[i * 4 + 3] = (signed char)(fw); } else { d16[i * 4 + 0] = short(fx); d16[i * 4 + 1] = short(fy); d16[i * 4 + 2] = short(fz); d16[i * 4 + 3] = short(fw); } } } static int quantizeColor(float v, int bytebits, int bits) { int result = meshopt_quantizeUnorm(v, bytebits); // replicate the top bit into the low significant bits const int mask = (1 << (bytebits - bits)) - 1; return (result & ~mask) | (mask & -(result >> (bytebits - 1))); } static void encodeColor(void* destination, size_t count, size_t stride, int bits, const float* data) { assert(stride == 4 || stride == 8); assert(bits >= 2 && bits <= 16); unsigned char* d8 = static_cast(destination); unsigned short* d16 = static_cast(destination); for (size_t i = 0; i < count; ++i) { const float* c = &data[i * 4]; if (stride == 4) { d8[i * 4 + 0] = uint8_t(quantizeColor(c[0], 8, bits)); d8[i * 4 + 1] = uint8_t(quantizeColor(c[1], 8, bits)); d8[i * 4 + 2] = uint8_t(quantizeColor(c[2], 8, bits)); d8[i * 4 + 3] = uint8_t(quantizeColor(c[3], 8, bits)); } else { d16[i * 4 + 0] = uint16_t(quantizeColor(c[0], 16, bits)); d16[i * 4 + 1] = uint16_t(quantizeColor(c[1], 16, bits)); d16[i * 4 + 2] = uint16_t(quantizeColor(c[2], 16, bits)); d16[i * 4 + 3] = uint16_t(quantizeColor(c[3], 16, bits)); } } } static StreamFormat writeVertexStreamRaw(std::string& bin, const Stream& stream, cgltf_type type, size_t components) { assert(components >= 1 && components <= 4); for (size_t i = 0; i < stream.data.size(); ++i) { const Attr& a = stream.data[i]; bin.append(reinterpret_cast(a.f), sizeof(float) * components); } StreamFormat format = {type, cgltf_component_type_r_32f, false, sizeof(float) * components}; return format; } static StreamFormat writeVertexStreamFloat(std::string& bin, const Stream& stream, cgltf_type type, int components, bool expf, int bits, meshopt_EncodeExpMode mode) { assert(components >= 1 && components <= 4); StreamFormat::Filter filter = expf ? StreamFormat::Filter_Exp : StreamFormat::Filter_None; if (filter == StreamFormat::Filter_Exp) { size_t offset = bin.size(); size_t stride = sizeof(float) * components; for (size_t i = 0; i < stream.data.size(); ++i) bin.append(reinterpret_cast(stream.data[i].f), stride); meshopt_encodeFilterExp(&bin[offset], stream.data.size(), stride, bits + 1, reinterpret_cast(&bin[offset]), mode); } else { for (size_t i = 0; i < stream.data.size(); ++i) { const Attr& a = stream.data[i]; float v[4]; for (int k = 0; k < components; ++k) v[k] = meshopt_quantizeFloat(a.f[k], bits); bin.append(reinterpret_cast(v), sizeof(float) * components); } } StreamFormat format = {type, cgltf_component_type_r_32f, false, sizeof(float) * components, filter}; return format; } StreamFormat writeVertexStream(std::string& bin, const Stream& stream, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings, bool filters) { if (stream.type == cgltf_attribute_type_position) { if (!settings.quantize) return writeVertexStreamRaw(bin, stream, cgltf_type_vec3, 3); if (settings.pos_float) return writeVertexStreamFloat(bin, stream, cgltf_type_vec3, 3, settings.compress && filters, qp.bits, settings.compressmore ? meshopt_EncodeExpSharedComponent : meshopt_EncodeExpSeparate); if (stream.target == 0) { float pos_rscale = qp.scale == 0.f ? 0.f : 1.f / qp.scale; for (size_t i = 0; i < stream.data.size(); ++i) { const Attr& a = stream.data[i]; uint16_t v[4] = { uint16_t(meshopt_quantizeUnorm((a.f[0] - qp.offset[0]) * pos_rscale, qp.bits)), uint16_t(meshopt_quantizeUnorm((a.f[1] - qp.offset[1]) * pos_rscale, qp.bits)), uint16_t(meshopt_quantizeUnorm((a.f[2] - qp.offset[2]) * pos_rscale, qp.bits)), 0}; bin.append(reinterpret_cast(v), sizeof(v)); } StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_16u, qp.normalized, 8}; return format; } else { float pos_rscale = qp.scale == 0.f ? 0.f : 1.f / qp.scale * (qp.normalized ? 32767.f / 65535.f : 1.f); int maxv = 0; for (size_t i = 0; i < stream.data.size(); ++i) { const Attr& a = stream.data[i]; maxv = std::max(maxv, meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, qp.bits)); maxv = std::max(maxv, meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, qp.bits)); maxv = std::max(maxv, meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, qp.bits)); } if (maxv <= 127 && !qp.normalized) { for (size_t i = 0; i < stream.data.size(); ++i) { const Attr& a = stream.data[i]; int8_t v[4] = { int8_t((a.f[0] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, qp.bits)), int8_t((a.f[1] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, qp.bits)), int8_t((a.f[2] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, qp.bits)), 0}; bin.append(reinterpret_cast(v), sizeof(v)); } StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_8, false, 4}; return format; } else { for (size_t i = 0; i < stream.data.size(); ++i) { const Attr& a = stream.data[i]; int16_t v[4] = { int16_t((a.f[0] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, qp.bits)), int16_t((a.f[1] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, qp.bits)), int16_t((a.f[2] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, qp.bits)), 0}; bin.append(reinterpret_cast(v), sizeof(v)); } StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_16, qp.normalized, 8}; return format; } } } else if (stream.type == cgltf_attribute_type_texcoord) { if (!settings.quantize) return writeVertexStreamRaw(bin, stream, cgltf_type_vec2, 2); // expand the encoded range to ensure it covers [0..1) interval // this can slightly reduce precision but we should not need more precision inside 0..1, and this significantly improves compressed size when using encodeExpOne if (settings.tex_float) return writeVertexStreamFloat(bin, stream, cgltf_type_vec2, 2, settings.compress && filters, qt.bits, meshopt_EncodeExpClamped); float uv_rscale[2] = { qt.scale[0] == 0.f ? 0.f : 1.f / qt.scale[0], qt.scale[1] == 0.f ? 0.f : 1.f / qt.scale[1], }; for (size_t i = 0; i < stream.data.size(); ++i) { const Attr& a = stream.data[i]; uint16_t v[2] = { uint16_t(meshopt_quantizeUnorm((a.f[0] - qt.offset[0]) * uv_rscale[0], qt.bits)), uint16_t(meshopt_quantizeUnorm((a.f[1] - qt.offset[1]) * uv_rscale[1], qt.bits)), }; bin.append(reinterpret_cast(v), sizeof(v)); } StreamFormat format = {cgltf_type_vec2, cgltf_component_type_r_16u, qt.normalized, 4}; return format; } else if (stream.type == cgltf_attribute_type_normal) { if (!settings.quantize) return writeVertexStreamRaw(bin, stream, cgltf_type_vec3, 3); // expand the encoded range to ensure it covers [0..1) interval if (settings.nrm_float) return writeVertexStreamFloat(bin, stream, cgltf_type_vec3, 3, settings.compress && filters, settings.nrm_bits, (settings.compressmore || stream.target) ? meshopt_EncodeExpSharedComponent : meshopt_EncodeExpClamped); bool oct = filters && settings.compressmore && stream.target == 0; int bits = settings.nrm_bits; StreamFormat::Filter filter = oct ? StreamFormat::Filter_Oct : StreamFormat::Filter_None; size_t offset = bin.size(); size_t stride = bits > 8 ? 8 : 4; bin.resize(bin.size() + stream.data.size() * stride); if (oct) meshopt_encodeFilterOct(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f); else encodeSnorm(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f); cgltf_component_type component_type = bits > 8 ? cgltf_component_type_r_16 : cgltf_component_type_r_8; StreamFormat format = {cgltf_type_vec3, component_type, true, stride, filter}; return format; } else if (stream.type == cgltf_attribute_type_tangent) { if (!settings.quantize) return writeVertexStreamRaw(bin, stream, cgltf_type_vec4, 4); bool oct = filters && settings.compressmore && stream.target == 0; int bits = (settings.nrm_bits > 8) ? 8 : settings.nrm_bits; StreamFormat::Filter filter = oct ? StreamFormat::Filter_Oct : StreamFormat::Filter_None; size_t offset = bin.size(); size_t stride = 4; bin.resize(bin.size() + stream.data.size() * stride); if (oct) meshopt_encodeFilterOct(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f); else encodeSnorm(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f); cgltf_type type = (stream.target == 0) ? cgltf_type_vec4 : cgltf_type_vec3; StreamFormat format = {type, cgltf_component_type_r_8, true, 4, filter}; return format; } else if (stream.type == cgltf_attribute_type_color) { bool col = filters && settings.compresskhr && settings.compressmore; int bits = settings.col_bits; StreamFormat::Filter filter = col ? StreamFormat::Filter_Color : StreamFormat::Filter_None; size_t offset = bin.size(); size_t stride = bits > 8 ? 8 : 4; bin.resize(bin.size() + stream.data.size() * stride); if (col) meshopt_encodeFilterColor(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f); else encodeColor(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f); if (bits > 8) { StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16u, true, 8, filter}; return format; } else { StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, true, 4, filter}; return format; } } else if (stream.type == cgltf_attribute_type_weights) { for (size_t i = 0; i < stream.data.size(); ++i) { const Attr& a = stream.data[i]; float ws = a.f[0] + a.f[1] + a.f[2] + a.f[3]; float wsi = (ws == 0.f) ? 0.f : 1.f / ws; uint8_t v[4] = { uint8_t(meshopt_quantizeUnorm(a.f[0] * wsi, 8)), uint8_t(meshopt_quantizeUnorm(a.f[1] * wsi, 8)), uint8_t(meshopt_quantizeUnorm(a.f[2] * wsi, 8)), uint8_t(meshopt_quantizeUnorm(a.f[3] * wsi, 8))}; if (wsi != 0.f) renormalizeWeights(v); bin.append(reinterpret_cast(v), sizeof(v)); } StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, true, 4}; return format; } else if (stream.type == cgltf_attribute_type_joints) { unsigned int maxj = 0; for (size_t i = 0; i < stream.data.size(); ++i) maxj = std::max(maxj, unsigned(stream.data[i].f[0])); assert(maxj <= 65535); if (maxj <= 255) { for (size_t i = 0; i < stream.data.size(); ++i) { const Attr& a = stream.data[i]; uint8_t v[4] = { uint8_t(a.f[0]), uint8_t(a.f[1]), uint8_t(a.f[2]), uint8_t(a.f[3])}; bin.append(reinterpret_cast(v), sizeof(v)); } StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, false, 4}; return format; } else { for (size_t i = 0; i < stream.data.size(); ++i) { const Attr& a = stream.data[i]; uint16_t v[4] = { uint16_t(a.f[0]), uint16_t(a.f[1]), uint16_t(a.f[2]), uint16_t(a.f[3])}; bin.append(reinterpret_cast(v), sizeof(v)); } StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16u, false, 8}; return format; } } else if (stream.type == cgltf_attribute_type_custom) { // note: _custom is equivalent to _ID, as such the data contains scalar integers if (!settings.compressmore || !filters) return writeVertexStreamRaw(bin, stream, cgltf_type_scalar, 1); unsigned int maxv = 0; for (size_t i = 0; i < stream.data.size(); ++i) maxv = std::max(maxv, unsigned(stream.data[i].f[0])); // exp encoding uses a signed mantissa with only 23 significant bits; input glTF encoding may encode indices losslessly up to 2^24 if (maxv >= (1 << 23)) return writeVertexStreamRaw(bin, stream, cgltf_type_scalar, 1); for (size_t i = 0; i < stream.data.size(); ++i) { const Attr& a = stream.data[i]; uint32_t id = uint32_t(a.f[0]); uint32_t v = id; // exp encoding of integers in [0..2^23-1] range is equivalent to the integer itself bin.append(reinterpret_cast(&v), sizeof(v)); } StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32f, false, 4, StreamFormat::Filter_Exp}; return format; } else { return writeVertexStreamRaw(bin, stream, cgltf_type_vec4, 4); } } StreamFormat writeIndexStream(std::string& bin, const std::vector& stream) { unsigned int maxi = 0; for (size_t i = 0; i < stream.size(); ++i) maxi = std::max(maxi, stream[i]); // save 16-bit indices if we can; note that we can't use restart index (65535) if (maxi < 65535) { for (size_t i = 0; i < stream.size(); ++i) { uint16_t v[1] = {uint16_t(stream[i])}; bin.append(reinterpret_cast(v), sizeof(v)); } StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_16u, false, 2}; return format; } else { for (size_t i = 0; i < stream.size(); ++i) { uint32_t v[1] = {stream[i]}; bin.append(reinterpret_cast(v), sizeof(v)); } StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32u, false, 4}; return format; } } StreamFormat writeTimeStream(std::string& bin, const std::vector& data) { for (size_t i = 0; i < data.size(); ++i) { float v[1] = {data[i]}; bin.append(reinterpret_cast(v), sizeof(v)); } StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32f, false, 4}; return format; } StreamFormat writeKeyframeStream(std::string& bin, cgltf_animation_path_type type, const std::vector& data, const Settings& settings, bool has_tangents) { if (type == cgltf_animation_path_type_rotation) { StreamFormat::Filter filter = settings.compressmore && !has_tangents ? StreamFormat::Filter_Quat : StreamFormat::Filter_None; size_t offset = bin.size(); size_t stride = 8; bin.resize(bin.size() + data.size() * stride); if (filter == StreamFormat::Filter_Quat) meshopt_encodeFilterQuat(&bin[offset], data.size(), stride, settings.rot_bits, data[0].f); else encodeSnorm(&bin[offset], data.size(), stride, 16, data[0].f); StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16, true, 8, filter}; return format; } else if (type == cgltf_animation_path_type_weights) { for (size_t i = 0; i < data.size(); ++i) { const Attr& a = data[i]; uint8_t v[1] = {uint8_t(meshopt_quantizeUnorm(a.f[0], 8))}; bin.append(reinterpret_cast(v), sizeof(v)); } StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_8u, true, 1}; return format; } else if (type == cgltf_animation_path_type_translation || type == cgltf_animation_path_type_scale) { StreamFormat::Filter filter = settings.compressmore ? StreamFormat::Filter_Exp : StreamFormat::Filter_None; int bits = (type == cgltf_animation_path_type_translation) ? settings.trn_bits : settings.scl_bits; size_t offset = bin.size(); for (size_t i = 0; i < data.size(); ++i) { const Attr& a = data[i]; float v[3] = {a.f[0], a.f[1], a.f[2]}; bin.append(reinterpret_cast(v), sizeof(v)); } if (filter == StreamFormat::Filter_Exp) meshopt_encodeFilterExp(&bin[offset], data.size(), 12, bits, reinterpret_cast(&bin[offset]), meshopt_EncodeExpSharedVector); StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_32f, false, 12, filter}; return format; } else { for (size_t i = 0; i < data.size(); ++i) { const Attr& a = data[i]; float v[4] = {a.f[0], a.f[1], a.f[2], a.f[3]}; bin.append(reinterpret_cast(v), sizeof(v)); } StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_32f, false, 16}; return format; } } void compressVertexStream(std::string& bin, const std::string& data, size_t count, size_t stride, int level) { assert(data.size() == count * stride); std::vector compressed(meshopt_encodeVertexBufferBound(count, stride)); size_t size = meshopt_encodeVertexBufferLevel(&compressed[0], compressed.size(), data.c_str(), count, stride, level, level > 0); bin.append(reinterpret_cast(&compressed[0]), size); } void compressIndexStream(std::string& bin, const std::string& data, size_t count, size_t stride) { assert(stride == 2 || stride == 4); assert(data.size() == count * stride); assert(count % 3 == 0); std::vector compressed(meshopt_encodeIndexBufferBound(count, count)); size_t size = 0; if (stride == 2) size = meshopt_encodeIndexBuffer(&compressed[0], compressed.size(), reinterpret_cast(data.c_str()), count); else size = meshopt_encodeIndexBuffer(&compressed[0], compressed.size(), reinterpret_cast(data.c_str()), count); bin.append(reinterpret_cast(&compressed[0]), size); } void compressIndexSequence(std::string& bin, const std::string& data, size_t count, size_t stride) { assert(stride == 2 || stride == 4); assert(data.size() == count * stride); std::vector compressed(meshopt_encodeIndexSequenceBound(count, count)); size_t size = 0; if (stride == 2) size = meshopt_encodeIndexSequence(&compressed[0], compressed.size(), reinterpret_cast(data.c_str()), count); else size = meshopt_encodeIndexSequence(&compressed[0], compressed.size(), reinterpret_cast(data.c_str()), count); bin.append(reinterpret_cast(&compressed[0]), size); } ================================================ FILE: gltf/wasistubs.cpp ================================================ #ifdef __wasi__ #include #include #include extern "C" void __cxa_throw(void* ptr, void* type, void* destructor) { abort(); } extern "C" void* __cxa_allocate_exception(size_t thrown_size) { abort(); } extern "C" int32_t __wasi_path_open32(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6, int32_t arg7, int32_t arg8) __attribute__(( __import_module__("wasi_snapshot_preview1"), __import_name__("path_open32"), __warn_unused_result__)); extern "C" int32_t __imported_wasi_snapshot_preview1_path_open(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int64_t arg5, int64_t arg6, int32_t arg7, int32_t arg8) { return __wasi_path_open32(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } extern "C" int32_t __wasi_fd_seek32(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) __attribute__(( __import_module__("wasi_snapshot_preview1"), __import_name__("fd_seek32"), __warn_unused_result__)); extern "C" int32_t __imported_wasi_snapshot_preview1_fd_seek(int32_t arg0, int64_t arg1, int32_t arg2, int32_t arg3) { *(uint64_t*)arg3 = 0; return __wasi_fd_seek32(arg0, arg1, arg2, arg3); } extern "C" int32_t __imported_wasi_snapshot_preview1_clock_time_get(int32_t arg0, int64_t arg1, int32_t arg2) { return __WASI_ERRNO_NOSYS; } #endif ================================================ FILE: gltf/wasistubs.txt ================================================ __wasi_path_open32 __wasi_fd_seek32 ================================================ FILE: gltf/write.cpp ================================================ // This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include #include #include #include static const char* componentType(cgltf_component_type type) { switch (type) { case cgltf_component_type_r_8: return "5120"; case cgltf_component_type_r_8u: return "5121"; case cgltf_component_type_r_16: return "5122"; case cgltf_component_type_r_16u: return "5123"; case cgltf_component_type_r_32u: return "5125"; case cgltf_component_type_r_32f: return "5126"; default: return "0"; } } static const char* shapeType(cgltf_type type) { switch (type) { case cgltf_type_scalar: return "SCALAR"; case cgltf_type_vec2: return "VEC2"; case cgltf_type_vec3: return "VEC3"; case cgltf_type_vec4: return "VEC4"; case cgltf_type_mat2: return "MAT2"; case cgltf_type_mat3: return "MAT3"; case cgltf_type_mat4: return "MAT4"; default: return ""; } } const char* attributeType(cgltf_attribute_type type) { switch (type) { case cgltf_attribute_type_position: return "POSITION"; case cgltf_attribute_type_normal: return "NORMAL"; case cgltf_attribute_type_tangent: return "TANGENT"; case cgltf_attribute_type_texcoord: return "TEXCOORD"; case cgltf_attribute_type_color: return "COLOR"; case cgltf_attribute_type_joints: return "JOINTS"; case cgltf_attribute_type_weights: return "WEIGHTS"; case cgltf_attribute_type_custom: return "CUSTOM"; default: return "ATTRIBUTE"; } } const char* animationPath(cgltf_animation_path_type type) { switch (type) { case cgltf_animation_path_type_translation: return "translation"; case cgltf_animation_path_type_rotation: return "rotation"; case cgltf_animation_path_type_scale: return "scale"; case cgltf_animation_path_type_weights: return "weights"; default: return ""; } } static const char* lightType(cgltf_light_type type) { switch (type) { case cgltf_light_type_directional: return "directional"; case cgltf_light_type_point: return "point"; case cgltf_light_type_spot: return "spot"; default: return ""; } } static const char* alphaMode(cgltf_alpha_mode mode) { switch (mode) { case cgltf_alpha_mode_opaque: return "OPAQUE"; case cgltf_alpha_mode_mask: return "MASK"; case cgltf_alpha_mode_blend: return "BLEND"; default: return ""; } } static const char* interpolationType(cgltf_interpolation_type type) { switch (type) { case cgltf_interpolation_type_linear: return "LINEAR"; case cgltf_interpolation_type_step: return "STEP"; case cgltf_interpolation_type_cubic_spline: return "CUBICSPLINE"; default: return ""; } } static const char* compressionMode(BufferView::Compression mode) { switch (mode) { case BufferView::Compression_Attribute: return "ATTRIBUTES"; case BufferView::Compression_Index: return "TRIANGLES"; case BufferView::Compression_IndexSequence: return "INDICES"; default: return ""; } } static const char* compressionFilter(StreamFormat::Filter filter) { switch (filter) { case StreamFormat::Filter_None: return "NONE"; case StreamFormat::Filter_Oct: return "OCTAHEDRAL"; case StreamFormat::Filter_Quat: return "QUATERNION"; case StreamFormat::Filter_Exp: return "EXPONENTIAL"; case StreamFormat::Filter_Color: return "COLOR"; default: return ""; } } static void writeTextureInfo(std::string& json, const cgltf_data* data, const cgltf_texture_view& view, const QuantizationTexture* qt, std::vector& textures, const char* scale = NULL) { assert(view.texture); bool has_transform = false; cgltf_texture_transform transform = {}; transform.scale[0] = transform.scale[1] = 1.f; if (hasValidTransform(view)) { transform = view.transform; has_transform = true; } if (qt) { transform.offset[0] += qt->offset[0]; transform.offset[1] += qt->offset[1]; transform.scale[0] *= qt->scale[0] / float((1 << qt->bits) - 1) * (qt->normalized ? 65535.f : 1.f); transform.scale[1] *= qt->scale[1] / float((1 << qt->bits) - 1) * (qt->normalized ? 65535.f : 1.f); has_transform = true; } append(json, "{\"index\":"); append(json, size_t(textures[view.texture - data->textures].remap)); if (view.texcoord != 0) { append(json, ",\"texCoord\":"); append(json, size_t(view.texcoord)); } if (scale && view.scale != 1) { append(json, ",\""); append(json, scale); append(json, "\":"); append(json, view.scale); } if (has_transform) { append(json, ",\"extensions\":{\"KHR_texture_transform\":{"); append(json, "\"offset\":"); append(json, transform.offset, 2); append(json, ",\"scale\":"); append(json, transform.scale, 2); if (transform.rotation != 0.f) { append(json, ",\"rotation\":"); append(json, transform.rotation); } append(json, "}}"); } append(json, "}"); } static const float white[4] = {1, 1, 1, 1}; static const float black[4] = {0, 0, 0, 0}; static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_pbr_metallic_roughness& pbr, const QuantizationTexture* qt, std::vector& textures) { comma(json); append(json, "\"pbrMetallicRoughness\":{"); if (memcmp(pbr.base_color_factor, white, 16) != 0) { comma(json); append(json, "\"baseColorFactor\":"); append(json, pbr.base_color_factor, 4); } if (pbr.base_color_texture.texture) { comma(json); append(json, "\"baseColorTexture\":"); writeTextureInfo(json, data, pbr.base_color_texture, qt, textures); } if (pbr.metallic_factor != 1) { comma(json); append(json, "\"metallicFactor\":"); append(json, pbr.metallic_factor); } if (pbr.roughness_factor != 1) { comma(json); append(json, "\"roughnessFactor\":"); append(json, pbr.roughness_factor); } if (pbr.metallic_roughness_texture.texture) { comma(json); append(json, "\"metallicRoughnessTexture\":"); writeTextureInfo(json, data, pbr.metallic_roughness_texture, qt, textures); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_pbr_specular_glossiness& pbr, const QuantizationTexture* qt, std::vector& textures) { comma(json); append(json, "\"KHR_materials_pbrSpecularGlossiness\":{"); if (pbr.diffuse_texture.texture) { comma(json); append(json, "\"diffuseTexture\":"); writeTextureInfo(json, data, pbr.diffuse_texture, qt, textures); } if (pbr.specular_glossiness_texture.texture) { comma(json); append(json, "\"specularGlossinessTexture\":"); writeTextureInfo(json, data, pbr.specular_glossiness_texture, qt, textures); } if (memcmp(pbr.diffuse_factor, white, 16) != 0) { comma(json); append(json, "\"diffuseFactor\":"); append(json, pbr.diffuse_factor, 4); } if (memcmp(pbr.specular_factor, white, 12) != 0) { comma(json); append(json, "\"specularFactor\":"); append(json, pbr.specular_factor, 3); } if (pbr.glossiness_factor != 1) { comma(json); append(json, "\"glossinessFactor\":"); append(json, pbr.glossiness_factor); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_clearcoat& cc, const QuantizationTexture* qt, std::vector& textures) { comma(json); append(json, "\"KHR_materials_clearcoat\":{"); if (cc.clearcoat_texture.texture) { comma(json); append(json, "\"clearcoatTexture\":"); writeTextureInfo(json, data, cc.clearcoat_texture, qt, textures); } if (cc.clearcoat_roughness_texture.texture) { comma(json); append(json, "\"clearcoatRoughnessTexture\":"); writeTextureInfo(json, data, cc.clearcoat_roughness_texture, qt, textures); } if (cc.clearcoat_normal_texture.texture) { comma(json); append(json, "\"clearcoatNormalTexture\":"); writeTextureInfo(json, data, cc.clearcoat_normal_texture, qt, textures, "scale"); } if (cc.clearcoat_factor != 0) { comma(json); append(json, "\"clearcoatFactor\":"); append(json, cc.clearcoat_factor); } if (cc.clearcoat_factor != 0) { comma(json); append(json, "\"clearcoatRoughnessFactor\":"); append(json, cc.clearcoat_roughness_factor); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_transmission& tm, const QuantizationTexture* qt, std::vector& textures) { comma(json); append(json, "\"KHR_materials_transmission\":{"); if (tm.transmission_texture.texture) { comma(json); append(json, "\"transmissionTexture\":"); writeTextureInfo(json, data, tm.transmission_texture, qt, textures); } if (tm.transmission_factor != 0) { comma(json); append(json, "\"transmissionFactor\":"); append(json, tm.transmission_factor); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_ior& tm) { (void)data; comma(json); append(json, "\"KHR_materials_ior\":{"); append(json, "\"ior\":"); append(json, tm.ior); append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_specular& tm, const QuantizationTexture* qt, std::vector& textures) { comma(json); append(json, "\"KHR_materials_specular\":{"); if (tm.specular_texture.texture) { comma(json); append(json, "\"specularTexture\":"); writeTextureInfo(json, data, tm.specular_texture, qt, textures); } if (tm.specular_color_texture.texture) { comma(json); append(json, "\"specularColorTexture\":"); writeTextureInfo(json, data, tm.specular_color_texture, qt, textures); } if (tm.specular_factor != 1) { comma(json); append(json, "\"specularFactor\":"); append(json, tm.specular_factor); } if (memcmp(tm.specular_color_factor, white, 12) != 0) { comma(json); append(json, "\"specularColorFactor\":"); append(json, tm.specular_color_factor, 3); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_sheen& tm, const QuantizationTexture* qt, std::vector& textures) { comma(json); append(json, "\"KHR_materials_sheen\":{"); if (tm.sheen_color_texture.texture) { comma(json); append(json, "\"sheenColorTexture\":"); writeTextureInfo(json, data, tm.sheen_color_texture, qt, textures); } if (tm.sheen_roughness_texture.texture) { comma(json); append(json, "\"sheenRoughnessTexture\":"); writeTextureInfo(json, data, tm.sheen_roughness_texture, qt, textures); } if (memcmp(tm.sheen_color_factor, black, 12) != 0) { comma(json); append(json, "\"sheenColorFactor\":"); append(json, tm.sheen_color_factor, 3); } if (tm.sheen_roughness_factor != 0) { comma(json); append(json, "\"sheenRoughnessFactor\":"); append(json, tm.sheen_roughness_factor); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_volume& tm, const QuantizationPosition* qp, const QuantizationTexture* qt, std::vector& textures) { comma(json); append(json, "\"KHR_materials_volume\":{"); if (tm.thickness_texture.texture) { comma(json); append(json, "\"thicknessTexture\":"); writeTextureInfo(json, data, tm.thickness_texture, qt, textures); } if (tm.thickness_factor != 0) { // thickness is in mesh coordinate space which is rescaled by quantization comma(json); append(json, "\"thicknessFactor\":"); append(json, tm.thickness_factor / (qp ? qp->node_scale : 1.f)); } if (memcmp(tm.attenuation_color, white, 12) != 0) { comma(json); append(json, "\"attenuationColor\":"); append(json, tm.attenuation_color, 3); } if (tm.attenuation_distance != FLT_MAX) { comma(json); append(json, "\"attenuationDistance\":"); append(json, tm.attenuation_distance); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_emissive_strength& tm) { (void)data; comma(json); append(json, "\"KHR_materials_emissive_strength\":{"); if (tm.emissive_strength != 1) { comma(json); append(json, "\"emissiveStrength\":"); append(json, tm.emissive_strength); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_iridescence& tm, const QuantizationTexture* qt, std::vector& textures) { comma(json); append(json, "\"KHR_materials_iridescence\":{"); if (tm.iridescence_factor != 0) { comma(json); append(json, "\"iridescenceFactor\":"); append(json, tm.iridescence_factor); } if (tm.iridescence_texture.texture) { comma(json); append(json, "\"iridescenceTexture\":"); writeTextureInfo(json, data, tm.iridescence_texture, qt, textures); } if (tm.iridescence_ior != 1.3f) { comma(json); append(json, "\"iridescenceIor\":"); append(json, tm.iridescence_ior); } if (tm.iridescence_thickness_min != 100.f) { comma(json); append(json, "\"iridescenceThicknessMinimum\":"); append(json, tm.iridescence_thickness_min); } if (tm.iridescence_thickness_max != 400.f) { comma(json); append(json, "\"iridescenceThicknessMaximum\":"); append(json, tm.iridescence_thickness_max); } if (tm.iridescence_thickness_texture.texture) { comma(json); append(json, "\"iridescenceThicknessTexture\":"); writeTextureInfo(json, data, tm.iridescence_thickness_texture, qt, textures); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_anisotropy& tm, const QuantizationTexture* qt, std::vector& textures) { comma(json); append(json, "\"KHR_materials_anisotropy\":{"); if (tm.anisotropy_strength != 0) { comma(json); append(json, "\"anisotropyStrength\":"); append(json, tm.anisotropy_strength); } if (tm.anisotropy_rotation != 0) { comma(json); append(json, "\"anisotropyRotation\":"); append(json, tm.anisotropy_rotation); } if (tm.anisotropy_texture.texture) { comma(json); append(json, "\"anisotropyTexture\":"); writeTextureInfo(json, data, tm.anisotropy_texture, qt, textures); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_dispersion& tm) { (void)data; comma(json); append(json, "\"KHR_materials_dispersion\":{"); append(json, "\"dispersion\":"); append(json, tm.dispersion); append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_diffuse_transmission& tm, const QuantizationTexture* qt, std::vector& textures) { comma(json); append(json, "\"KHR_materials_diffuse_transmission\":{"); if (tm.diffuse_transmission_factor != 0) { comma(json); append(json, "\"diffuseTransmissionFactor\":"); append(json, tm.diffuse_transmission_factor); } if (tm.diffuse_transmission_texture.texture) { comma(json); append(json, "\"diffuseTransmissionTexture\":"); writeTextureInfo(json, data, tm.diffuse_transmission_texture, qt, textures); } if (memcmp(tm.diffuse_transmission_color_factor, white, sizeof(float) * 3) != 0) { comma(json); append(json, "\"diffuseTransmissionColorFactor\":"); append(json, tm.diffuse_transmission_color_factor, 3); } if (tm.diffuse_transmission_color_texture.texture) { comma(json); append(json, "\"diffuseTransmissionColorTexture\":"); writeTextureInfo(json, data, tm.diffuse_transmission_color_texture, qt, textures); } append(json, "}"); } void writeMaterial(std::string& json, const cgltf_data* data, const cgltf_material& material, const QuantizationPosition* qp, const QuantizationTexture* qt, std::vector& textures) { if (material.name && *material.name) { comma(json); append(json, "\"name\":\""); append(json, material.name); append(json, "\""); } if (material.has_pbr_metallic_roughness) { writeMaterialComponent(json, data, material.pbr_metallic_roughness, qt, textures); } if (material.normal_texture.texture) { comma(json); append(json, "\"normalTexture\":"); writeTextureInfo(json, data, material.normal_texture, qt, textures, "scale"); } if (material.occlusion_texture.texture) { comma(json); append(json, "\"occlusionTexture\":"); writeTextureInfo(json, data, material.occlusion_texture, qt, textures, "strength"); } if (material.emissive_texture.texture) { comma(json); append(json, "\"emissiveTexture\":"); writeTextureInfo(json, data, material.emissive_texture, qt, textures); } if (memcmp(material.emissive_factor, black, 12) != 0) { comma(json); append(json, "\"emissiveFactor\":"); append(json, material.emissive_factor, 3); } if (material.alpha_mode != cgltf_alpha_mode_opaque) { comma(json); append(json, "\"alphaMode\":\""); append(json, alphaMode(material.alpha_mode)); append(json, "\""); } if (material.alpha_cutoff != 0.5f) { comma(json); append(json, "\"alphaCutoff\":"); append(json, material.alpha_cutoff); } if (material.double_sided) { comma(json); append(json, "\"doubleSided\":true"); } if (material.has_pbr_specular_glossiness || material.has_clearcoat || material.has_transmission || material.has_ior || material.has_specular || material.has_sheen || material.has_volume || material.has_emissive_strength || material.has_iridescence || material.has_anisotropy || material.has_dispersion || material.has_diffuse_transmission || material.unlit) { comma(json); append(json, "\"extensions\":{"); if (material.has_pbr_specular_glossiness) writeMaterialComponent(json, data, material.pbr_specular_glossiness, qt, textures); if (material.has_clearcoat) writeMaterialComponent(json, data, material.clearcoat, qt, textures); if (material.has_transmission) writeMaterialComponent(json, data, material.transmission, qt, textures); if (material.has_ior) writeMaterialComponent(json, data, material.ior); if (material.has_specular) writeMaterialComponent(json, data, material.specular, qt, textures); if (material.has_sheen) writeMaterialComponent(json, data, material.sheen, qt, textures); if (material.has_volume) writeMaterialComponent(json, data, material.volume, qp, qt, textures); if (material.has_emissive_strength) writeMaterialComponent(json, data, material.emissive_strength); if (material.has_iridescence) writeMaterialComponent(json, data, material.iridescence, qt, textures); if (material.has_anisotropy) writeMaterialComponent(json, data, material.anisotropy, qt, textures); if (material.has_dispersion) writeMaterialComponent(json, data, material.dispersion); if (material.has_diffuse_transmission) writeMaterialComponent(json, data, material.diffuse_transmission, qt, textures); if (material.unlit) { comma(json); append(json, "\"KHR_materials_unlit\":{}"); } append(json, "}"); } } size_t getBufferView(std::vector& views, BufferView::Kind kind, StreamFormat::Filter filter, BufferView::Compression compression, size_t stride, int variant) { if (variant >= 0) { for (size_t i = 0; i < views.size(); ++i) { BufferView& v = views[i]; if (v.kind == kind && v.filter == filter && v.compression == compression && v.stride == stride && v.variant == variant) return i; } } BufferView view = {kind, filter, compression, stride, variant}; views.push_back(view); return views.size() - 1; } void writeBufferView(std::string& json, BufferView::Kind kind, StreamFormat::Filter filter, size_t count, size_t stride, size_t bin_offset, size_t bin_size, BufferView::Compression compression, size_t compressed_offset, size_t compressed_size, const char* meshopt_ext) { assert(bin_size == count * stride); // when compression is enabled, we store uncompressed data in buffer 1 and compressed data in buffer 0 // when compression is disabled, we store uncompressed data in buffer 0 size_t buffer = compression != BufferView::Compression_None ? 1 : 0; append(json, "{\"buffer\":"); append(json, buffer); append(json, ",\"byteOffset\":"); append(json, bin_offset); append(json, ",\"byteLength\":"); append(json, bin_size); if (kind == BufferView::Kind_Vertex) { append(json, ",\"byteStride\":"); append(json, stride); } if (kind == BufferView::Kind_Vertex || kind == BufferView::Kind_Index) { append(json, ",\"target\":"); append(json, (kind == BufferView::Kind_Vertex) ? "34962" : "34963"); } if (compression != BufferView::Compression_None) { append(json, ",\"extensions\":{"); append(json, "\""); append(json, meshopt_ext); append(json, "\":{"); append(json, "\"buffer\":0"); append(json, ",\"byteOffset\":"); append(json, size_t(compressed_offset)); append(json, ",\"byteLength\":"); append(json, size_t(compressed_size)); append(json, ",\"byteStride\":"); append(json, stride); append(json, ",\"mode\":\""); append(json, compressionMode(compression)); append(json, "\""); if (filter != StreamFormat::Filter_None) { append(json, ",\"filter\":\""); append(json, compressionFilter(filter)); append(json, "\""); } append(json, ",\"count\":"); append(json, count); append(json, "}}"); } append(json, "}"); } static void writeAccessor(std::string& json, size_t view, size_t offset, cgltf_type type, cgltf_component_type component_type, bool normalized, size_t count, const float* min = NULL, const float* max = NULL, size_t numminmax = 0) { append(json, "{\"bufferView\":"); append(json, view); append(json, ",\"byteOffset\":"); append(json, offset); append(json, ",\"componentType\":"); append(json, componentType(component_type)); append(json, ",\"count\":"); append(json, count); append(json, ",\"type\":\""); append(json, shapeType(type)); append(json, "\""); if (normalized) { append(json, ",\"normalized\":true"); } if (min && max) { assert(numminmax); append(json, ",\"min\":"); append(json, min, numminmax); append(json, ",\"max\":"); append(json, max, numminmax); } append(json, "}"); } static void writeEmbeddedImage(std::string& json, std::vector& views, const char* data, size_t size, const char* mime_type, TextureKind kind) { size_t view = getBufferView(views, BufferView::Kind_Image, StreamFormat::Filter_None, BufferView::Compression_None, 1, -1 - kind); assert(views[view].data.empty()); views[view].data.assign(data, size); append(json, "\"bufferView\":"); append(json, view); append(json, ",\"mimeType\":\""); append(json, mime_type); append(json, "\""); } static std::string decodeUri(const char* uri) { std::string result = uri; if (!result.empty()) { cgltf_decode_uri(&result[0]); result.resize(strlen(result.c_str())); } return result; } void writeSampler(std::string& json, const cgltf_sampler& sampler) { if (sampler.mag_filter != cgltf_filter_type_undefined) { comma(json); append(json, "\"magFilter\":"); append(json, size_t(sampler.mag_filter)); } if (sampler.min_filter != cgltf_filter_type_undefined) { comma(json); append(json, "\"minFilter\":"); append(json, size_t(sampler.min_filter)); } if (sampler.wrap_s != cgltf_wrap_mode_repeat) { comma(json); append(json, "\"wrapS\":"); append(json, size_t(sampler.wrap_s)); } if (sampler.wrap_t != cgltf_wrap_mode_repeat) { comma(json); append(json, "\"wrapT\":"); append(json, size_t(sampler.wrap_t)); } } static void writeImageError(std::string& json, const char* action, size_t index, const char* uri, const char* reason) { append(json, "\"uri\":\""); append(json, "data:image/png;base64,ERR/"); append(json, "\""); fprintf(stderr, "Warning: unable to %s image %d (%s), skipping%s%s%s\n", action, int(index), uri ? uri : "embedded", reason ? " (" : "", reason ? reason : "", reason ? ")" : ""); } static void writeImageData(std::string& json, std::vector& views, size_t index, const char* uri, const char* mime_type, const std::string& contents, const char* output_path, TextureKind kind, bool embed) { bool dataUri = uri && strncmp(uri, "data:", 5) == 0; if (!embed && uri && !dataUri && output_path) { std::string file_name = getFileName(uri) + mimeExtension(mime_type); std::string file_path = getFullPath(decodeUri(file_name.c_str()).c_str(), output_path); if (writeFile(file_path.c_str(), contents)) { append(json, "\"uri\":\""); append(json, file_name); append(json, "\""); } else { writeImageError(json, "save", int(index), uri, file_path.c_str()); } } else { writeEmbeddedImage(json, views, contents.c_str(), contents.size(), mime_type, kind); } } void writeImage(std::string& json, std::vector& views, const cgltf_image& image, const ImageInfo& info, const std::string* encoded, size_t index, const char* input_path, const char* output_path, const Settings& settings) { if (image.name && *image.name) { append(json, "\"name\":\""); append(json, image.name); append(json, "\","); } if (encoded) { const char* mime_type = (settings.texture_mode[info.kind] == TextureMode_WebP) ? "image/webp" : "image/ktx2"; // image was pre-encoded via encodeImages (which might have failed!) if (encoded->compare(0, 5, "error") == 0) writeImageError(json, "encode", int(index), image.uri, encoded->c_str()); else writeImageData(json, views, index, image.uri, mime_type, *encoded, output_path, info.kind, settings.texture_embed); return; } bool dataUri = image.uri && strncmp(image.uri, "data:", 5) == 0; if (image.uri && !dataUri && settings.texture_ref) { // fast-path: we don't need to read the image to memory append(json, "\"uri\":\""); append(json, image.uri); append(json, "\""); return; } std::string img_data; std::string mime_type; if (!readImage(image, input_path, img_data, mime_type)) { writeImageError(json, "read", index, image.uri, NULL); return; } writeImageData(json, views, index, image.uri, mime_type.c_str(), img_data, output_path, info.kind, settings.texture_embed); } void writeTexture(std::string& json, const cgltf_texture& texture, const ImageInfo* info, cgltf_data* data, const Settings& settings) { if (texture.sampler) { append(json, "\"sampler\":"); append(json, size_t(texture.sampler - data->samplers)); } if (texture.image) { if (info && settings.texture_mode[info->kind] != TextureMode_Raw) { const char* texture_ext = (settings.texture_mode[info->kind] == TextureMode_WebP) ? "EXT_texture_webp" : "KHR_texture_basisu"; comma(json); append(json, "\"extensions\":{\""); append(json, texture_ext); append(json, "\":{\"source\":"); append(json, size_t(texture.image - data->images)); append(json, "}}"); return; // skip input basisu image if present, as we replace it with the one we encoded } else { comma(json); append(json, "\"source\":"); append(json, size_t(texture.image - data->images)); } } if (texture.basisu_image) { comma(json); append(json, "\"extensions\":{\"KHR_texture_basisu\":{\"source\":"); append(json, size_t(texture.basisu_image - data->images)); append(json, "}}"); } else if (texture.webp_image) { comma(json); append(json, "\"extensions\":{\"EXT_texture_webp\":{\"source\":"); append(json, size_t(texture.webp_image - data->images)); append(json, "}}"); } } static void writePrimitiveAccessor(std::string& json_accessors, const Stream& stream, size_t view, size_t offset, const StreamFormat& format, const QuantizationPosition& qp, const Settings& settings) { comma(json_accessors); if (stream.type == cgltf_attribute_type_position) { float min[3] = {}; float max[3] = {}; getPositionBounds(min, max, stream, qp, settings); writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, stream.data.size(), min, max, 3); } else { writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, stream.data.size()); } } static void writePrimitiveAttribute(std::string& json, const Stream& stream, size_t accessor) { comma(json); append(json, "\""); if (stream.custom_name) { append(json, stream.custom_name); } else { append(json, attributeType(stream.type)); if (stream.type != cgltf_attribute_type_position && stream.type != cgltf_attribute_type_normal && stream.type != cgltf_attribute_type_tangent) { append(json, "_"); append(json, size_t(stream.index)); } } append(json, "\":"); append(json, accessor); } static void writeMeshAttributesInterleaved(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, int target, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings) { struct Attribute { const Stream* stream; StreamFormat format; std::string data; }; std::vector attributes; size_t stride = 0; for (size_t j = 0; j < mesh.streams.size(); ++j) { const Stream& stream = mesh.streams[j]; if (stream.target != target) continue; Attribute attr = {&stream}; attr.format = writeVertexStream(attr.data, stream, qp, qt, settings, /* filters= */ false); assert(attr.format.filter == StreamFormat::Filter_None); stride += attr.format.stride; attributes.emplace_back(std::move(attr)); } BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; size_t view = getBufferView(views, BufferView::Kind_Vertex, StreamFormat::Filter_None, compression, stride, 0); size_t offset = views[view].data.size(); size_t vertex_count = mesh.streams[0].data.size(); views[view].data.resize(views[view].data.size() + stride * vertex_count); size_t write_offset = offset; for (size_t i = 0; i < vertex_count; ++i) for (Attribute& attr : attributes) { memcpy(&views[view].data[write_offset], &attr.data[i * attr.format.stride], attr.format.stride); write_offset += attr.format.stride; } for (Attribute& attr : attributes) { const Stream& stream = *attr.stream; const StreamFormat& format = attr.format; writePrimitiveAccessor(json_accessors, stream, view, offset, format, qp, settings); size_t vertex_accr = accr_offset++; writePrimitiveAttribute(json, stream, vertex_accr); offset += attr.format.stride; } } void writeMeshAttributes(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, int target, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings) { if (settings.mesh_interleaved) return writeMeshAttributesInterleaved(json, views, json_accessors, accr_offset, mesh, target, qp, qt, settings); std::string scratch; for (size_t j = 0; j < mesh.streams.size(); ++j) { const Stream& stream = mesh.streams[j]; if (stream.target != target) continue; scratch.clear(); StreamFormat format = writeVertexStream(scratch, stream, qp, qt, settings); BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; size_t view = getBufferView(views, BufferView::Kind_Vertex, format.filter, compression, format.stride, stream.type); size_t offset = views[view].data.size(); views[view].data += scratch; writePrimitiveAccessor(json_accessors, stream, view, offset, format, qp, settings); size_t vertex_accr = accr_offset++; writePrimitiveAttribute(json, stream, vertex_accr); } } size_t writeMeshIndices(std::vector& views, std::string& json_accessors, size_t& accr_offset, const std::vector& indices, cgltf_primitive_type type, const Settings& settings) { std::string scratch; StreamFormat format = writeIndexStream(scratch, indices); BufferView::Compression compression = settings.compress ? (type == cgltf_primitive_type_triangles ? BufferView::Compression_Index : BufferView::Compression_IndexSequence) : BufferView::Compression_None; size_t view = getBufferView(views, BufferView::Kind_Index, StreamFormat::Filter_None, compression, format.stride); size_t offset = views[view].data.size(); views[view].data += scratch; comma(json_accessors); writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, indices.size()); size_t index_accr = accr_offset++; return index_accr; } void writeMeshGeometry(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings) { append(json, "{\"attributes\":{"); writeMeshAttributes(json, views, json_accessors, accr_offset, mesh, 0, qp, qt, settings); append(json, "}"); if (mesh.type != cgltf_primitive_type_triangles) { append(json, ",\"mode\":"); append(json, size_t(mesh.type - cgltf_primitive_type_points)); } if (mesh.targets) { append(json, ",\"targets\":["); for (size_t j = 0; j < mesh.targets; ++j) { comma(json); append(json, "{"); writeMeshAttributes(json, views, json_accessors, accr_offset, mesh, int(1 + j), qp, qt, settings); append(json, "}"); } append(json, "]"); } if (!mesh.indices.empty()) { size_t index_accr = writeMeshIndices(views, json_accessors, accr_offset, mesh.indices, mesh.type, settings); append(json, ",\"indices\":"); append(json, index_accr); } } static size_t writeAnimationTime(std::vector& views, std::string& json_accessors, size_t& accr_offset, const std::vector& time, const Settings& settings) { std::string scratch; StreamFormat format = writeTimeStream(scratch, time); BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; size_t view = getBufferView(views, BufferView::Kind_Time, StreamFormat::Filter_None, compression, format.stride); size_t offset = views[view].data.size(); views[view].data += scratch; comma(json_accessors); writeAccessor(json_accessors, view, offset, cgltf_type_scalar, format.component_type, format.normalized, time.size(), &time.front(), &time.back(), 1); size_t time_accr = accr_offset++; return time_accr; } static size_t writeAnimationTime(std::vector& views, std::string& json_accessors, size_t& accr_offset, float mint, int frames, float period, const Settings& settings) { std::vector time(frames); for (int j = 0; j < frames; ++j) time[j] = mint + float(j) * period; return writeAnimationTime(views, json_accessors, accr_offset, time, settings); } size_t writeJointBindMatrices(std::vector& views, std::string& json_accessors, size_t& accr_offset, const cgltf_skin& skin, const QuantizationPosition& qp, const Settings& settings) { std::string scratch; for (size_t j = 0; j < skin.joints_count; ++j) { float transform[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; if (skin.inverse_bind_matrices) { cgltf_accessor_read_float(skin.inverse_bind_matrices, j, transform, 16); } if (settings.quantize && !settings.pos_float) { // pos_offset has to be applied first, thus it results in an offset rotated by the bind matrix transform[12] += qp.offset[0] * transform[0] + qp.offset[1] * transform[4] + qp.offset[2] * transform[8]; transform[13] += qp.offset[0] * transform[1] + qp.offset[1] * transform[5] + qp.offset[2] * transform[9]; transform[14] += qp.offset[0] * transform[2] + qp.offset[1] * transform[6] + qp.offset[2] * transform[10]; // node_scale will be applied before the rotation/scale from transform for (int k = 0; k < 12; ++k) transform[k] *= qp.node_scale; } scratch.append(reinterpret_cast(transform), sizeof(transform)); } BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; size_t view = getBufferView(views, BufferView::Kind_Skin, StreamFormat::Filter_None, compression, 64); size_t offset = views[view].data.size(); views[view].data += scratch; comma(json_accessors); writeAccessor(json_accessors, view, offset, cgltf_type_mat4, cgltf_component_type_r_32f, false, skin.joints_count); size_t matrix_accr = accr_offset++; return matrix_accr; } static void writeInstanceData(std::vector& views, std::string& json_accessors, cgltf_animation_path_type type, const std::vector& data, const Settings& settings) { BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; std::string scratch; StreamFormat format = writeKeyframeStream(scratch, type, data, settings); size_t view = getBufferView(views, BufferView::Kind_Instance, format.filter, compression, format.stride, type); size_t offset = views[view].data.size(); views[view].data += scratch; comma(json_accessors); writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, data.size()); } size_t writeInstances(std::vector& views, std::string& json_accessors, size_t& accr_offset, const std::vector& instances, const QuantizationPosition& qp, bool has_color, const Settings& settings) { std::vector position, rotation, scale; position.resize(instances.size()); rotation.resize(instances.size()); scale.resize(instances.size()); Stream color = {cgltf_attribute_type_color}; if (has_color) color.data.resize(instances.size()); for (size_t i = 0; i < instances.size(); ++i) { decomposeTransform(position[i].f, rotation[i].f, scale[i].f, instances[i].transform); if (settings.quantize && !settings.pos_float) { const float* transform = instances[i].transform; // pos_offset has to be applied first, thus it results in an offset rotated by the instance matrix position[i].f[0] += qp.offset[0] * transform[0] + qp.offset[1] * transform[4] + qp.offset[2] * transform[8]; position[i].f[1] += qp.offset[0] * transform[1] + qp.offset[1] * transform[5] + qp.offset[2] * transform[9]; position[i].f[2] += qp.offset[0] * transform[2] + qp.offset[1] * transform[6] + qp.offset[2] * transform[10]; // node_scale will be applied before the rotation/scale from transform scale[i].f[0] *= qp.node_scale; scale[i].f[1] *= qp.node_scale; scale[i].f[2] *= qp.node_scale; } if (has_color) memcpy(color.data[i].f, instances[i].color, sizeof(Attr)); } writeInstanceData(views, json_accessors, cgltf_animation_path_type_translation, position, settings); writeInstanceData(views, json_accessors, cgltf_animation_path_type_rotation, rotation, settings); writeInstanceData(views, json_accessors, cgltf_animation_path_type_scale, scale, settings); size_t result = accr_offset; accr_offset += 3; if (has_color) { BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; std::string scratch; StreamFormat format = writeVertexStream(scratch, color, QuantizationPosition(), QuantizationTexture(), settings); size_t view = getBufferView(views, BufferView::Kind_Instance, format.filter, compression, format.stride, 0); size_t offset = views[view].data.size(); views[view].data += scratch; comma(json_accessors); writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, instances.size()); accr_offset += 1; } return result; } void writeMeshNode(std::string& json, size_t mesh_offset, cgltf_node* node, cgltf_skin* skin, cgltf_data* data, const QuantizationPosition* qp) { comma(json); append(json, "{\"mesh\":"); append(json, mesh_offset); if (skin) { append(json, ",\"skin\":"); append(json, size_t(skin - data->skins)); } if (qp) { append(json, ",\"translation\":"); append(json, qp->offset, 3); append(json, ",\"scale\":["); append(json, qp->node_scale); append(json, ","); append(json, qp->node_scale); append(json, ","); append(json, qp->node_scale); append(json, "]"); } if (node && node->weights_count) { append(json, ",\"weights\":"); append(json, node->weights, node->weights_count); } append(json, "}"); } void writeMeshNodeInstanced(std::string& json, size_t mesh_offset, size_t accr_offset, bool has_color) { comma(json); append(json, "{\"mesh\":"); append(json, mesh_offset); append(json, ",\"extensions\":{\"EXT_mesh_gpu_instancing\":{\"attributes\":{"); comma(json); append(json, "\"TRANSLATION\":"); append(json, accr_offset + 0); comma(json); append(json, "\"ROTATION\":"); append(json, accr_offset + 1); comma(json); append(json, "\"SCALE\":"); append(json, accr_offset + 2); if (has_color) { comma(json); append(json, "\"_COLOR_0\":"); append(json, accr_offset + 3); } append(json, "}}}"); append(json, "}"); } void writeSkin(std::string& json, const cgltf_skin& skin, size_t matrix_accr, const std::vector& nodes, cgltf_data* data) { comma(json); append(json, "{"); if (skin.name && *skin.name) { append(json, "\"name\":\""); append(json, skin.name); append(json, "\","); } append(json, "\"joints\":["); for (size_t j = 0; j < skin.joints_count; ++j) { comma(json); append(json, size_t(nodes[skin.joints[j] - data->nodes].remap)); } append(json, "]"); append(json, ",\"inverseBindMatrices\":"); append(json, matrix_accr); if (skin.skeleton) { comma(json); append(json, "\"skeleton\":"); append(json, size_t(nodes[skin.skeleton - data->nodes].remap)); } append(json, "}"); } void writeNode(std::string& json, const cgltf_node& node, const std::vector& nodes, cgltf_data* data) { const NodeInfo& ni = nodes[&node - data->nodes]; if (node.name && *node.name) { comma(json); append(json, "\"name\":\""); append(json, node.name); append(json, "\""); } if (node.has_translation) { comma(json); append(json, "\"translation\":"); append(json, node.translation, 3); } if (node.has_rotation) { comma(json); append(json, "\"rotation\":"); append(json, node.rotation, 4); } if (node.has_scale) { comma(json); append(json, "\"scale\":"); append(json, node.scale, 3); } if (node.has_matrix) { comma(json); append(json, "\"matrix\":"); append(json, node.matrix, 16); } bool has_children = !ni.mesh_nodes.empty(); for (size_t j = 0; j < node.children_count; ++j) has_children |= nodes[node.children[j] - data->nodes].keep; if (has_children) { comma(json); append(json, "\"children\":["); for (size_t j = 0; j < node.children_count; ++j) { const NodeInfo& ci = nodes[node.children[j] - data->nodes]; if (ci.keep) { comma(json); append(json, size_t(ci.remap)); } } for (size_t j = 0; j < ni.mesh_nodes.size(); ++j) { comma(json); append(json, ni.mesh_nodes[j]); } append(json, "]"); } if (ni.has_mesh) { comma(json); append(json, "\"mesh\":"); append(json, ni.mesh_index); if (ni.mesh_skin) { append(json, ",\"skin\":"); append(json, size_t(ni.mesh_skin - data->skins)); } if (node.weights_count) { append(json, ",\"weights\":"); append(json, node.weights, node.weights_count); } } if (node.camera) { comma(json); append(json, "\"camera\":"); append(json, size_t(node.camera - data->cameras)); } if (node.light) { comma(json); append(json, "\"extensions\":{\"KHR_lights_punctual\":{\"light\":"); append(json, size_t(node.light - data->lights)); append(json, "}}"); } } void writeAnimation(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Animation& animation, size_t i, cgltf_data* data, const std::vector& nodes, const Settings& settings) { std::vector tracks; for (size_t j = 0; j < animation.tracks.size(); ++j) { const Track& track = animation.tracks[j]; const NodeInfo& ni = nodes[track.node - data->nodes]; if (!ni.keep) continue; if (!settings.anim_const && (ni.animated_path_mask & (1 << track.path)) == 0) continue; tracks.push_back(&track); } if (tracks.empty()) { fprintf(stderr, "Warning: ignoring animation %d (%s) because it has no tracks with motion; use -ac to override\n", int(i), animation.name ? animation.name : ""); return; } bool needs_time = false; bool needs_pose = false; for (size_t j = 0; j < tracks.size(); ++j) { const Track& track = *tracks[j]; #ifndef NDEBUG size_t keyframe_size = (track.interpolation == cgltf_interpolation_type_cubic_spline) ? 3 : 1; size_t time_size = track.constant ? 1 : (track.time.empty() ? animation.frames : track.time.size()); assert(track.data.size() == keyframe_size * track.components * time_size); #endif needs_time = needs_time || (track.time.empty() && !track.constant); needs_pose = needs_pose || track.constant; } bool needs_range = needs_pose && !needs_time && animation.frames > 1; needs_pose = needs_pose && !(needs_range && tracks.size() == 1); assert(int(needs_time) + int(needs_pose) + int(needs_range) <= 2); float animation_period = 1.f / float(settings.anim_freq); float animation_length = float(animation.frames - 1) * animation_period; size_t time_accr = needs_time ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, animation.frames, animation_period, settings) : 0; size_t pose_accr = needs_pose ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, 1, 0.f, settings) : 0; size_t range_accr = needs_range ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, 2, animation_length, settings) : 0; std::string json_samplers; std::string json_channels; size_t track_offset = 0; size_t last_track_time_accr = 0; const Track* last_track_time = NULL; for (size_t j = 0; j < tracks.size(); ++j) { const Track& track = *tracks[j]; bool range = needs_range && j == 0; int range_size = range ? 2 : 1; size_t track_time_accr = time_accr; if (!track.time.empty()) { // reuse time accessors between consecutive tracks if possible if (last_track_time && track.time == last_track_time->time) track_time_accr = last_track_time_accr; else { track_time_accr = writeAnimationTime(views, json_accessors, accr_offset, track.time, settings); last_track_time_accr = track_time_accr; last_track_time = &track; } } std::string scratch; StreamFormat format = writeKeyframeStream(scratch, track.path, track.data, settings, track.interpolation == cgltf_interpolation_type_cubic_spline); if (range) { assert(range_size == 2); scratch += scratch; } BufferView::Compression compression = settings.compress && track.path != cgltf_animation_path_type_weights ? BufferView::Compression_Attribute : BufferView::Compression_None; size_t view = getBufferView(views, BufferView::Kind_Keyframe, format.filter, compression, format.stride, track.path); size_t offset = views[view].data.size(); views[view].data += scratch; comma(json_accessors); writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, track.data.size() * range_size); size_t data_accr = accr_offset++; comma(json_samplers); append(json_samplers, "{\"input\":"); append(json_samplers, range ? range_accr : (track.constant ? pose_accr : track_time_accr)); append(json_samplers, ",\"output\":"); append(json_samplers, data_accr); if (track.interpolation != cgltf_interpolation_type_linear) { append(json_samplers, ",\"interpolation\":\""); append(json_samplers, interpolationType(track.interpolation)); append(json_samplers, "\""); } append(json_samplers, "}"); const NodeInfo& tni = nodes[track.node - data->nodes]; size_t target_node = size_t(tni.remap); // when animating morph weights, quantization may move mesh assignments to a mesh node in which case we need to move the animation output if (track.path == cgltf_animation_path_type_weights && tni.mesh_nodes.size() == 1) target_node = tni.mesh_nodes[0]; comma(json_channels); append(json_channels, "{\"sampler\":"); append(json_channels, track_offset); append(json_channels, ",\"target\":{\"node\":"); append(json_channels, target_node); append(json_channels, ",\"path\":\""); append(json_channels, animationPath(track.path)); append(json_channels, "\"}}"); track_offset++; } comma(json); append(json, "{"); if (animation.name && *animation.name) { append(json, "\"name\":\""); append(json, animation.name); append(json, "\","); } append(json, "\"samplers\":["); append(json, json_samplers); append(json, "],\"channels\":["); append(json, json_channels); append(json, "]}"); } void writeCamera(std::string& json, const cgltf_camera& camera) { comma(json); append(json, "{"); switch (camera.type) { case cgltf_camera_type_perspective: append(json, "\"type\":\"perspective\",\"perspective\":{"); append(json, "\"yfov\":"); append(json, camera.data.perspective.yfov); append(json, ",\"znear\":"); append(json, camera.data.perspective.znear); if (camera.data.perspective.aspect_ratio != 0.f) { append(json, ",\"aspectRatio\":"); append(json, camera.data.perspective.aspect_ratio); } if (camera.data.perspective.zfar != 0.f) { append(json, ",\"zfar\":"); append(json, camera.data.perspective.zfar); } append(json, "}"); break; case cgltf_camera_type_orthographic: append(json, "\"type\":\"orthographic\",\"orthographic\":{"); append(json, "\"xmag\":"); append(json, camera.data.orthographic.xmag); append(json, ",\"ymag\":"); append(json, camera.data.orthographic.ymag); append(json, ",\"znear\":"); append(json, camera.data.orthographic.znear); append(json, ",\"zfar\":"); append(json, camera.data.orthographic.zfar); append(json, "}"); break; default: fprintf(stderr, "Warning: skipping camera of unknown type\n"); } append(json, "}"); } void writeLight(std::string& json, const cgltf_light& light) { comma(json); append(json, "{\"type\":\""); append(json, lightType(light.type)); append(json, "\""); if (memcmp(light.color, white, 12) != 0) { comma(json); append(json, "\"color\":"); append(json, light.color, 3); } if (light.intensity != 1.f) { comma(json); append(json, "\"intensity\":"); append(json, light.intensity); } if (light.range != 0.f) { comma(json); append(json, "\"range\":"); append(json, light.range); } if (light.type == cgltf_light_type_spot) { comma(json); append(json, "\"spot\":{"); append(json, "\"innerConeAngle\":"); append(json, light.spot_inner_cone_angle); append(json, ",\"outerConeAngle\":"); append(json, light.spot_outer_cone_angle == 0.f ? 0.78539816339f : light.spot_outer_cone_angle); append(json, "}"); } append(json, "}"); } void writeArray(std::string& json, const char* name, const std::string& contents) { if (contents.empty()) return; comma(json); append(json, "\""); append(json, name); append(json, "\":["); append(json, contents); append(json, "]"); } void writeExtensions(std::string& json, const ExtensionInfo* extensions, size_t count) { bool used_extensions = false; bool required_extensions = false; for (size_t i = 0; i < count; ++i) { used_extensions |= extensions[i].used; required_extensions |= extensions[i].used && extensions[i].required; } if (used_extensions) { comma(json); append(json, "\"extensionsUsed\":["); for (size_t i = 0; i < count; ++i) if (extensions[i].used) { comma(json); append(json, "\""); append(json, extensions[i].name); append(json, "\""); } append(json, "]"); } if (required_extensions) { comma(json); append(json, "\"extensionsRequired\":["); for (size_t i = 0; i < count; ++i) if (extensions[i].used && extensions[i].required) { comma(json); append(json, "\""); append(json, extensions[i].name); append(json, "\""); } append(json, "]"); } } void writeExtras(std::string& json, const cgltf_extras& extras) { if (!extras.data) return; comma(json); append(json, "\"extras\":"); appendJson(json, extras.data); } void writeScene(std::string& json, const cgltf_scene& scene, const std::string& roots, const Settings& settings) { comma(json); append(json, "{"); if (scene.name && *scene.name) { append(json, "\"name\":\""); append(json, scene.name); append(json, "\""); } if (!roots.empty()) { comma(json); append(json, "\"nodes\":["); append(json, roots); append(json, "]"); } if (settings.keep_extras) writeExtras(json, scene.extras); append(json, "}"); } ================================================ FILE: js/README.md ================================================ # meshoptimizer.js This folder contains JavaScript/WebAssembly modules that can be used to access parts of functionality of meshoptimizer library. While normally these would be used internally by glTF loaders, processors and other Web optimization tools, they can also be used directly if needed. The modules are available as an [NPM package](https://www.npmjs.com/package/meshoptimizer) but can also be redistributed individually on a file-by-file basis. When using the NPM package, package exports can be used to import individual components (e.g. `meshoptimizer/decoder`) as well as the entire package (`meshoptimizer`). ## Structure Each component comes in a separate file, `meshopt_component.js`, which uses ES6 module exports and can be imported from another ES6 module. The export name is MeshoptComponent and is an object that has two fields: - `supported` is a boolean that can be checked to see if the component is supported by the current execution environment; it will generally be `false` when WebAssembly is not supported or enabled. To use these components on browsers without WebAssembly a polyfill library is recommended. - `ready` is a Promise that is resolved when WebAssembly compilation and initialization finishes; any functions are unsafe to call before that happens. In addition to that, each component exposes a set of specific functions documented below. ## Decoder `MeshoptDecoder` (`meshopt_decoder.mjs`) implements high performance decompression of attribute and index buffers encoded using meshopt compression. This can be used to decompress glTF buffers encoded with `EXT_meshopt_compression` extension or for custom geometry compression pipelines. The module contains two implementations, scalar and SIMD, with the best performing implementation selected automatically. When SIMD is available, the decoders run at 1-3 GB/s on modern desktop computers. > Note: for maximum compatibility, MeshoptDecoder is also available as CommonJS module via `meshopt_decoder.cjs`; it can be used by a wide variety of JavaScript module loaders, including node.js require(), AMD, Common.JS, and can also be loaded into the web page directly via a `